Merge "Use colorSecondary for darker bg color." into ub-launcher3-calgary
diff --git a/res/drawable/all_apps_search_bg.xml b/res/drawable/all_apps_search_bg.xml
index b0ed9b5..cf63d41 100644
--- a/res/drawable/all_apps_search_bg.xml
+++ b/res/drawable/all_apps_search_bg.xml
@@ -15,14 +15,6 @@
      limitations under the License.
 -->
 <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-    <item>
-        <shape android:shape="rectangle">
-            <solid android:color="@color/quantum_panel_bg_color" />
-            <corners
-                android:topLeftRadius="2dp"
-                android:topRightRadius="2dp" />
-        </shape>
-    </item>
     <item
         android:top="@dimen/all_apps_search_bar_bg_overflow"
         android:left="@dimen/all_apps_search_bar_bg_overflow"
@@ -33,7 +25,7 @@
             <solid android:color="@android:color/transparent" />
             <stroke
                 android:width="@dimen/all_apps_search_bar_divider_width"
-                android:color="#1E000000"/>
+                android:color="?android:attr/colorAccent"/>
         </shape>
     </item>
 </layer-list>
\ No newline at end of file
diff --git a/res/drawable/all_apps_search_hint.xml b/res/drawable/all_apps_search_hint.xml
new file mode 100644
index 0000000..b2ff7a4
--- /dev/null
+++ b/res/drawable/all_apps_search_hint.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@android:color/transparent" android:state_focused="true" />
+    <item android:color="?android:attr/colorAccent"/>
+</selector>
\ No newline at end of file
diff --git a/res/drawable/bg_white_pill.xml b/res/drawable/bg_white_pill.xml
new file mode 100644
index 0000000..29c3145
--- /dev/null
+++ b/res/drawable/bg_white_pill.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <solid android:color="@color/quantum_panel_bg_color" />
+    <size android:height="48dp" />
+    <corners android:radius="24dp" />
+</shape>
\ No newline at end of file
diff --git a/res/drawable/ic_allapps_search.xml b/res/drawable/ic_allapps_search.xml
new file mode 100644
index 0000000..2aeb947
--- /dev/null
+++ b/res/drawable/ic_allapps_search.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+     Copyright (C) 2016 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
+    <path
+        android:fillColor="?android:attr/colorAccent"
+        android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z" />
+</vector>
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml
index 1b843ed..a0b73f0 100644
--- a/res/layout/all_apps.xml
+++ b/res/layout/all_apps.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 The Android Open Source Project
+<!-- Copyright (C) 2016 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.
@@ -16,15 +16,14 @@
 <!-- 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"
     android:layout_height="match_parent"
-    android:paddingTop="@dimen/container_bounds_inset"
-    android:paddingBottom="@dimen/container_bounds_inset"
     android:orientation="vertical"
+    android:paddingBottom="@dimen/container_bounds_inset"
+    android:paddingTop="@dimen/container_bounds_inset"
     launcher:revealBackground="@drawable/quantum_panel_shape">
 
     <View
@@ -32,60 +31,70 @@
         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" />
 
 
     <com.android.launcher3.allapps.AllAppsRecyclerViewContainerView
+        android:id="@+id/main_content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:id="@+id/main_content"
-        android:saveEnabled="false"
-        android:visibility="gone"
         android:layout_gravity="center"
+        android:elevation="15dp"
         android:focusable="true"
         android:focusableInTouchMode="true"
-        android:elevation="15dp" >
+        android:saveEnabled="false"
+        android:visibility="gone">
 
         <!-- DO NOT CHANGE THE ID -->
         <com.android.launcher3.allapps.AllAppsRecyclerView
             android:id="@+id/apps_list_view"
-            android:theme="@style/CustomOverscroll.Light"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:layout_gravity="center_horizontal|top"
-            android:clipToPadding="false"
-            android:focusable="true"
             android:layout_marginTop="@dimen/all_apps_search_bar_height"
-            android:descendantFocusability="afterDescendants" />
+            android:clipToPadding="false"
+            android:descendantFocusability="afterDescendants"
+            android:focusable="true"
+            android:theme="@style/CustomOverscroll.Light" />
 
         <LinearLayout
             android:id="@+id/search_container"
             android:layout_width="match_parent"
-            android:saveEnabled="false"
             android:layout_height="@dimen/all_apps_search_bar_height"
-            android:layout_gravity="start|top"
+            android:layout_gravity="center|top"
+            android:paddingLeft="@dimen/container_fastscroll_thumb_max_width"
+            android:paddingRight="@dimen/container_fastscroll_thumb_max_width"
+            android:background="@drawable/all_apps_search_bg"
+            android:gravity="center|bottom"
             android:orientation="horizontal"
-            android:background="@drawable/all_apps_search_bg" >
+            android:saveEnabled="false">
+
+            <ImageView
+                android:id="@+id/search_icon"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:scaleType="fitCenter"
+                android:layout_marginTop="@dimen/all_apps_search_bar_icon_margin_top"
+                android:paddingEnd="@dimen/all_apps_search_bar_icon_margin_right"
+                android:src="@drawable/ic_allapps_search" />
 
             <com.android.launcher3.ExtendedEditText
                 android:id="@+id/search_box_input"
-                android:layout_width="match_parent"
+                android:layout_width="wrap_content"
                 android:layout_height="match_parent"
                 android:background="@android:color/transparent"
                 android:focusableInTouchMode="true"
-                android:gravity="fill_horizontal|center_vertical"
+                android:gravity="start|center_vertical"
                 android:hint="@string/all_apps_search_bar_hint"
-                android:inputType="text|textNoSuggestions|textCapWords"
                 android:imeOptions="actionSearch|flagNoExtractUi"
+                android:inputType="text|textNoSuggestions|textCapWords"
                 android:maxLines="1"
                 android:scrollHorizontally="true"
-                android:layout_marginLeft="@dimen/container_fastscroll_thumb_max_width"
-                android:layout_marginRight="@dimen/container_fastscroll_thumb_max_width"
                 android:singleLine="true"
                 android:textColor="#4c4c4c"
-                android:textColorHint="#9c9c9c"
+                android:textColorHint="@drawable/all_apps_search_hint"
                 android:textSize="16sp" />
         </LinearLayout>
 
diff --git a/res/layout/deep_shortcut.xml b/res/layout/deep_shortcut.xml
new file mode 100644
index 0000000..b879235
--- /dev/null
+++ b/res/layout/deep_shortcut.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.launcher3.shortcuts.DeepShortcutView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    style="@style/Icon.DeepShortcut"
+    android:focusable="true"/>
diff --git a/res/layout/deep_shortcuts_container.xml b/res/layout/deep_shortcuts_container.xml
new file mode 100644
index 0000000..0441995
--- /dev/null
+++ b/res/layout/deep_shortcuts_container.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.launcher3.shortcuts.DeepShortcutsContainer
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/deep_shortcuts_container"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:elevation="@dimen/deep_shortcuts_elevation"
+    android:orientation="vertical">
+
+</com.android.launcher3.shortcuts.DeepShortcutsContainer>
\ No newline at end of file
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index ef550d7..6303190 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -72,8 +72,7 @@
     <string name="wallpaper_button_text" msgid="8404103075899945851">"Обои"</string>
     <string name="settings_button_text" msgid="8119458837558863227">"Настройки"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Функция отключена администратором"</string>
-    <!-- no translation found for accessibility_action_overview (6257665857640347026) -->
-    <skip />
+    <string name="accessibility_action_overview" msgid="6257665857640347026">"Обзор"</string>
     <string name="allow_rotation_title" msgid="3132336367556833843">"Разрешить поворачивать главный экран"</string>
     <string name="allow_rotation_desc" msgid="7635719920854330492">"При повороте устройства"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"В настройках отключен поворот экрана"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 5e2c44e..d18352c 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -72,8 +72,7 @@
     <string name="wallpaper_button_text" msgid="8404103075899945851">"壁纸"</string>
     <string name="settings_button_text" msgid="8119458837558863227">"设置"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"已被您的管理员停用"</string>
-    <!-- no translation found for accessibility_action_overview (6257665857640347026) -->
-    <skip />
+    <string name="accessibility_action_overview" msgid="6257665857640347026">"概览"</string>
     <string name="allow_rotation_title" msgid="3132336367556833843">"允许旋转主屏幕"</string>
     <string name="allow_rotation_desc" msgid="7635719920854330492">"设备旋转时"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"当前的显示设置不允许旋转设备"</string>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 02c6c70..c8a05b7 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -64,6 +64,8 @@
     <dimen name="all_apps_grid_section_y_offset">8dp</dimen>
     <dimen name="all_apps_grid_section_text_size">24sp</dimen>
     <dimen name="all_apps_search_bar_height">60dp</dimen>
+    <dimen name="all_apps_search_bar_icon_margin_right">4dp</dimen>
+    <dimen name="all_apps_search_bar_icon_margin_top">1dp</dimen>
     <dimen name="all_apps_icon_top_bottom_padding">8dp</dimen>
     <dimen name="all_apps_icon_width_gap">24dp</dimen>
     <!-- The top padding should account for the existing all_apps_list_top_bottom_padding -->
@@ -76,7 +78,7 @@
     <dimen name="all_apps_background_canvas_height">475dp</dimen>
 
     <!-- Search bar in All Apps -->
-    <dimen name="all_apps_header_max_elevation">4dp</dimen>
+    <dimen name="all_apps_header_max_elevation">3dp</dimen>
     <dimen name="all_apps_header_scroll_to_elevation">16dp</dimen>
     <dimen name="all_apps_header_shadow_height">6dp</dimen>
 
@@ -154,4 +156,14 @@
     <dimen name="pending_widget_min_padding">8dp</dimen>
     <dimen name="pending_widget_elevation">2dp</dimen>
 
+<!-- Deep shortcuts -->
+    <dimen name="deep_shortcuts_elevation">9dp</dimen>
+    <dimen name="deep_shortcuts_width">180dp</dimen>
+    <dimen name="deep_shortcuts_spacing">4dp</dimen>
+    <dimen name="deep_shortcuts_drag_view_scale">6dp</dimen>
+    <!-- an icon with shortcuts must be dragged this much distance away from the nearest edge
+         of the shortcut container before the container is removed. -->
+    <dimen name="deep_shortcuts_start_drag_threshold">35dp</dimen>
+    <dimen name="deep_shortcut_icon_size">36dp</dimen>
+
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 4e5fcff..11760f4 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -47,7 +47,7 @@
 
     <!-- All Apps -->
     <!-- Search bar text in the apps view. [CHAR_LIMIT=50] -->
-    <string name="all_apps_search_bar_hint">Search Apps&#8230;</string>
+    <string name="all_apps_search_bar_hint">Search Apps</string>
     <!-- Loading apps text. [CHAR_LIMIT=50] -->
     <string name="all_apps_loading_message">Loading Apps&#8230;</string>
     <!-- No-search-results text. [CHAR_LIMIT=50] -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 8ea772b..3e827a6 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -77,6 +77,21 @@
         <item name="customShadows">false</item>
     </style>
 
+    <style name="Icon.DeepShortcut">
+        <item name="android:background">@drawable/bg_white_pill</item>
+        <item name="android:gravity">start|center_vertical</item>
+        <item name="android:minWidth">@dimen/deep_shortcuts_width</item>
+        <item name="android:maxWidth">@dimen/deep_shortcuts_width</item>
+        <item name="android:elevation">@dimen/deep_shortcuts_elevation</item>
+        <item name="android:paddingLeft">7dp</item>
+        <item name="android:drawablePadding">12dp</item>
+        <item name="android:textColor">@color/quantum_panel_text_color</item>
+        <item name="android:shadowRadius">0</item>
+        <item name="customShadows">false</item>
+        <item name="layoutHorizontal">true</item>
+        <item name="iconSizeOverride">@dimen/deep_shortcut_icon_size</item>
+    </style>
+
     <!-- Drop targets -->
     <style name="DropTargetButtonBase">
         <item name="android:drawablePadding">7.5dp</item>
@@ -106,5 +121,4 @@
         <item name="indicatorSize">4dp</item>
         <item name="ringOutset">4dp</item>
     </style>
-
 </resources>
diff --git a/src/com/android/launcher3/BaseContainerView.java b/src/com/android/launcher3/BaseContainerView.java
index 45d0b52..f9c2407 100644
--- a/src/com/android/launcher3/BaseContainerView.java
+++ b/src/com/android/launcher3/BaseContainerView.java
@@ -18,6 +18,8 @@
 
 import android.content.Context;
 import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.InsetDrawable;
 import android.util.AttributeSet;
@@ -60,12 +62,17 @@
             mHorizontalPadding = DeviceProfile.getContainerPadding(context, width);
         }
 
-        TypedArray a = context.obtainStyledAttributes(attrs,
-                R.styleable.BaseContainerView, defStyleAttr, 0);
-        mRevealDrawable = new InsetDrawable(
-                a.getDrawable(R.styleable.BaseContainerView_revealBackground),
-                mHorizontalPadding, 0, mHorizontalPadding, 0);
-        a.recycle();
+        if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && this instanceof AllAppsContainerView) {
+            mRevealDrawable = new InsetDrawable(new ColorDrawable(Color.WHITE), mHorizontalPadding,
+                    0, mHorizontalPadding, 0);
+        } else {
+            TypedArray a = context.obtainStyledAttributes(attrs,
+                    R.styleable.BaseContainerView, defStyleAttr, 0);
+            mRevealDrawable = new InsetDrawable(
+                    a.getDrawable(R.styleable.BaseContainerView_revealBackground),
+                    mHorizontalPadding, 0, mHorizontalPadding, 0);
+            a.recycle();
+        }
     }
 
     @Override
diff --git a/src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java b/src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java
index baf96fe..37584fe 100644
--- a/src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java
+++ b/src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java
@@ -18,6 +18,7 @@
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
 import android.content.res.Resources;
+import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
@@ -31,9 +32,16 @@
 
     private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 1.5f;
 
+    private static final int SHADOW_INSET = 5;
+    private static final int SHADOW_SHIFT_Y = 4;
+    private static final float SHADOW_ALPHA_MULTIPLIER = 0.5f;
+
     private Resources mRes;
     private BaseRecyclerView mRv;
 
+    private Bitmap mShadow;
+    private Paint mShadowPaint;
+
     private Drawable mBg;
     // The absolute bounds of the fast scroller bg
     private Rect mBgBounds = new Rect();
@@ -52,13 +60,20 @@
     public BaseRecyclerViewFastScrollPopup(BaseRecyclerView rv, Resources res) {
         mRes = res;
         mRv = rv;
+
         mBgOriginalSize = res.getDimensionPixelSize(R.dimen.container_fastscroll_popup_size);
         mBg = rv.getContext().getDrawable(R.drawable.container_fastscroll_popup_bg);
         mBg.setBounds(0, 0, mBgOriginalSize, mBgOriginalSize);
+
         mTextPaint = new Paint();
         mTextPaint.setColor(Color.WHITE);
         mTextPaint.setAntiAlias(true);
         mTextPaint.setTextSize(res.getDimensionPixelSize(R.dimen.container_fastscroll_popup_text_size));
+
+        mShadowPaint = new Paint();
+        mShadowPaint.setAntiAlias(true);
+        mShadowPaint.setFilterBitmap(true);
+        mShadowPaint.setDither(true);
     }
 
     /**
@@ -75,6 +90,7 @@
 
     /**
      * Updates the bounds for the fast scroller.
+     *
      * @return the invalidation rect for this update.
      */
     public Rect updateFastScrollerBounds(int lastTouchY) {
@@ -98,7 +114,12 @@
             mBgBounds.top = Math.max(edgePadding,
                     Math.min(mBgBounds.top, mRv.getHeight() - edgePadding - bgHeight));
             mBgBounds.bottom = mBgBounds.top + bgHeight;
+
+            // Generate a bitmap for a shadow matching these bounds
+            mShadow = HolographicOutlineHelper.obtain(
+                    mRv.getContext()).createMediumDropShadow(mBg, false /* shouldCache */);
         } else {
+            mShadow = null;
             mBgBounds.setEmpty();
         }
 
@@ -138,17 +159,32 @@
 
     public void draw(Canvas c) {
         if (isVisible()) {
-            // Draw the fast scroller popup
+            // Determine the alpha and prepare the canvas
+            final int alpha = (int) (mAlpha * 255);
             int restoreCount = c.save(Canvas.MATRIX_SAVE_FLAG);
             c.translate(mBgBounds.left, mBgBounds.top);
             mTmpRect.set(mBgBounds);
             mTmpRect.offsetTo(0, 0);
+
+            // Expand the rect (with a negative inset), translate it, and draw the shadow
+            if (mShadow != null) {
+                mTmpRect.inset(-SHADOW_INSET * 2, -SHADOW_INSET * 2);
+                mTmpRect.offset(0, SHADOW_SHIFT_Y);
+                mShadowPaint.setAlpha((int) (alpha * SHADOW_ALPHA_MULTIPLIER));
+                c.drawBitmap(mShadow, null, mTmpRect, mShadowPaint);
+                mTmpRect.inset(SHADOW_INSET * 2, SHADOW_INSET * 2);
+                mTmpRect.offset(0, -SHADOW_SHIFT_Y);
+            }
+
+            // Draw the background
             mBg.setBounds(mTmpRect);
-            mBg.setAlpha((int) (mAlpha * 255));
+            mBg.setAlpha(alpha);
             mBg.draw(c);
-            mTextPaint.setAlpha((int) (mAlpha * 255));
+
+            // Draw the text
+            mTextPaint.setAlpha(alpha);
             c.drawText(mSectionName, (mBgBounds.width() - mTextBounds.width()) / 2,
-                    mBgBounds.height() - (mBgBounds.height() - mTextBounds.height()) / 2,
+                    mBgBounds.height() - (mBgBounds.height() / 2) - mTextBounds.exactCenterY(),
                     mTextPaint);
             c.restoreToCount(restoreCount);
         }
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 1762ca4..ca60d5c 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -67,6 +67,7 @@
     private final Launcher mLauncher;
     private Drawable mIcon;
     private final Drawable mBackground;
+    private OnLongClickListener mOnLongClickListener;
     private final CheckLongPressHelper mLongPressHelper;
     private final HolographicOutlineHelper mOutlineHelper;
     private final StylusEventHelper mStylusEventHelper;
@@ -271,6 +272,16 @@
     }
 
     @Override
+    public void setOnLongClickListener(OnLongClickListener l) {
+        super.setOnLongClickListener(l);
+        mOnLongClickListener = l;
+    }
+
+    public OnLongClickListener getOnLongClickListener() {
+        return mOnLongClickListener;
+    }
+
+    @Override
     public boolean onTouchEvent(MotionEvent event) {
         // Call the superclass onTouchEvent first, because sometimes it changes the state to
         // isPressed() on an ACTION_UP
diff --git a/src/com/android/launcher3/CheckLongPressHelper.java b/src/com/android/launcher3/CheckLongPressHelper.java
index 483c622..dde733c 100644
--- a/src/com/android/launcher3/CheckLongPressHelper.java
+++ b/src/com/android/launcher3/CheckLongPressHelper.java
@@ -22,10 +22,12 @@
 
 public class CheckLongPressHelper {
 
+    public static final int DEFAULT_LONG_PRESS_TIMEOUT = 300;
+
     @Thunk View mView;
     @Thunk View.OnLongClickListener mListener;
     @Thunk boolean mHasPerformedLongPress;
-    private int mLongPressTimeout = 300;
+    private int mLongPressTimeout = DEFAULT_LONG_PRESS_TIMEOUT;
     private CheckForLongPress mPendingCheckForLongPress;
 
     class CheckForLongPress implements Runnable {
diff --git a/src/com/android/launcher3/HolographicOutlineHelper.java b/src/com/android/launcher3/HolographicOutlineHelper.java
index 6ea06e9..1cff8ef 100644
--- a/src/com/android/launcher3/HolographicOutlineHelper.java
+++ b/src/com/android/launcher3/HolographicOutlineHelper.java
@@ -83,6 +83,7 @@
             int outlineColor) {
         applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, true);
     }
+
     void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color,
             int outlineColor, boolean clipAlpha) {
 
@@ -151,33 +152,44 @@
     }
 
     Bitmap createMediumDropShadow(BubbleTextView view) {
-        Drawable icon = view.getIcon();
-        if (icon == null) {
+        return createMediumDropShadow(view.getIcon(), view.getScaleX(), view.getScaleY(), true);
+    }
+
+    Bitmap createMediumDropShadow(Drawable drawable, boolean shouldCache) {
+        return createMediumDropShadow(drawable, 1f, 1f, shouldCache);
+    }
+
+    Bitmap createMediumDropShadow(Drawable drawable, float scaleX, float scaleY,
+                boolean shouldCache) {
+        if (drawable == null) {
             return null;
         }
-        Rect rect = icon.getBounds();
+        Rect rect = drawable.getBounds();
 
-        int bitmapWidth = (int) (rect.width() * view.getScaleX());
-        int bitmapHeight = (int) (rect.height() * view.getScaleY());
+        int bitmapWidth = (int) (rect.width() * scaleX);
+        int bitmapHeight = (int) (rect.height() * scaleY);
         if (bitmapHeight <= 0 || bitmapWidth <= 0) {
             return null;
         }
 
         int key = (bitmapWidth << 16) | bitmapHeight;
-        Bitmap cache = mBitmapCache.get(key);
+        Bitmap cache = shouldCache ? mBitmapCache.get(key) : null;
         if (cache == null) {
             cache = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ALPHA_8);
             mCanvas.setBitmap(cache);
-            mBitmapCache.put(key, cache);
+
+            if (shouldCache) {
+                mBitmapCache.put(key, cache);
+            }
         } else {
             mCanvas.setBitmap(cache);
             mCanvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
         }
 
         int saveCount = mCanvas.save();
-        mCanvas.scale(view.getScaleX(), view.getScaleY());
+        mCanvas.scale(scaleX, scaleY);
         mCanvas.translate(-rect.left, -rect.top);
-        icon.draw(mCanvas);
+        drawable.draw(mCanvas);
         mCanvas.restoreToCount(saveCount);
         mCanvas.setBitmap(null);
 
@@ -188,7 +200,7 @@
         int resultWidth = bitmapWidth + extraSize;
         int resultHeight = bitmapHeight + extraSize;
         key = (resultWidth << 16) | resultHeight;
-        Bitmap result = mBitmapCache.get(key);
+        Bitmap result = shouldCache ? mBitmapCache.get(key) : null;
         if (result == null) {
             result = Bitmap.createBitmap(resultWidth, resultHeight, Bitmap.Config.ALPHA_8);
             mCanvas.setBitmap(result);
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index f54a2d4..2a94e55 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3;
 
+import android.content.ComponentName;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
@@ -137,6 +138,10 @@
         return null;
     }
 
+    public ComponentName getTargetComponent() {
+        return getIntent() == null ? null : getIntent().getComponent();
+    }
+
     public void writeToValues(ContentValues values) {
         values.put(LauncherSettings.Favorites.ITEM_TYPE, itemType);
         values.put(LauncherSettings.Favorites.CONTAINER, container);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 97af37c..a31c8ae 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -59,7 +59,6 @@
 import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.Environment;
 import android.os.Handler;
 import android.os.Message;
 import android.os.StrictMode;
@@ -113,11 +112,10 @@
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
 import com.android.launcher3.logging.FileLog;
-import com.android.launcher3.logging.LoggerUtils;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.pageindicators.PageIndicator;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageManagerHelper;
@@ -136,7 +134,6 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Locale;
 
 /**
  * Default launcher application.
@@ -1373,7 +1370,9 @@
         mDragController.addDropTarget(mWorkspace);
         mDropTargetBar.setup(mDragController);
 
-        mAllAppsController.setupViews(mAppsView, mHotseat, mWorkspace);
+        if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
+            mAllAppsController.setupViews(mAppsView, mHotseat, mWorkspace);
+        }
 
         if (TestingUtils.MEMORY_DUMP_ENABLED) {
             TestingUtils.addWeightWatcher(this);
@@ -4062,6 +4061,15 @@
         if (LOGD) Log.d(TAG, "bindDeepShortcutMap: " + mDeepShortcutMap);
     }
 
+    public List<String> getShortcutIdsForItem(ItemInfo info) {
+        if (!DeepShortcutManager.supportsShortcuts(info)) {
+            return Collections.EMPTY_LIST;
+        }
+        ComponentName component = info.getTargetComponent();
+        List<String> ids = mDeepShortcutMap.get(new ComponentKey(component, info.user));
+        return ids == null ? Collections.EMPTY_LIST : ids;
+    }
+
     /**
      * A package was updated.
      *
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 3e66654..3e98c13 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -236,7 +236,7 @@
 
     /** Runs the specified runnable immediately if called from the main thread, otherwise it is
      * posted on the main thread handler. */
-    @Thunk void runOnMainThread(Runnable r) {
+    private void runOnMainThread(Runnable r) {
         if (sWorkerThread.getThreadId() == Process.myTid()) {
             // If we are on the worker thread, post onto the main handler
             mHandler.post(r);
@@ -247,7 +247,7 @@
 
     /** Runs the specified runnable immediately if called from the worker thread, otherwise it is
      * posted on the worker thread handler. */
-    @Thunk static void runOnWorkerThread(Runnable r) {
+    private static void runOnWorkerThread(Runnable r) {
         if (sWorkerThread.getThreadId() == Process.myTid()) {
             r.run();
         } else {
@@ -1298,8 +1298,8 @@
                 // If there is already one running, tell it to stop.
                 stopLoaderLocked();
                 mLoaderTask = new LoaderTask(mApp.getContext(), synchronousBindPage);
-                if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
-                        && mAllAppsLoaded && mWorkspaceLoaded && !mIsLoaderTaskRunning) {
+                if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE && mAllAppsLoaded
+                        && mWorkspaceLoaded && mDeepShortcutsLoaded && !mIsLoaderTaskRunning) {
                     mLoaderTask.runBindSynchronousPage(synchronousBindPage);
                 } else {
                     sWorkerThread.setPriority(Thread.NORM_PRIORITY);
@@ -1441,6 +1441,8 @@
             // XXX: For now, continue posting the binding of AllApps as there are other issues that
             //      arise from that.
             onlyBindAllApps();
+
+            bindDeepShortcuts();
         }
 
         public void run() {
@@ -2660,12 +2662,7 @@
                     }
                 }
             };
-            boolean isRunningOnMainThread = !(sWorkerThread.getThreadId() == Process.myTid());
-            if (isRunningOnMainThread) {
-                r.run();
-            } else {
-                mHandler.post(r);
-            }
+            runOnMainThread(r);
         }
 
         private void loadAllApps() {
@@ -2777,7 +2774,7 @@
                     mDeepShortcutsLoaded = true;
                 }
             }
-            bindDeepShortcutMapOnMainThread();
+            bindDeepShortcuts();
         }
 
         public void dumpState() {
@@ -2810,10 +2807,10 @@
         }
     }
 
-    private void bindDeepShortcutMapOnMainThread() {
+    public void bindDeepShortcuts() {
         final MultiHashMap<ComponentKey, String> shortcutMapCopy = new MultiHashMap<>();
         shortcutMapCopy.putAll(mBgDeepShortcutMap);
-        mHandler.post(new Runnable() {
+        Runnable r = new Runnable() {
             @Override
             public void run() {
                 Callbacks callbacks = getCallback();
@@ -2821,7 +2818,8 @@
                     callbacks.bindDeepShortcutMap(shortcutMapCopy);
                 }
             }
-        });
+        };
+        runOnMainThread(r);
     }
 
     /**
@@ -3322,7 +3320,7 @@
 
             // Update the deep shortcut map, in case the list of ids has changed for an activity.
             updateDeepShortcutMap(mPackageName, mShortcuts);
-            bindDeepShortcutMapOnMainThread();
+            bindDeepShortcuts();
         }
     }
 
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 8b42deb..cedbe74 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -47,7 +47,6 @@
 import android.os.Build.VERSION;
 import android.os.Bundle;
 import android.os.PowerManager;
-import android.support.v4.os.BuildCompat;
 import android.text.Spannable;
 import android.text.SpannableString;
 import android.text.TextUtils;
@@ -57,7 +56,9 @@
 import android.util.Pair;
 import android.util.SparseArray;
 import android.util.TypedValue;
+import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewParent;
 import android.widget.Toast;
 
 import com.android.launcher3.compat.UserHandleCompat;
@@ -68,8 +69,10 @@
 import java.io.ByteArrayOutputStream;
 import java.io.Closeable;
 import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Locale;
 import java.util.Set;
 import java.util.concurrent.Executor;
@@ -425,6 +428,30 @@
                 localY < (v.getHeight() + slop);
     }
 
+    /** Translates MotionEvents from src's coordinate system to dst's. */
+    public static void translateEventCoordinates(View src, View dst, MotionEvent dstEvent) {
+        toGlobalMotionEvent(src, dstEvent);
+        toLocalMotionEvent(dst, dstEvent);
+    }
+
+    /**
+     * Emulates View.toGlobalMotionEvent(). This implementation does not handle transformations
+     * (scaleX, scaleY, etc).
+     */
+    private static void toGlobalMotionEvent(View view, MotionEvent event) {
+        view.getLocationOnScreen(sLoc0);
+        event.offsetLocation(sLoc0[0], sLoc0[1]);
+    }
+
+    /**
+     * Emulates View.toLocalMotionEvent(). This implementation does not handle transformations
+     * (scaleX, scaleY, etc).
+     */
+    private static void toLocalMotionEvent(View view, MotionEvent event) {
+        view.getLocationOnScreen(sLoc0);
+        event.offsetLocation(-sLoc0[0], -sLoc0[1]);
+    }
+
     public static int[] getCenterDeltaInScreenSpace(View v0, View v1, int[] delta) {
         v0.getLocationInWindow(sLoc0);
         v1.getLocationInWindow(sLoc1);
@@ -819,6 +846,11 @@
         return true;
     }
 
+    /** Returns whether the collection is null or empty. */
+    public static boolean isEmpty(Collection c) {
+        return c == null || c.isEmpty();
+    }
+
     /**
      * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
      * This allows the badging to be done based on the action bitmap size rather than
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 416da3a..9366c42 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -22,6 +22,8 @@
 import android.animation.LayoutTransition;
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
 import android.app.WallpaperManager;
@@ -75,7 +77,8 @@
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.launcher3.pageindicators.PageIndicator;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.ShortcutsContainerListener;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.LongArrayMap;
@@ -211,8 +214,18 @@
         }
     }
 
+    private static final int HOTSEAT_STATE_ALPHA_INDEX = 2;
+
+    /**
+     * These values correspond to {@link Direction#X} & {@link Direction#Y}
+     */
     private float[] mPageAlpha = new float[] {1, 1};
-    private float[] mHotseatAlpha = new float[] {1, 1};
+    /**
+     * Hotseat alpha can be changed when moving horizontally, vertically, changing states.
+     * The values correspond to {@link Direction#X}, {@link Direction#Y} &
+     * {@link #HOTSEAT_STATE_ALPHA_INDEX} respectively.
+     */
+    private float[] mHotseatAlpha = new float[] {1, 1, 1};
 
     @ViewDebug.ExportedProperty(category = "launcher")
     private State mState = State.NORMAL;
@@ -1082,6 +1095,10 @@
         if (!(child instanceof Folder)) {
             child.setHapticFeedbackEnabled(false);
             child.setOnLongClickListener(mLongClickListener);
+            if (child instanceof BubbleTextView && DeepShortcutManager.supportsShortcuts(info)) {
+                // TODO: only add this listener if the item has shortcuts associated with it.
+                child.setOnTouchListener(new ShortcutsContainerListener((BubbleTextView) child));
+            }
         }
         if (child instanceof DropTarget) {
             mDragController.addDropTarget((DropTarget) child);
@@ -1448,16 +1465,43 @@
      */
     public void setHotseatTranslation(Direction direction, float translation, float alpha) {
         Property<View, Float> property = direction.viewProperty;
-        mHotseatAlpha[direction.ordinal()] = alpha;
-        float finalAlpha = mHotseatAlpha[0] * mHotseatAlpha[1];
-
-        if (mPageIndicator != null) {
-            property.set(mPageIndicator, translation);
-            mPageIndicator.setAlpha(alpha);
-        }
-
+        property.set(mPageIndicator, translation);
         property.set(mLauncher.getHotseat(), translation);
+        setHotseatAlphaAtIndex(alpha, direction.ordinal());
+    }
+
+    private void setHotseatAlphaAtIndex(float alpha, int index) {
+        mHotseatAlpha[index] = alpha;
+        float finalAlpha = mHotseatAlpha[0] * mHotseatAlpha[1] * mHotseatAlpha[2];
+
         mLauncher.getHotseat().setAlpha(finalAlpha);
+        mPageIndicator.setAlpha(finalAlpha);
+    }
+
+    public ValueAnimator createHotseatAlphaAnimator(float finalValue) {
+        if (Float.compare(finalValue, mHotseatAlpha[HOTSEAT_STATE_ALPHA_INDEX]) == 0) {
+            // Return a dummy animator to avoid null checks.
+            return ValueAnimator.ofFloat(0, 0);
+        } else {
+            ValueAnimator animator = ValueAnimator
+                    .ofFloat(mHotseatAlpha[HOTSEAT_STATE_ALPHA_INDEX], finalValue);
+            animator.addUpdateListener(new AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator valueAnimator) {
+                    float value = (Float) valueAnimator.getAnimatedValue();
+                    setHotseatAlphaAtIndex(value, HOTSEAT_STATE_ALPHA_INDEX);
+                }
+            });
+
+            AccessibilityManager am = (AccessibilityManager)
+                    mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE);
+            final boolean accessibilityEnabled = am.isEnabled();
+            animator.addUpdateListener(
+                    new AlphaUpdateListener(mLauncher.getHotseat(), accessibilityEnabled));
+            animator.addUpdateListener(
+                    new AlphaUpdateListener(mPageIndicator, accessibilityEnabled));
+            return animator;
+        }
     }
 
     @Override
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 888cc57..2fcd73d 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -189,11 +189,6 @@
     final @Thunk Workspace mWorkspace;
 
     @Thunk AnimatorSet mStateAnimator;
-    @Thunk float[] mOldBackgroundAlphas;
-    @Thunk float[] mOldAlphas;
-    @Thunk float[] mNewBackgroundAlphas;
-    @Thunk float[] mNewAlphas;
-    @Thunk int mLastChildCount = -1;
 
     @Thunk float mCurrentScale;
     @Thunk float mNewScale;
@@ -248,19 +243,6 @@
     }
 
     /**
-     * Reinitializes the arrays that we need for the animations on each page.
-     */
-    private void reinitializeAnimationArrays() {
-        final int childCount = mWorkspace.getChildCount();
-        if (mLastChildCount == childCount) return;
-
-        mOldBackgroundAlphas = new float[childCount];
-        mOldAlphas = new float[childCount];
-        mNewBackgroundAlphas = new float[childCount];
-        mNewAlphas = new float[childCount];
-    }
-
-    /**
      * Returns the proper animation duration for a transition.
      */
     private int getAnimationDuration(TransitionStates states) {
@@ -282,9 +264,6 @@
     private void animateWorkspace(final TransitionStates states, final boolean animated,
                                   final int duration, final HashMap<View, Integer> layerViews,
                                   final boolean accessibilityEnabled) {
-        // Reinitialize animation arrays for the current workspace state
-        reinitializeAnimationArrays();
-
         // Cancel existing workspace animations and create a new animator set if requested
         cancelAnimation();
         if (animated) {
@@ -294,8 +273,8 @@
         // Update the workspace state
         float finalBackgroundAlpha = (states.stateIsSpringLoaded || states.stateIsOverview) ?
                 1.0f : 0f;
-        float finalHotseatAlpha = (states.stateIsNormal || states.stateIsSpringLoaded) ? 1f : 0f;
-        float finalPageIndicatorAlpha = finalHotseatAlpha;
+        float finalHotseatAlpha = (states.stateIsNormal || states.stateIsSpringLoaded ||
+                (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && states.stateIsNormalHidden)) ? 1f : 0f;
         float finalOverviewPanelAlpha = states.stateIsOverview ? 1f : 0f;
 
         float finalWorkspaceTranslationY = 0;
@@ -325,9 +304,9 @@
         }
 
         int toPage = mWorkspace.getPageNearestToCenterOfScreen();
+        // TODO: Animate the celllayout alpha instead of the pages.
         for (int i = 0; i < childCount; i++) {
             final CellLayout cl = (CellLayout) mWorkspace.getChildAt(i);
-            boolean isCurrentPage = (i == toPage);
             float initialAlpha = cl.getShortcutsAndWidgets().getAlpha();
             float finalAlpha;
             if (states.stateIsOverviewHidden) {
@@ -342,8 +321,9 @@
 
             // If we are animating to/from the small state, then hide the side pages and fade the
             // current page in
-            if (!mWorkspace.isSwitchingState()) {
+            if (!FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && !mWorkspace.isSwitchingState()) {
                 if (states.workspaceToAllApps || states.allAppsToWorkspace) {
+                    boolean isCurrentPage = (i == toPage);
                     if (states.allAppsToWorkspace && isCurrentPage) {
                         initialAlpha = 0f;
                     } else if (!isCurrentPage) {
@@ -353,11 +333,23 @@
                 }
             }
 
-            mOldAlphas[i] = initialAlpha;
-            mNewAlphas[i] = finalAlpha;
             if (animated) {
-                mOldBackgroundAlphas[i] = cl.getBackgroundAlpha();
-                mNewBackgroundAlphas[i] = finalBackgroundAlpha;
+                float oldBackgroundAlpha = cl.getBackgroundAlpha();
+                if (initialAlpha != finalAlpha) {
+                    LauncherViewPropertyAnimator alphaAnim =
+                            new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets());
+                    alphaAnim.alpha(finalAlpha)
+                            .setDuration(duration)
+                            .setInterpolator(mZoomInInterpolator);
+                    mStateAnimator.play(alphaAnim);
+                }
+                if (oldBackgroundAlpha != 0 || finalBackgroundAlpha != 0) {
+                    ValueAnimator bgAnim = ObjectAnimator.ofFloat(cl, "backgroundAlpha",
+                            oldBackgroundAlpha, finalBackgroundAlpha);
+                    bgAnim.setInterpolator(mZoomInInterpolator);
+                    bgAnim.setDuration(duration);
+                    mStateAnimator.play(bgAnim);
+                }
             } else {
                 cl.setBackgroundAlpha(finalBackgroundAlpha);
                 cl.setShortcutAndWidgetAlpha(finalAlpha);
@@ -365,8 +357,6 @@
         }
 
         final ViewGroup overviewPanel = mLauncher.getOverviewPanel();
-        final View hotseat = mLauncher.getHotseat();
-        final View pageIndicator = mWorkspace.getPageIndicator();
         if (animated) {
             LauncherViewPropertyAnimator scale = new LauncherViewPropertyAnimator(mWorkspace);
             scale.scaleX(mNewScale)
@@ -375,49 +365,7 @@
                     .setDuration(duration)
                     .setInterpolator(mZoomInInterpolator);
             mStateAnimator.play(scale);
-            for (int index = 0; index < childCount; index++) {
-                final int i = index;
-                final CellLayout cl = (CellLayout) mWorkspace.getChildAt(i);
-                float currentAlpha = cl.getShortcutsAndWidgets().getAlpha();
-                if (mOldAlphas[i] == 0 && mNewAlphas[i] == 0) {
-                    cl.setBackgroundAlpha(mNewBackgroundAlphas[i]);
-                    cl.setShortcutAndWidgetAlpha(mNewAlphas[i]);
-                } else {
-                    if (layerViews != null) {
-                        layerViews.put(cl, LauncherStateTransitionAnimation.BUILD_LAYER);
-                    }
-                    if (mOldAlphas[i] != mNewAlphas[i] || currentAlpha != mNewAlphas[i]) {
-                        LauncherViewPropertyAnimator alphaAnim =
-                                new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets());
-                        alphaAnim.alpha(mNewAlphas[i])
-                                .setDuration(duration)
-                                .setInterpolator(mZoomInInterpolator);
-                        mStateAnimator.play(alphaAnim);
-                    }
-                    if (mOldBackgroundAlphas[i] != 0 ||
-                            mNewBackgroundAlphas[i] != 0) {
-                        ValueAnimator bgAnim = ObjectAnimator.ofFloat(cl, "backgroundAlpha",
-                                mOldBackgroundAlphas[i], mNewBackgroundAlphas[i]);
-                        bgAnim.setInterpolator(mZoomInInterpolator);
-                        bgAnim.setDuration(duration);
-                        mStateAnimator.play(bgAnim);
-                    }
-                }
-            }
-            Animator pageIndicatorAlpha;
-            if (pageIndicator != null) {
-                pageIndicatorAlpha = new LauncherViewPropertyAnimator(pageIndicator)
-                        .alpha(finalPageIndicatorAlpha).withLayer();
-                pageIndicatorAlpha.addListener(new AlphaUpdateListener(pageIndicator,
-                        accessibilityEnabled));
-            } else {
-                // create a dummy animation so we don't need to do null checks later
-                pageIndicatorAlpha = ValueAnimator.ofFloat(0, 0);
-            }
-
-            LauncherViewPropertyAnimator hotseatAlpha = new LauncherViewPropertyAnimator(hotseat)
-                    .alpha(finalHotseatAlpha);
-            hotseatAlpha.addListener(new AlphaUpdateListener(hotseat, accessibilityEnabled));
+            Animator hotseatAlpha = mWorkspace.createHotseatAlphaAnimator(finalHotseatAlpha);
 
             LauncherViewPropertyAnimator overviewPanelAlpha =
                     new LauncherViewPropertyAnimator(overviewPanel).alpha(finalOverviewPanelAlpha);
@@ -426,36 +374,33 @@
 
             // For animation optimations, we may need to provide the Launcher transition
             // with a set of views on which to force build layers in certain scenarios.
-            hotseat.setLayerType(View.LAYER_TYPE_HARDWARE, null);
             overviewPanel.setLayerType(View.LAYER_TYPE_HARDWARE, null);
             if (layerViews != null) {
                 // If layerViews is not null, we add these views, and indicate that
                 // the caller can manage layer state.
-                layerViews.put(hotseat, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
                 layerViews.put(overviewPanel, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
+                layerViews.put(mLauncher.getHotseat(),
+                        LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
+                layerViews.put(mWorkspace.getPageIndicator(),
+                        LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
             } else {
                 // Otherwise let the animator handle layer management.
-                hotseatAlpha.withLayer();
                 overviewPanelAlpha.withLayer();
             }
 
             if (states.workspaceToOverview) {
-                pageIndicatorAlpha.setInterpolator(new DecelerateInterpolator(2));
                 hotseatAlpha.setInterpolator(new DecelerateInterpolator(2));
                 overviewPanelAlpha.setInterpolator(null);
             } else if (states.overviewToWorkspace) {
-                pageIndicatorAlpha.setInterpolator(null);
                 hotseatAlpha.setInterpolator(null);
                 overviewPanelAlpha.setInterpolator(new DecelerateInterpolator(2));
             }
 
             overviewPanelAlpha.setDuration(duration);
-            pageIndicatorAlpha.setDuration(duration);
             hotseatAlpha.setDuration(duration);
 
             mStateAnimator.play(overviewPanelAlpha);
             mStateAnimator.play(hotseatAlpha);
-            mStateAnimator.play(pageIndicatorAlpha);
             mStateAnimator.addListener(new AnimatorListenerAdapter() {
                 boolean canceled = false;
                 @Override
@@ -476,12 +421,7 @@
         } else {
             overviewPanel.setAlpha(finalOverviewPanelAlpha);
             AlphaUpdateListener.updateVisibility(overviewPanel, accessibilityEnabled);
-            hotseat.setAlpha(finalHotseatAlpha);
-            AlphaUpdateListener.updateVisibility(hotseat, accessibilityEnabled);
-            if (pageIndicator != null) {
-                pageIndicator.setAlpha(finalPageIndicatorAlpha);
-                AlphaUpdateListener.updateVisibility(pageIndicator, accessibilityEnabled);
-            }
+            mWorkspace.createHotseatAlphaAnimator(finalHotseatAlpha).end();
             mWorkspace.updateCustomContentVisibility();
             mWorkspace.setScaleX(mNewScale);
             mWorkspace.setScaleY(mNewScale);
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index e67c9df..868408f 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -18,22 +18,21 @@
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.res.Resources;
-import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
 import android.text.method.TextKeyListener;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.ImageView;
 import android.widget.LinearLayout;
 
 import com.android.launcher3.AppInfo;
@@ -45,14 +44,14 @@
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget;
 import com.android.launcher3.ExtendedEditText;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.folder.Folder;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherTransitionable;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.folder.Folder;
 import com.android.launcher3.keyboard.FocusedItemDecorator;
 import com.android.launcher3.util.ComponentKey;
 
@@ -62,7 +61,6 @@
 import java.util.List;
 
 
-
 /**
  * A merge algorithm that merges every section indiscriminately.
  */
@@ -70,8 +68,8 @@
 
     @Override
     public boolean continueMerging(AlphabeticalAppsList.SectionInfo section,
-           AlphabeticalAppsList.SectionInfo withSection,
-           int sectionAppCount, int numAppsPerRow, int mergeCount) {
+            AlphabeticalAppsList.SectionInfo withSection,
+            int sectionAppCount, int numAppsPerRow, int mergeCount) {
         // Don't merge the predicted apps
         if (section.firstAppItem.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE) {
             return false;
@@ -102,8 +100,8 @@
 
     @Override
     public boolean continueMerging(AlphabeticalAppsList.SectionInfo section,
-           AlphabeticalAppsList.SectionInfo withSection,
-           int sectionAppCount, int numAppsPerRow, int mergeCount) {
+            AlphabeticalAppsList.SectionInfo withSection,
+            int sectionAppCount, int numAppsPerRow, int mergeCount) {
         // Don't merge the predicted apps
         if (section.firstAppItem.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE) {
             return false;
@@ -133,8 +131,7 @@
  * The all apps view container.
  */
 public class AllAppsContainerView extends BaseContainerView implements DragSource,
-        LauncherTransitionable, View.OnTouchListener, View.OnLongClickListener,
-        AllAppsSearchBarController.Callbacks {
+        LauncherTransitionable, View.OnLongClickListener, AllAppsSearchBarController.Callbacks {
 
     private static final int MIN_ROWS_IN_MERGED_SECTION_PHONE = 3;
     private static final int MAX_NUM_MERGES_PHONE = 2;
@@ -153,6 +150,7 @@
 
     private View mSearchContainer;
     private ExtendedEditText mSearchInput;
+    private ImageView mSearchIcon;
     private HeaderElevationController mElevationController;
 
     private SpannableStringBuilder mSearchQueryBuilder = null;
@@ -163,8 +161,6 @@
     private int mRecyclerViewTopBottomPadding;
     // This coordinate is relative to this container view
     private final Point mBoundsCheckLastTouchDownPos = new Point(-1, -1);
-    // This coordinate is relative to its parent
-    private final Point mIconLastTouchPos = new Point();
 
     public AllAppsContainerView(Context context) {
         this(context, null);
@@ -181,7 +177,7 @@
         mLauncher = Launcher.getLauncher(context);
         mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
         mApps = new AlphabeticalAppsList(context);
-        mAdapter = new AllAppsGridAdapter(mLauncher, mApps, this, mLauncher, this);
+        mAdapter = new AllAppsGridAdapter(mLauncher, mApps, mLauncher, this);
         mApps.setAdapter(mAdapter);
         mLayoutManager = mAdapter.getLayoutManager();
         mItemDecoration = mAdapter.getItemDecoration();
@@ -239,6 +235,7 @@
             mSearchBarController.setVisibility(View.INVISIBLE);
         }
     }
+
     /**
      * Sets the search bar that shows above the a-z list.
      */
@@ -268,8 +265,8 @@
         Utilities.mapCoordInSelfToDescendent(mAppsRecyclerView, this, point);
 
         // if the MotionEvent is inside the thumb, container should not be pulled down.
-        if (mAppsRecyclerView.getScrollBar().isNearThumb(point[0], point[1])){
-             return false;
+        if (mAppsRecyclerView.getScrollBar().isNearThumb(point[0], point[1])) {
+            return false;
         }
         // If scroller is at the very top, then it's okay for the container to be pulled down.
         if (Float.compare(0f, mAppsRecyclerView.getScrollBar().getThumbOffset().y) == 0) {
@@ -277,6 +274,7 @@
         }
         return false;
     }
+
     /**
      * Focuses the search field and begins an app search.
      */
@@ -312,6 +310,42 @@
 
         mSearchContainer = findViewById(R.id.search_container);
         mSearchInput = (ExtendedEditText) findViewById(R.id.search_box_input);
+        mSearchIcon = (ImageView) findViewById(R.id.search_icon);
+
+        final LinearLayout.LayoutParams searchParams =
+                (LinearLayout.LayoutParams) mSearchInput.getLayoutParams();
+        mSearchInput.setOnFocusChangeListener(new OnFocusChangeListener() {
+            @Override
+            public void onFocusChange(View view, boolean focused) {
+                if (focused) {
+                    searchParams.width = LayoutParams.MATCH_PARENT;
+                    mSearchInput.setLayoutParams(searchParams);
+                    mSearchInput.setGravity(Gravity.FILL_HORIZONTAL | Gravity.CENTER_VERTICAL);
+                    mSearchIcon.setVisibility(View.GONE);
+                } else {
+                    searchParams.width = LayoutParams.WRAP_CONTENT;
+                    mSearchInput.setLayoutParams(searchParams);
+                    mSearchInput.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
+                    mSearchIcon.setVisibility(View.VISIBLE);
+                }
+            }
+        });
+
+        final OnClickListener searchFocusListener = new OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                if (!mSearchInput.isFocused()) {
+                    mSearchInput.requestFocus();
+                    final InputMethodManager imm =
+                            (InputMethodManager)getContext().getSystemService(
+                                    Context.INPUT_METHOD_SERVICE);
+                    imm.showSoftInput(mSearchInput, 0);
+                }
+            }
+        };
+        mSearchInput.setOnClickListener(searchFocusListener);
+        mSearchContainer.setOnClickListener(searchFocusListener);
+
         mElevationController = Utilities.ATLEAST_LOLLIPOP
                 ? new HeaderElevationController.ControllerVL(mSearchContainer)
                 : new HeaderElevationController.ControllerV16(mSearchContainer);
@@ -386,7 +420,11 @@
                 if (mNumAppsPerRow > 0) {
                     int iconSize = availableWidth / mNumAppsPerRow;
                     int iconSpacing = (iconSize - grid.allAppsIconSizePx) / 2;
-                    mSearchInput.setPaddingRelative(iconSpacing, 0, iconSpacing, 0);
+                    final int thumbMaxWidth =
+                            getResources().getDimensionPixelSize(
+                                    R.dimen.container_fastscroll_thumb_max_width);
+                    mSearchContainer.setPaddingRelative(
+                            iconSpacing + thumbMaxWidth, 0, iconSpacing + thumbMaxWidth, 0);
                 }
             }
             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
@@ -459,7 +497,6 @@
         lp.leftMargin = bgPadding.left;
         lp.rightMargin = bgPadding.right;
 
-
         // Clip the view to the left and right edge of the background to
         // to prevent shadows from rendering beyond the edges
         final Rect newClipBounds = new Rect(
@@ -479,7 +516,7 @@
                 MarginLayoutParams mlp = (MarginLayoutParams) mAppsRecyclerView.getLayoutParams();
 
                 Rect insets = mLauncher.getDragLayer().getInsets();
-                getContentView().setPadding(0,0,0, insets.bottom);
+                getContentView().setPadding(0, 0, 0, insets.bottom);
                 int height = insets.top + grid.hotseatCellHeightPx;
 
                 mlp.topMargin = height;
@@ -489,10 +526,10 @@
                         (LinearLayout.LayoutParams) mSearchInput.getLayoutParams();
                 llp.topMargin = insets.top;
                 mSearchInput.setLayoutParams(llp);
+                mSearchIcon.setLayoutParams(llp);
 
                 lp.height = height;
             }
-            mSearchContainer.getBackground().setAlpha(0);
         }
         mSearchContainer.setLayoutParams(lp);
     }
@@ -529,18 +566,6 @@
         return handleTouchEvent(ev);
     }
 
-    @SuppressLint("ClickableViewAccessibility")
-    @Override
-    public boolean onTouch(View v, MotionEvent ev) {
-        switch (ev.getAction()) {
-            case MotionEvent.ACTION_DOWN:
-            case MotionEvent.ACTION_MOVE:
-                mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
-                break;
-        }
-        return false;
-    }
-
     @Override
     public boolean onLongClick(View v) {
         // Return early if this is not initiated from a touch
@@ -553,7 +578,7 @@
         if (!mLauncher.isDraggingEnabled()) return false;
 
         // Start the drag
-        mLauncher.getWorkspace().beginDragShared(v, mIconLastTouchPos, this, false);
+        mLauncher.getWorkspace().beginDragShared(v, this, false);
         // Enter spring loaded mode
         mLauncher.enterSpringLoadedDragMode();
 
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index ca2556e..6540a23 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -17,17 +17,13 @@
 
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.PointF;
 import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.support.v4.view.accessibility.AccessibilityRecordCompat;
 import android.support.v4.view.accessibility.AccessibilityEventCompat;
-import android.net.Uri;
+import android.support.v4.view.accessibility.AccessibilityRecordCompat;
 import android.support.v7.widget.GridLayoutManager;
 import android.support.v7.widget.RecyclerView;
 import android.view.Gravity;
@@ -38,13 +34,14 @@
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.TextView;
+
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.util.Thunk;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.ShortcutsContainerListener;
 
 import java.util.HashMap;
 import java.util.List;
@@ -331,7 +328,6 @@
     private final GridLayoutManager mGridLayoutMgr;
     private final GridSpanSizer mGridSizer;
     private final GridItemDecoration mItemDecoration;
-    private final View.OnTouchListener mTouchListener;
     private final View.OnClickListener mIconClickListener;
     private final View.OnLongClickListener mIconLongClickListener;
 
@@ -357,8 +353,7 @@
     private Intent mMarketSearchIntent;
 
     public AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps,
-            View.OnTouchListener touchListener, View.OnClickListener iconClickListener,
-            View.OnLongClickListener iconLongClickListener) {
+            View.OnClickListener iconClickListener, View.OnLongClickListener iconLongClickListener) {
         Resources res = launcher.getResources();
         mLauncher = launcher;
         mApps = apps;
@@ -368,7 +363,6 @@
         mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
         mItemDecoration = new GridItemDecoration();
         mLayoutInflater = LayoutInflater.from(launcher);
-        mTouchListener = touchListener;
         mIconClickListener = iconClickListener;
         mIconLongClickListener = iconLongClickListener;
         mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
@@ -454,7 +448,6 @@
                 BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
                         viewType == ICON_VIEW_TYPE ? R.layout.all_apps_icon :
                                 R.layout.all_apps_prediction_bar_icon, parent, false);
-                icon.setOnTouchListener(mTouchListener);
                 icon.setOnClickListener(mIconClickListener);
                 icon.setOnLongClickListener(mIconLongClickListener);
                 icon.setLongPressTimeout(ViewConfiguration.get(parent.getContext())
@@ -490,6 +483,10 @@
                 AppInfo info = mApps.getAdapterItems().get(position).appInfo;
                 BubbleTextView icon = (BubbleTextView) holder.mContent;
                 icon.applyFromApplicationInfo(info);
+                if (DeepShortcutManager.supportsShortcuts(info)) {
+                    // TODO: only add this listener if the item has shortcuts associated with it.
+                    icon.setOnTouchListener(new ShortcutsContainerListener(icon));
+                }
                 icon.setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
                 break;
             }
@@ -497,6 +494,10 @@
                 AppInfo info = mApps.getAdapterItems().get(position).appInfo;
                 BubbleTextView icon = (BubbleTextView) holder.mContent;
                 icon.applyFromApplicationInfo(info);
+                if (DeepShortcutManager.supportsShortcuts(info)) {
+                    // TODO: only add this listener if the item has shortcuts associated with it.
+                    icon.setOnTouchListener(new ShortcutsContainerListener(icon));
+                }
                 icon.setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
                 break;
             }
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 4c51dfe..3ae8cd8 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -11,7 +11,6 @@
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 
-import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Hotseat;
 import com.android.launcher3.Launcher;
@@ -32,7 +31,8 @@
  * If release velocity < THRES1, snap according to either top or bottom depending on whether it's
  *     closer to top or closer to the page indicator.
  */
-public class AllAppsTransitionController implements TouchController, VerticalPullDetector.Listener {
+public class AllAppsTransitionController implements TouchController, VerticalPullDetector.Listener,
+        View.OnLayoutChangeListener {
 
     private static final String TAG = "AllAppsTrans";
     private static final boolean DBG = false;
@@ -99,31 +99,34 @@
             } else {
                 // Now figure out which direction scroll events the controller will start
                 // calling the callbacks.
-                int conditionsToReportScroll = 0;
+                int directionsToDetectScroll = 0;
+                boolean ignoreSlopWhenSettling = false;
 
-                if (mDetector.isRestingState()) {
+                if (mDetector.isIdleState()) {
                     if (mLauncher.isAllAppsVisible()) {
-                        conditionsToReportScroll |= VerticalPullDetector.THRESHOLD_DOWN;
+                        directionsToDetectScroll |= VerticalPullDetector.DIRECTION_DOWN;
                     } else {
-                        conditionsToReportScroll |= VerticalPullDetector.THRESHOLD_UP;
+                        directionsToDetectScroll |= VerticalPullDetector.DIRECTION_UP;
                     }
                 } else {
                     if (isInDisallowRecatchBottomZone()) {
-                        conditionsToReportScroll |= VerticalPullDetector.THRESHOLD_UP;
+                        directionsToDetectScroll |= VerticalPullDetector.DIRECTION_UP;
                     } else if (isInDisallowRecatchTopZone()) {
-                        conditionsToReportScroll |= VerticalPullDetector.THRESHOLD_DOWN;
+                        directionsToDetectScroll |= VerticalPullDetector.DIRECTION_DOWN;
                     } else {
-                        conditionsToReportScroll |= VerticalPullDetector.THRESHOLD_ONLY;
+                        directionsToDetectScroll |= VerticalPullDetector.DIRECTION_BOTH;
+                        ignoreSlopWhenSettling = true;
                     }
                 }
-                mDetector.setDetectableScrollConditions(conditionsToReportScroll);
+                mDetector.setDetectableScrollConditions(directionsToDetectScroll,
+                        ignoreSlopWhenSettling);
             }
         }
         if (mNoIntercept) {
             return false;
         }
         mDetector.onTouchEvent(ev);
-        if (mDetector.isScrollingState() && (isInDisallowRecatchBottomZone() || isInDisallowRecatchTopZone())) {
+        if (mDetector.isSettlingState() && (isInDisallowRecatchBottomZone() || isInDisallowRecatchTopZone())) {
             return false;
         }
         return mDetector.shouldIntercept();
@@ -131,7 +134,7 @@
 
     private boolean shouldPossiblyIntercept(MotionEvent ev) {
         DeviceProfile grid = mLauncher.getDeviceProfile();
-        if (mDetector.isRestingState()) {
+        if (mDetector.isIdleState()) {
             if (grid.isVerticalBarLayout()) {
                 if (ev.getY() > mLauncher.getDeviceProfile().heightPx - mBezelSwipeUpHeight) {
                     return true;
@@ -163,7 +166,7 @@
     }
 
     @Override
-    public void onScrollStart(boolean start) {
+    public void onDragStart(boolean start) {
         cancelAnimation();
         mCurrentAnimation = LauncherAnimUtils.createAnimatorSet();
         mShiftStart = mAppsView.getTranslationY();
@@ -171,18 +174,17 @@
     }
 
     @Override
-    public boolean onScroll(float displacement, float velocity) {
+    public boolean onDrag(float displacement, float velocity) {
         if (mAppsView == null) {
             return false;   // early termination.
         }
-        if (0 <= mShiftStart + displacement && mShiftStart + displacement < mShiftRange) {
-            setProgress(mShiftStart + displacement);
-        }
+        float progress = Math.min(Math.max(0, mShiftStart + displacement), mShiftRange);
+        setProgress(progress);
         return true;
     }
 
     @Override
-    public void onScrollEnd(float velocity, boolean fling) {
+    public void onDragEnd(float velocity, boolean fling) {
         if (mAppsView == null) {
             return; // early termination.
         }
@@ -229,15 +231,11 @@
      */
     public void preparePull(boolean start) {
         if (start) {
-            // Initialize values that should not change until #onScrollEnd
+            // Initialize values that should not change until #onDragEnd
             mStatusBarHeight = mLauncher.getDragLayer().getInsets().top;
             mHotseat.setVisibility(View.VISIBLE);
             mHotseat.bringToFront();
-            if (!mLauncher.getDeviceProfile().isVerticalBarLayout()) {
-                mShiftRange = mHotseat.getTop();
-            } else {
-                mShiftRange = mHotseat.getBottom();
-            }
+
             if (!mLauncher.isAllAppsVisible()) {
                 mLauncher.tryAndUpdatePredictedApps();
 
@@ -248,20 +246,6 @@
                 mAppsView.getContentView().setBackground(null);
                 mAppsView.getRevealView().setVisibility(View.VISIBLE);
                 mAppsView.getRevealView().setAlpha(mHotseatBackgroundAlpha);
-
-                DeviceProfile grid= mLauncher.getDeviceProfile();
-                if (!grid.isVerticalBarLayout()) {
-                    mShiftRange = mHotseat.getTop();
-                } else {
-                    mShiftRange = mHotseat.getBottom();
-                }
-                mAppsView.getRevealView().setAlpha(mHotseatBackgroundAlpha);
-                setProgress(mShiftRange);
-            } else {
-                View child = ((CellLayout) mWorkspace.getChildAt(mWorkspace.getNextPage()))
-                        .getShortcutsAndWidgets();
-                child.setVisibility(View.VISIBLE);
-                child.setAlpha(1f);
             }
         } else {
             setProgress(mShiftCurrent);
@@ -279,17 +263,13 @@
             return;
         }
         int systemUiFlags = mLauncher.getWindow().getDecorView().getSystemUiVisibility();
-        // SYSTEM_UI_FLAG_LIGHT_NAV_BAR == SYSTEM_UI_FLAG_LIGHT_STATUS_BAR << 1
-        // Use proper constant once API is submitted.
         if (enable) {
             mLauncher.getWindow().getDecorView().setSystemUiVisibility(systemUiFlags
-                    | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
-                    | (View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR << 1));
+                    | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
 
         } else {
             mLauncher.getWindow().getDecorView().setSystemUiVisibility(systemUiFlags
-                    & ~(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
-                            |(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR << 1)));
+                    & ~(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR));
 
         }
         mLightStatusBar = enable;
@@ -343,7 +323,7 @@
         if (animationOut == null){
             return;
         }
-        if (mDetector.isRestingState()) {
+        if (mDetector.isIdleState()) {
             preparePull(true);
             mAnimationDuration = duration;
             mShiftStart = mAppsView.getTranslationY();
@@ -383,7 +363,7 @@
         if (animationOut == null){
             return;
         }
-        if(mDetector.isRestingState()) {
+        if(mDetector.isIdleState()) {
             preparePull(true);
             mAnimationDuration = duration;
             mShiftStart = mAppsView.getTranslationY();
@@ -430,6 +410,7 @@
         mAppsView.setVisibility(View.INVISIBLE);
         mHotseat.setBackgroundTransparent(false /* transparent */);
         mHotseat.setVisibility(View.VISIBLE);
+        mAppsView.reset();
         setProgress(mShiftRange);
     }
 
@@ -449,5 +430,19 @@
         mAppsView = appsView;
         mHotseat = hotseat;
         mWorkspace = workspace;
+        mHotseat.addOnLayoutChangeListener(this);
+    }
+
+    @Override
+    public void onLayoutChange(View v, int left, int top, int right, int bottom,
+            int oldLeft, int oldTop, int oldRight, int oldBottom) {
+        if (!mLauncher.getDeviceProfile().isVerticalBarLayout()) {
+            mShiftRange = top;
+        } else {
+            mShiftRange = bottom;
+        }
+        if (!mLauncher.isAllAppsVisible()) {
+            setProgress(mShiftRange);
+        }
     }
 }
diff --git a/src/com/android/launcher3/allapps/VerticalPullDetector.java b/src/com/android/launcher3/allapps/VerticalPullDetector.java
index b54cb00..b058ad2 100644
--- a/src/com/android/launcher3/allapps/VerticalPullDetector.java
+++ b/src/com/android/launcher3/allapps/VerticalPullDetector.java
@@ -18,10 +18,10 @@
 
     private float mTouchSlop;
 
-    private int mScrollDirections;
-    public static final int THRESHOLD_UP = 1 << 0;
-    public static final int THRESHOLD_DOWN = 1 << 1;
-    public static final int THRESHOLD_ONLY = THRESHOLD_DOWN | THRESHOLD_UP;
+    private int mScrollConditions;
+    public static final int DIRECTION_UP = 1 << 0;
+    public static final int DIRECTION_DOWN = 1 << 1;
+    public static final int DIRECTION_BOTH = DIRECTION_DOWN | DIRECTION_UP;
 
 
     /**
@@ -36,42 +36,54 @@
     public static final float SCROLL_VELOCITY_DAMPENING_RC = 1000f / (2f * (float) Math.PI * 10);
 
     /* Scroll state, this is set to true during dragging and animation. */
-    private State mState = State.NONE;
+    private ScrollState mState = ScrollState.IDLE;
 
-    enum State {
-        NONE,
-        CATCH,          // onScrollStart
-        DRAG,           // onScrollStart, onScroll
-        SCROLLING       // onScrollEnd
+    enum ScrollState {
+        IDLE,
+        DRAGGING,      // onDragStart, onDrag
+        SETTLING       // onDragEnd
     };
 
-    //------------------- State transition diagram -----------------------------------
+    //------------------- ScrollState transition diagram -----------------------------------
     //
-    // NONE -> (mDisplacement > mTouchSlop) -> DRAG
-    // DRAG -> (MotionEvent#ACTION_UP, MotionEvent#ACTION_CANCEL) -> SCROLLING
-    // SCROLLING -> (MotionEvent#ACTION_DOWN) && (mDisplacement > mTouchSlop) -> CATCH
-    // SCROLLING -> (View settled) -> NONE
+    // IDLE ->      (mDisplacement > mTouchSlop) -> DRAGGING
+    // DRAGGING -> (MotionEvent#ACTION_UP, MotionEvent#ACTION_CANCEL) -> SETTLING
+    // SETTLING -> (MotionEvent#ACTION_DOWN) -> DRAGGING
+    // SETTLING -> (View settled) -> IDLE
 
-    private void setState(State newState) {
+    private void setState(ScrollState newState) {
         if (DBG) {
             Log.d(TAG, "setState:" + mState + "->" + newState);
         }
+        // onDragStart and onDragEnd is reported ONLY on state transition
+        if (newState == ScrollState.DRAGGING) {
+            initializeDragging();
+            if (mState == ScrollState.IDLE) {
+                reportDragStart(false /* recatch */);
+            } else if (mState == ScrollState.SETTLING) {
+                reportDragStart(true /* recatch */);
+            }
+        }
+        if (newState == ScrollState.SETTLING) {
+            reportDragEnd();
+        }
+
         mState = newState;
     }
 
     public boolean shouldIntercept() {
-        return mState == State.DRAG || mState == State.SCROLLING || mState == State.CATCH;
+        return mState == ScrollState.DRAGGING || mState == ScrollState.SETTLING;
     }
 
     /**
      * There's no touch and there's no animation.
      */
-    public boolean isRestingState() {
-        return mState == State.NONE;
+    public boolean isIdleState() {
+        return mState == ScrollState.IDLE;
     }
 
-    public boolean isScrollingState() {
-        return mState == State.SCROLLING;
+    public boolean isSettlingState() {
+        return mState == ScrollState.SETTLING;
     }
 
     private float mDownX;
@@ -87,6 +99,7 @@
     private float mDisplacementX;
 
     private float mSubtractDisplacement;
+    private boolean mIgnoreSlopWhenSettling;
 
     /* Client of this gesture detector can register a callback. */
     Listener mListener;
@@ -96,17 +109,18 @@
     }
 
     interface Listener{
-        void onScrollStart(boolean start);
-        boolean onScroll(float displacement, float velocity);
-        void onScrollEnd(float velocity, boolean fling);
+        void onDragStart(boolean start);
+        boolean onDrag(float displacement, float velocity);
+        void onDragEnd(float velocity, boolean fling);
     }
 
     public VerticalPullDetector(Context context) {
         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
     }
 
-    public void setDetectableScrollConditions(int scrollDirectionFlags) {
-        mScrollDirections = scrollDirectionFlags;
+    public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) {
+        mScrollConditions = scrollDirectionFlags;
+        mIgnoreSlopWhenSettling = ignoreSlop;
     }
 
     private boolean shouldScrollStart() {
@@ -122,8 +136,8 @@
             return false;
         }
         // Check if the client is interested in scroll in current direction.
-        if (((mScrollDirections & THRESHOLD_DOWN) > 0 && mDisplacementY > 0) ||
-            ((mScrollDirections & THRESHOLD_UP) > 0 && mDisplacementY < 0)) {
+        if (((mScrollConditions & DIRECTION_DOWN) > 0 && mDisplacementY > 0) ||
+            ((mScrollConditions & DIRECTION_UP) > 0 && mDisplacementY < 0)) {
             return true;
         }
         return false;
@@ -136,12 +150,11 @@
                 mDownX = ev.getX();
                 mDownY = ev.getY();
                 mLastDisplacement = 0;
+                mDisplacementY = 0;
                 mVelocity = 0;
 
-                // handle state and listener calls.
-                if (mState == State.SCROLLING && shouldScrollStart()){
-                    reportScrollStart(true /* recatch */);
-                    setState(State.CATCH);
+                if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
+                    setState(ScrollState.DRAGGING);
                 }
                 break;
             case MotionEvent.ACTION_MOVE:
@@ -150,22 +163,18 @@
                 mVelocity = computeVelocity(ev, mVelocity);
 
                 // handle state and listener calls.
-                if (shouldScrollStart() && mState != State.DRAG) {
-                    if (mState == State.NONE) {
-                        reportScrollStart(false /* recatch */);
-                    }
-                    setState(State.DRAG);
+                if (mState != ScrollState.DRAGGING && shouldScrollStart()) {
+                    setState(ScrollState.DRAGGING);
                 }
-                if (mState == State.DRAG) {
-                    reportScroll();
+                if (mState == ScrollState.DRAGGING) {
+                    reportDragging();
                 }
                 break;
             case MotionEvent.ACTION_CANCEL:
             case MotionEvent.ACTION_UP:
                 // These are synthetic events and there is no need to update internal values.
-                if (mState == State.DRAG || mState == State.CATCH) {
-                    reportScrollEnd();
-                    setState(State.SCROLLING);
+                if (mState == ScrollState.DRAGGING) {
+                    setState(ScrollState.SETTLING);
                 }
                 break;
             default:
@@ -182,41 +191,47 @@
     }
 
     public void finishedScrolling() {
-        setState(State.NONE);
+        setState(ScrollState.IDLE);
     }
 
-    private boolean reportScrollStart(boolean recatch) {
-        mListener.onScrollStart(!recatch);
+    private boolean reportDragStart(boolean recatch) {
+        mListener.onDragStart(!recatch);
+        if (DBG) {
+            Log.d(TAG, "onDragStart recatch:" + recatch);
+        }
+        return true;
+    }
+
+    private void initializeDragging() {
+        if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
+            mSubtractDisplacement = 0;
+        }
         if (mDisplacementY > 0) {
             mSubtractDisplacement = mTouchSlop;
         } else {
             mSubtractDisplacement = -mTouchSlop;
         }
-        if (DBG) {
-            Log.d(TAG, "onScrollStart recatch:" + recatch);
-        }
-        return true;
     }
 
-    private boolean reportScroll() {
+    private boolean reportDragging() {
         float delta = mDisplacementY - mLastDisplacement;
         if (delta != 0) {
             if (DBG) {
-                Log.d(TAG, String.format("onScroll disp=%.1f, velocity=%.1f",
+                Log.d(TAG, String.format("onDrag disp=%.1f, velocity=%.1f",
                         mDisplacementY, mVelocity));
             }
 
-            return mListener.onScroll(mDisplacementY - mSubtractDisplacement, mVelocity);
+            return mListener.onDrag(mDisplacementY - mSubtractDisplacement, mVelocity);
         }
         return true;
     }
 
-    private void reportScrollEnd() {
+    private void reportDragEnd() {
         if (DBG) {
             Log.d(TAG, String.format("onScrolEnd disp=%.1f, velocity=%.1f",
                     mDisplacementY, mVelocity));
         }
-        mListener.onScrollEnd(mVelocity, Math.abs(mVelocity) > RELEASE_VELOCITY_PX_MS);
+        mListener.onDragEnd(mVelocity, Math.abs(mVelocity) > RELEASE_VELOCITY_PX_MS);
 
     }
     /**
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index af5ff58..dc93bca 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -45,6 +45,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.accessibility.DragViewStateAnnouncer;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.TouchController;
 
@@ -247,9 +248,12 @@
 
         mDragObject = new DropTarget.DragObject();
 
+        final Resources res = mLauncher.getResources();
+        final float scaleDps = FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND ?
+                res.getDimensionPixelSize(R.dimen.dragViewScale) : 0f;
         final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
                 registrationY, 0, 0, b.getWidth(), b.getHeight(),
-                initialDragViewScale);
+                initialDragViewScale, scaleDps);
 
         mDragObject.dragComplete = false;
         if (mIsAccessibleDrag) {
@@ -284,6 +288,10 @@
         return dragView;
     }
 
+    public Point getMotionDown() {
+        return new Point(mMotionDownX, mMotionDownY);
+    }
+
     /**
      * Call this from a drag source view like this:
      *
diff --git a/src/com/android/launcher3/dragndrop/DragDriver.java b/src/com/android/launcher3/dragndrop/DragDriver.java
index 7ad45f9..2164708 100644
--- a/src/com/android/launcher3/dragndrop/DragDriver.java
+++ b/src/com/android/launcher3/dragndrop/DragDriver.java
@@ -16,12 +16,6 @@
 
 package com.android.launcher3.dragndrop;
 
-import com.android.launcher3.AnotherWindowDropTarget;
-import com.android.launcher3.DropTarget;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
-
 import android.content.ClipData;
 import android.content.Intent;
 import android.graphics.Canvas;
@@ -30,6 +24,12 @@
 import android.view.MotionEvent;
 import android.view.View;
 
+import com.android.launcher3.AnotherWindowDropTarget;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
+
 /**
  * Base class for driving a drag/drop operation.
  */
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 8aed6d8..ce97536 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -43,23 +43,22 @@
 
 import com.android.launcher3.AppWidgetResizeFrame;
 import com.android.launcher3.CellLayout;
+import com.android.launcher3.DropTargetBar;
 import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetHostView;
 import com.android.launcher3.PinchToOverviewListener;
 import com.android.launcher3.R;
-import com.android.launcher3.DropTargetBar;
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
-import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
+import com.android.launcher3.shortcuts.DeepShortcutsContainer;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.TouchController;
 
@@ -193,22 +192,23 @@
     }
 
     public boolean isEventOverHotseat(MotionEvent ev) {
-        getDescendantRectRelativeToSelf(mLauncher.getHotseat(), mHitRect);
-        return mHitRect.contains((int) ev.getX(), (int) ev.getY());
+        return isEventOverView(mLauncher.getHotseat(), ev);
     }
 
     private boolean isEventOverFolderTextRegion(Folder folder, MotionEvent ev) {
-        getDescendantRectRelativeToSelf(folder.getEditTextRegion(), mHitRect);
-        return mHitRect.contains((int) ev.getX(), (int) ev.getY());
+        return isEventOverView(folder.getEditTextRegion(), ev);
     }
 
     private boolean isEventOverFolder(Folder folder, MotionEvent ev) {
-        getDescendantRectRelativeToSelf(folder, mHitRect);
-        return mHitRect.contains((int) ev.getX(), (int) ev.getY());
+        return isEventOverView(folder, ev);
     }
 
     private boolean isEventOverDropTargetBar(MotionEvent ev) {
-        getDescendantRectRelativeToSelf(mLauncher.getDropTargetBar(), mHitRect);
+        return isEventOverView(mLauncher.getDropTargetBar(), ev);
+    }
+
+    private boolean isEventOverView(View view, MotionEvent ev) {
+        getDescendantRectRelativeToSelf(view, mHitRect);
         return mHitRect.contains((int) ev.getX(), (int) ev.getY());
     }
 
@@ -230,6 +230,28 @@
             }
         }
 
+        // Remove the shortcuts container when touching outside of it.
+        DeepShortcutsContainer deepShortcutsContainer = (DeepShortcutsContainer)
+                findViewById(R.id.deep_shortcuts_container);
+        if (deepShortcutsContainer != null) {
+            if (isEventOverView(deepShortcutsContainer, ev)) {
+                // Let the container handle the event.
+                return false;
+            } else {
+                if (isInAccessibleDrag()) {
+                    // Do not close the container if in drag and drop.
+                    if (!isEventOverDropTargetBar(ev)) {
+                        return true;
+                    }
+                } else {
+                    removeView(deepShortcutsContainer);
+                    // We let touches on the original icon go through so that users can launch
+                    // the app with one tap if they don't find a shortcut they want.
+                    return !isEventOverView(deepShortcutsContainer.getDeferredDragIcon(), ev);
+                }
+            }
+        }
+
         Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
         if (currentFolder != null && intercept) {
             if (currentFolder.isEditingName()) {
@@ -258,7 +280,6 @@
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         int action = ev.getAction();
 
-
         if (action == MotionEvent.ACTION_DOWN) {
             if (handleTouchDown(ev, true)) {
                 return true;
@@ -275,6 +296,7 @@
             mActiveController = mDragController;
             return true;
         }
+
         if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && mAllAppsController.onInterceptTouchEvent(ev)) {
             mActiveController = mAllAppsController;
             return true;
@@ -395,7 +417,6 @@
         int x = (int) ev.getX();
         int y = (int) ev.getY();
 
-
         if (action == MotionEvent.ACTION_DOWN) {
             if (handleTouchDown(ev, false)) {
                 return true;
@@ -526,6 +547,10 @@
         return new LayoutParams(p);
     }
 
+    public void setController(TouchController controller) {
+        mActiveController = controller;
+    }
+
     public static class LayoutParams extends InsettableFrameLayout.LayoutParams {
         public int x, y;
         public boolean customPosition = false;
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index b1df41b..a5644ad 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -84,16 +84,13 @@
      * @param registrationY The y coordinate of the registration point.
      */
     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
-    public DragView(Launcher launcher, Bitmap bitmap, int registrationX, int registrationY,
-            int left, int top, int width, int height, final float initialScale) {
+    public DragView(Launcher launcher, Bitmap bitmap, int registrationX, int registrationY, int left,
+            int top, int width, int height, final float initialScale, final float finalScaleDps) {
         super(launcher);
         mDragLayer = launcher.getDragLayer();
         mDragController = launcher.getDragController();
 
-        final Resources res = getResources();
-        final float scaleDps = !FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND ? 0f
-                : res.getDimensionPixelSize(R.dimen.dragViewScale);
-        final float scale = (width + scaleDps) / width;
+        final float scale = (width + finalScaleDps) / width;
 
         // Set the initial scale to avoid any jumps
         setScaleX(initialScale);
@@ -349,12 +346,12 @@
      * @param touchX the x coordinate the user touched in DragLayer coordinates
      * @param touchY the y coordinate the user touched in DragLayer coordinates
      */
-    void move(int touchX, int touchY) {
+    public void move(int touchX, int touchY) {
         setTranslationX(touchX - mRegistrationX);
         setTranslationY(touchY - mRegistrationY);
     }
 
-    void remove() {
+    public void remove() {
         if (getParent() != null) {
             mDragLayer.removeView(DragView.this);
         }
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 2035f99..e94e02f 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -25,7 +25,6 @@
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.Resources;
-import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Build;
@@ -295,7 +294,7 @@
                 return false;
             }
 
-            mLauncher.getWorkspace().beginDragShared(v, new Point(), this, accessible);
+            mLauncher.getWorkspace().beginDragShared(v, this, accessible);
 
             mCurrentDragInfo = item;
             mEmptyCellRank = item.rank;
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index c6fc4cb..82c79a9 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -46,6 +46,8 @@
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.pageindicators.PageIndicator;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.ShortcutsContainerListener;
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
@@ -234,6 +236,10 @@
         textView.applyFromShortcutInfo(item, mIconCache);
         textView.setOnClickListener(mFolder);
         textView.setOnLongClickListener(mFolder);
+        if (DeepShortcutManager.supportsShortcuts(item)) {
+            // TODO: only add this listener if the item has shortcuts associated with it.
+            textView.setOnTouchListener(new ShortcutsContainerListener(textView));
+        }
         textView.setOnFocusChangeListener(mFocusIndicatorHelper);
         textView.setOnKeyListener(mKeyListener);
 
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
index 97c384d..0d5102f 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
@@ -27,6 +27,8 @@
 import android.os.Bundle;
 import android.util.Log;
 
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.UserHandleCompat;
 
@@ -54,6 +56,11 @@
         mLauncherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
     }
 
+    public static boolean supportsShortcuts(ItemInfo info) {
+        return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+                || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+    }
+
     public void onShortcutsChanged(List<ShortcutInfoCompat> shortcuts) {
         // mShortcutCache.removeShortcuts(shortcuts);
     }
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutView.java b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
new file mode 100644
index 0000000..7997d1e
--- /dev/null
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2016 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.shortcuts;
+
+import android.content.Context;
+import android.support.annotation.IntDef;
+import android.util.AttributeSet;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.R;
+
+/**
+ * A {@link BubbleTextView} that represents a deep shortcut within an app.
+ */
+public class DeepShortcutView extends BubbleTextView {
+
+    private static final float HOVER_SCALE = 1.1f;
+    // The direction this view should translate when animating the hover state.
+    // This allows hovered shortcuts to "push" other shortcuts away.
+    @IntDef({DIRECTION_UP, DIRECTION_NONE, DIRECTION_DOWN})
+    public @interface TranslationDirection {}
+
+    public static final int DIRECTION_UP = -1;
+    public static final int DIRECTION_NONE = 0;
+    public static final int DIRECTION_DOWN = 1;
+    @TranslationDirection
+    private int mTranslationDirection = DIRECTION_NONE;
+
+    private int mSpacing;
+    private int mTop;
+    private boolean mIsHoveringOver = false;
+
+    public DeepShortcutView(Context context) {
+        this(context, null, 0);
+    }
+
+    public DeepShortcutView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public DeepShortcutView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        mSpacing = getResources().getDimensionPixelSize(R.dimen.deep_shortcuts_spacing);
+    }
+
+    public int getSpacing() {
+        return mSpacing;
+    }
+
+    /**
+     * Updates the state of this view based on touches over the container before user lifts finger.
+     *
+     * @param containerContainsTouch whether the {@link DeepShortcutsContainer} this shortcut
+     *                               is inside contains the current touch
+     * @param isBelowHoveredShortcut whether a sibling shortcut before this one in the
+     *                               view hierarchy is being hovered over
+     * @param touchY the y coordinate of the touch, relative to the {@link DeepShortcutsContainer}
+     *               this shortcut is inside
+     * @return whether this shortcut is being hovered over
+     */
+    public boolean updateHoverState(boolean containerContainsTouch, boolean isBelowHoveredShortcut,
+            float touchY) {
+        if (!containerContainsTouch) {
+            mIsHoveringOver = false;
+            mTranslationDirection = DIRECTION_NONE;
+        } else if (isBelowHoveredShortcut) {
+            mIsHoveringOver = false;
+            mTranslationDirection = DIRECTION_DOWN;
+        } else {
+            // Include space around the view when determining hover state to avoid gaps.
+            mTop = (int) (getY() - getTranslationY());
+            mIsHoveringOver = (touchY >= mTop - mSpacing / 2)
+                    && (touchY < mTop + getHeight() + mSpacing / 2);
+            mTranslationDirection = mIsHoveringOver ? DIRECTION_NONE : DIRECTION_UP;
+        }
+        animateHoverState();
+        return mIsHoveringOver;
+    }
+
+    /**
+     * If this shortcut is being hovered over, we scale it up. If another shortcut is being hovered
+     * over, we translate this one away from it to account for its increased size.
+     *
+     * TODO: apply motion spec here
+     */
+    private void animateHoverState() {
+        float scale = mIsHoveringOver ? HOVER_SCALE : 1f;
+        setScaleX(scale);
+        setScaleY(scale);
+
+        float translation = (HOVER_SCALE - 1f) * getHeight();
+        setTranslationY(translation * mTranslationDirection);
+    }
+
+    public boolean isHoveringOver() {
+        return mIsHoveringOver;
+    }
+}
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
new file mode 100644
index 0000000..6f3875c
--- /dev/null
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
@@ -0,0 +1,411 @@
+package com.android.launcher3.shortcuts;
+
+import android.animation.Animator;
+import android.annotation.TargetApi;
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.widget.LinearLayout;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.DragSource;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LogDecelerateInterpolator;
+import com.android.launcher3.R;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.util.TouchController;
+import com.android.launcher3.util.UiThreadCircularReveal;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A container for shortcuts to deep links within apps.
+ */
+@TargetApi(Build.VERSION_CODES.N)
+public class DeepShortcutsContainer extends LinearLayout implements  View.OnClickListener,
+        View.OnLongClickListener, View.OnTouchListener, DragSource,
+        UserEventDispatcher.LaunchSourceProvider, TouchController {
+    private static final String TAG = "ShortcutsContainer";
+
+    private Launcher mLauncher;
+    private DeepShortcutManager mDeepShortcutsManager;
+    private final int mDragDeadzone;
+    private final int mStartDragThreshold;
+    private BubbleTextView mDeferredDragIcon;
+    private int mActivePointerId;
+    private Point mTouchDown = null;
+    private DragView mDragView;
+    private float mLastX, mLastY;
+    private float mDistanceDragged = 0;
+    private final Rect mTempRect = new Rect();
+    private final int[] mTempXY = new int[2];
+    private Point mIconLastTouchPos = new Point();
+    private boolean mIsLeftAligned;
+    private boolean mIsAboveIcon;
+
+    public DeepShortcutsContainer(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mLauncher = (Launcher) context;
+        mDeepShortcutsManager = LauncherAppState.getInstance().getShortcutManager();
+
+        mDragDeadzone = ViewConfiguration.get(context).getScaledTouchSlop();
+        mStartDragThreshold = getResources().getDimensionPixelSize(
+                R.dimen.deep_shortcuts_start_drag_threshold);
+    }
+
+    public DeepShortcutsContainer(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public DeepShortcutsContainer(Context context) {
+        this(context, null, 0);
+    }
+
+    public void populateAndShow(final BubbleTextView originalIcon, final List<String> ids) {
+        // Add dummy views first, and populate with real shortcut info when ready.
+        for (int i = 0; i < ids.size(); i++) {
+            final DeepShortcutView shortcut = (DeepShortcutView)
+                    mLauncher.getLayoutInflater().inflate(R.layout.deep_shortcut, this, false);
+            if (i < ids.size() - 1) {
+                ((LayoutParams) shortcut.getLayoutParams()).bottomMargin = shortcut.getSpacing();
+            }
+            addView(shortcut);
+        }
+
+        measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+        animateOpen(originalIcon);
+
+        deferDrag(originalIcon);
+
+        // Load the shortcuts on a background thread and update the container as it animates.
+        final ItemInfo originalInfo = (ItemInfo) originalIcon.getTag();
+        final UserHandleCompat user = originalInfo.user;
+        final ComponentName activity = originalInfo.getTargetComponent();
+        new AsyncTask<Void, Void, List<ShortcutInfo>>() {
+            public List<ShortcutInfo> doInBackground(Void ... args) {
+                List<ShortcutInfoCompat> shortcuts = mDeepShortcutsManager
+                        .queryForAllAppShortcuts(activity, ids, user);
+                List<ShortcutInfo> shortcutInfos = new ArrayList<>(shortcuts.size());
+                for (ShortcutInfoCompat shortcut : shortcuts) {
+                    shortcutInfos.add(ShortcutInfo.fromDeepShortcutInfo(shortcut, mLauncher));
+                }
+                return shortcutInfos;
+            }
+
+            // TODO: implement onProgressUpdate() to load shortcuts one at a time.
+
+            @Override
+            protected void onPostExecute(List<ShortcutInfo> shortcuts) {
+                for (int i = 0; i < shortcuts.size(); i++) {
+                    DeepShortcutView iconAndText = (DeepShortcutView) getChildAt(i);
+                    ShortcutInfo launcherShortcutInfo = shortcuts.get(i);
+                    iconAndText.applyFromShortcutInfo(launcherShortcutInfo,
+                            LauncherAppState.getInstance().getIconCache());
+                    iconAndText.setOnClickListener(DeepShortcutsContainer.this);
+                    iconAndText.setOnLongClickListener(DeepShortcutsContainer.this);
+                    iconAndText.setOnTouchListener(DeepShortcutsContainer.this);
+                    int viewId = mLauncher.getViewIdForItem(originalInfo);
+                    iconAndText.setId(viewId);
+                }
+            }
+        }.execute();
+    }
+
+    // TODO: update this animation
+    private void animateOpen(BubbleTextView originalIcon) {
+        orientAboutIcon(originalIcon);
+
+        setVisibility(View.VISIBLE);
+        int rx = (int) Math.max(Math.max(getMeasuredWidth() - getPivotX(), 0), getPivotX());
+        int ry = (int) Math.max(Math.max(getMeasuredHeight() - getPivotY(), 0), getPivotY());
+        float radius = (float) Math.hypot(rx, ry);
+        Animator reveal = UiThreadCircularReveal.createCircularReveal(this, (int) getPivotX(),
+                (int) getPivotY(), 0, radius);
+        reveal.setDuration(getResources().getInteger(R.integer.config_materialFolderExpandDuration));
+        reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
+        reveal.start();
+    }
+
+    /**
+     * Orients this container above or below the given icon, aligning with the left or right.
+     *
+     * These are the preferred orientations, in order:
+     * - Above and left-aligned
+     * - Above and right-aligned
+     * - Below and left-aligned
+     * - Below and right-aligned
+     *
+     * So we always align left if there is enough horizontal space
+     * and align above if there is enough vertical space.
+     *
+     * TODO: draw pointer based on orientation.
+     */
+    private void orientAboutIcon(BubbleTextView icon) {
+        int width = getMeasuredWidth();
+        int height = getMeasuredHeight();
+
+        DragLayer dragLayer = mLauncher.getDragLayer();
+        dragLayer.getDescendantRectRelativeToSelf(icon, mTempRect);
+        // Align left and above by default.
+        int x = mTempRect.left + icon.getPaddingLeft();
+        int y = mTempRect.top - height;
+        Rect insets = dragLayer.getInsets();
+
+        mIsLeftAligned = x + width < dragLayer.getRight() - insets.right;
+        if (!mIsLeftAligned) {
+            x = mTempRect.right - width - icon.getPaddingRight();
+        }
+
+        mIsAboveIcon = mTempRect.top - height > dragLayer.getTop() + insets.top;
+        if (!mIsAboveIcon) {
+            y = mTempRect.bottom;
+        }
+
+        setPivotX(width / 2);
+        setPivotY(height / 2);
+
+        // Insets are added later, so subtract them now.
+        y -= insets.top;
+
+        setX(x);
+        setY(y);
+    }
+
+    private void deferDrag(BubbleTextView originalIcon) {
+        mDeferredDragIcon = originalIcon;
+        showDragView(originalIcon);
+    }
+
+    public BubbleTextView getDeferredDragIcon() {
+        return mDeferredDragIcon;
+    }
+
+    private void showDragView(BubbleTextView originalIcon) {
+        // TODO: implement support for Drawable DragViews so we don't have to create a bitmap here.
+        Bitmap b = Utilities.createIconBitmap(originalIcon.getIcon(), mLauncher);
+        float scale = mLauncher.getDragLayer().getLocationInDragLayer(originalIcon, mTempXY);
+        int dragLayerX = Math.round(mTempXY[0] - (b.getWidth() - scale * originalIcon.getWidth()) / 2);
+        int dragLayerY = Math.round(mTempXY[1] - (b.getHeight() - scale * b.getHeight()) / 2
+                - Workspace.DRAG_BITMAP_PADDING / 2) + originalIcon.getPaddingTop();
+        int motionDownX = mLauncher.getDragController().getMotionDown().x;
+        int motionDownY = mLauncher.getDragController().getMotionDown().y;
+        final int registrationX = motionDownX - dragLayerX;
+        final int registrationY = motionDownY - dragLayerY;
+
+        float scaleDps = getResources().getDimensionPixelSize(R.dimen.deep_shortcuts_drag_view_scale);
+        mDragView = new DragView(mLauncher, b, registrationX, registrationY,
+                0, 0, b.getWidth(), b.getHeight(), 1f, scaleDps);
+        mLastX = mLastY = mDistanceDragged = 0;
+        mDragView.show(motionDownX, motionDownY);
+    }
+
+    public boolean onForwardedEvent(MotionEvent ev, int activePointerId, MotionEvent touchDownEvent) {
+        mTouchDown = new Point((int) touchDownEvent.getX(), (int) touchDownEvent.getY());
+        mActivePointerId = activePointerId;
+        return dispatchTouchEvent(ev);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        if (mDeferredDragIcon == null) {
+            return false;
+        }
+
+
+        final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
+        if (activePointerIndex < 0) {
+            return false;
+        }
+        final float x = ev.getX(activePointerIndex);
+        final float y = ev.getY(activePointerIndex);
+
+
+        int action = ev.getAction();
+        // The event was in this container's coordinate system before this,
+        // but will be in DragLayer's coordinate system from now on.
+        Utilities.translateEventCoordinates(this, mLauncher.getDragLayer(), ev);
+        final int dragLayerX = (int) ev.getX();
+        final int dragLayerY = (int) ev.getY();
+        int childCount = getChildCount();
+        if (action == MotionEvent.ACTION_MOVE) {
+            if (mLastX != 0 || mLastY != 0) {
+                mDistanceDragged += Math.hypot(mLastX - x, mLastY - y);
+            }
+            mLastX = x;
+            mLastY = y;
+
+            boolean containerContainsTouch = x >= 0 && y >= 0 && x < getWidth() && y < getHeight();
+            if (shouldStartDeferredDrag((int) x, (int) y, containerContainsTouch)) {
+                mDeferredDragIcon.getParent().requestDisallowInterceptTouchEvent(false);
+                mDeferredDragIcon.setVisibility(VISIBLE);
+                mDeferredDragIcon.getOnLongClickListener().onLongClick(mDeferredDragIcon);
+                mLauncher.getDragLayer().removeView(this);
+                mLauncher.getDragController().onTouchEvent(ev);
+                cleanupDeferredDrag();
+                return true;
+            } else {
+                // Determine whether touch is over a shortcut.
+                boolean hoveringOverShortcut = false;
+                for (int i = 0; i < childCount; i++) {
+                    DeepShortcutView shortcut = (DeepShortcutView) getChildAt(i);
+                    if (shortcut.updateHoverState(containerContainsTouch, hoveringOverShortcut, y)) {
+                        hoveringOverShortcut = true;
+                    }
+                }
+
+                if (!hoveringOverShortcut && mDistanceDragged > mDragDeadzone) {
+                    // After dragging further than a small deadzone,
+                    // have the drag view follow the user's finger.
+                    mDragView.setVisibility(VISIBLE);
+                    mDragView.move(dragLayerX, dragLayerY);
+                    mDeferredDragIcon.setVisibility(INVISIBLE);
+                } else if (hoveringOverShortcut) {
+                    // Jump drag view back to original place on grid,
+                    // so user doesn't think they are still dragging.
+                    // TODO: can we improve this interaction? maybe with a ghost icon or similar?
+                    mDragView.setVisibility(INVISIBLE);
+                    mDeferredDragIcon.setVisibility(VISIBLE);
+                }
+            }
+        } else if (action == MotionEvent.ACTION_UP) {
+            mDeferredDragIcon.setVisibility(VISIBLE);
+            cleanupDeferredDrag();
+            // Launch a shortcut if user was hovering over it.
+            for (int i = 0; i < childCount; i++) {
+                DeepShortcutView shortcut = (DeepShortcutView) getChildAt(i);
+                if (shortcut.isHoveringOver()) {
+                    shortcut.performClick();
+                    break;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Determines whether the deferred drag should be started based on touch coordinates
+     * relative to the original icon and the shortcuts container.
+     *
+     * Current behavior:
+     * - Compute distance from original touch down to closest container edge.
+     * - Compute distance from latest touch (given x and y) and compare to original distance;
+     *   if the new distance is larger than a threshold, the deferred drag should start.
+     * - Never defer the drag if this container contains the touch.
+     *
+     * @param x the x touch coordinate relative to this container
+     * @param y the y touch coordinate relative to this container
+     */
+    private boolean shouldStartDeferredDrag(int x, int y, boolean containerContainsTouch) {
+        Point closestEdge = new Point(mTouchDown.x, mIsAboveIcon ? getMeasuredHeight() : 0);
+        double distToEdge = Math.hypot(mTouchDown.x - closestEdge.x, mTouchDown.y - closestEdge.y);
+        double newDistToEdge = Math.hypot(x - closestEdge.x, y - closestEdge.y);
+        return  !containerContainsTouch && (newDistToEdge - distToEdge > mStartDragThreshold);
+    }
+
+    public void cleanupDeferredDrag() {
+        if (mDragView != null) {
+            mDragView.remove();
+        }
+    }
+
+    @Override
+    public boolean onTouch(View v, MotionEvent ev) {
+        // Touched a shortcut, update where it was touched so we can drag from there on long click.
+        switch (ev.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+            case MotionEvent.ACTION_MOVE:
+                mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
+                break;
+        }
+        return false;
+    }
+
+    @Override
+    public void onClick(View view) {
+        // Clicked on a shortcut.
+        mLauncher.onClick(view);
+        ((DragLayer) getParent()).removeView(this);
+    }
+
+    public boolean onLongClick(View v) {
+        // Return early if this is not initiated from a touch
+        if (!v.isInTouchMode()) return false;
+        // Return if global dragging is not enabled
+        if (!mLauncher.isDraggingEnabled()) return false;
+
+        // Long clicked on a shortcut.
+        // TODO remove this hack; it required because DragLayer isn't intercepting touch, so
+        // the controller is not updated from what it was previously.
+        mLauncher.getDragLayer().setController(mLauncher.getDragController());
+        mLauncher.getWorkspace().beginDragShared(v, mIconLastTouchPos, this, false);
+        ((DragLayer) getParent()).removeView(this);
+        // TODO: support dragging from within folder without having to close it
+        mLauncher.closeFolder();
+        return false;
+    }
+
+    @Override
+    public boolean supportsFlingToDelete() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsAppInfoDropTarget() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsDeleteDropTarget() {
+        return true;
+    }
+
+    @Override
+    public float getIntrinsicIconScaleFactor() {
+        return (float) getResources().getDimensionPixelSize(R.dimen.deep_shortcut_icon_size)
+                / mLauncher.getDeviceProfile().iconSizePx;
+    }
+
+    @Override
+    public void onFlingToDeleteCompleted() {
+        // Don't care; ignore.
+    }
+
+    @Override
+    public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
+            boolean success) {
+        if (!success) {
+            d.dragView.remove();
+            mLauncher.showWorkspace(true);
+            mLauncher.getDropTargetBar().onDragEnd();
+        }
+    }
+
+    @Override
+    public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
+        target.itemType = LauncherLogProto.SHORTCUT; // TODO: change to DYNAMIC_SHORTCUT
+        target.gridX = info.cellX;
+        target.gridY = info.cellY;
+        target.pageIndex = 0;
+        targetParent.containerType = LauncherLogProto.FOLDER; // TODO: change to DYNAMIC_SHORTCUTS
+    }
+}
diff --git a/src/com/android/launcher3/shortcuts/ShortcutCache.java b/src/com/android/launcher3/shortcuts/ShortcutCache.java
index fc118a8..d4db96d 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutCache.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutCache.java
@@ -56,6 +56,7 @@
         for (ShortcutInfoCompat shortcut : shortcuts) {
             ShortcutKey key = ShortcutKey.fromInfo(shortcut);
             mCachedShortcuts.remove(key);
+            mPinnedShortcuts.remove(key);
         }
     }
 
diff --git a/src/com/android/launcher3/shortcuts/ShortcutsContainerListener.java b/src/com/android/launcher3/shortcuts/ShortcutsContainerListener.java
new file mode 100644
index 0000000..956623e
--- /dev/null
+++ b/src/com/android/launcher3/shortcuts/ShortcutsContainerListener.java
@@ -0,0 +1,289 @@
+package com.android.launcher3.shortcuts;
+
+import android.content.Context;
+import android.os.SystemClock;
+import android.view.HapticFeedbackConstants;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewParent;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.CheckLongPressHelper;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.dragndrop.DragLayer;
+
+import java.util.List;
+
+/**
+ * A {@link android.view.View.OnTouchListener} that creates a {@link DeepShortcutsContainer} and
+ * forwards touch events to it. This listener should be put on any icon that supports shortcuts.
+ */
+public class ShortcutsContainerListener implements View.OnTouchListener,
+        View.OnAttachStateChangeListener {
+
+    /** Scaled touch slop, used for detecting movement outside bounds. */
+    private final float mScaledTouchSlop;
+
+    /** Timeout before disallowing intercept on the source's parent. */
+    private final int mTapTimeout;
+
+    /** Timeout before accepting a long-press to start forwarding. */
+    private final int mLongPressTimeout;
+
+    /** Source view from which events are forwarded. */
+    private final BubbleTextView mSrcIcon;
+
+    /** Runnable used to prevent conflicts with scrolling parents. */
+    private Runnable mDisallowIntercept;
+
+    /** Runnable used to trigger forwarding on long-press. */
+    private Runnable mTriggerLongPress;
+
+    /** Whether this listener is currently forwarding touch events. */
+    private boolean mForwarding;
+
+    /** The id of the first pointer down in the current event stream. */
+    private int mActivePointerId;
+
+    private Launcher mLauncher;
+    private DragLayer mDragLayer;
+    private MotionEvent mTouchDownEvent;
+
+    public ShortcutsContainerListener(BubbleTextView icon) {
+        mSrcIcon = icon;
+        mScaledTouchSlop = ViewConfiguration.get(icon.getContext()).getScaledTouchSlop();
+        mTapTimeout = ViewConfiguration.getTapTimeout();
+
+        mLongPressTimeout = CheckLongPressHelper.DEFAULT_LONG_PRESS_TIMEOUT;
+
+        icon.addOnAttachStateChangeListener(this);
+
+        mLauncher = Launcher.getLauncher(mSrcIcon.getContext());
+
+        mDragLayer = mLauncher.getDragLayer();
+    }
+
+    @Override
+    public boolean onTouch(View v, MotionEvent event) {
+        if (mLauncher.getShortcutIdsForItem((ItemInfo) v.getTag()).isEmpty()) {
+            // There are no shortcuts associated with this item, so return to normal touch handling.
+            return false;
+        }
+
+        if (event.getAction() == MotionEvent.ACTION_DOWN) {
+            mTouchDownEvent = MotionEvent.obtainNoHistory(event);
+        }
+
+        final boolean wasForwarding = mForwarding;
+        final boolean forwarding;
+        if (wasForwarding) {
+            forwarding = onTouchForwarded(event) || !onForwardingStopped();
+        } else {
+            forwarding = onTouchObserved(event) && onForwardingStarted();
+
+            if (forwarding) {
+                // Make sure we cancel any ongoing source event stream.
+                final long now = SystemClock.uptimeMillis();
+                final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL,
+                        0.0f, 0.0f, 0);
+                mSrcIcon.onTouchEvent(e);
+                e.recycle();
+            }
+        }
+
+        mForwarding = forwarding;
+        return forwarding || wasForwarding;
+    }
+
+    @Override
+    public void onViewAttachedToWindow(View v) {
+    }
+
+    @Override
+    public void onViewDetachedFromWindow(View v) {
+        mForwarding = false;
+        mActivePointerId = MotionEvent.INVALID_POINTER_ID;
+
+        if (mDisallowIntercept != null) {
+            mSrcIcon.removeCallbacks(mDisallowIntercept);
+        }
+    }
+
+    /**
+     * Called when forwarding would like to start.
+     * <p>
+     * This is when we populate the shortcuts container and add it to the DragLayer.
+     *
+     * @return true to start forwarding, false otherwise
+     */
+    protected boolean onForwardingStarted() {
+        List<String> ids = mLauncher.getShortcutIdsForItem((ItemInfo) mSrcIcon.getTag());
+        if (!ids.isEmpty()) {
+            // There are shortcuts associated with the app, so defer its drag.
+            LayoutInflater layoutInflater = (LayoutInflater) mLauncher.getSystemService
+                    (Context.LAYOUT_INFLATER_SERVICE);
+            final DeepShortcutsContainer deepShortcutsContainer = (DeepShortcutsContainer)
+                    layoutInflater.inflate(R.layout.deep_shortcuts_container, mDragLayer, false);
+            deepShortcutsContainer.setVisibility(View.INVISIBLE);
+            mDragLayer.addView(deepShortcutsContainer);
+            deepShortcutsContainer.populateAndShow(mSrcIcon, ids);
+            mSrcIcon.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
+                    HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Called when forwarding would like to stop.
+     *
+     * @return true to stop forwarding, false otherwise
+     */
+    protected boolean onForwardingStopped() {
+        return true;
+    }
+
+    /**
+     * Observes motion events and determines when to start forwarding.
+     *
+     * @param srcEvent motion event in source view coordinates
+     * @return true to start forwarding motion events, false otherwise
+     */
+    private boolean onTouchObserved(MotionEvent srcEvent) {
+        final View src = mSrcIcon;
+        if (!src.isEnabled()) {
+            return false;
+        }
+
+        final int actionMasked = srcEvent.getActionMasked();
+        switch (actionMasked) {
+            case MotionEvent.ACTION_DOWN:
+                mActivePointerId = srcEvent.getPointerId(0);
+
+                if (mDisallowIntercept == null) {
+                    mDisallowIntercept = new DisallowIntercept();
+                }
+                src.postDelayed(mDisallowIntercept, mTapTimeout);
+
+                if (mTriggerLongPress == null) {
+                    mTriggerLongPress = new TriggerLongPress();
+                }
+                src.postDelayed(mTriggerLongPress, mLongPressTimeout);
+                break;
+            case MotionEvent.ACTION_MOVE:
+                final int activePointerIndex = srcEvent.findPointerIndex(mActivePointerId);
+                if (activePointerIndex >= 0) {
+                    final float x = srcEvent.getX(activePointerIndex);
+                    final float y = srcEvent.getY(activePointerIndex);
+
+                    // Has the pointer moved outside of the view?
+                    if (!Utilities.pointInView(src, x, y, mScaledTouchSlop)) {
+                        clearCallbacks();
+
+                        return false;
+                    }
+                }
+                break;
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                clearCallbacks();
+                break;
+        }
+
+        return false;
+    }
+
+    private void clearCallbacks() {
+        if (mTriggerLongPress != null) {
+            mSrcIcon.removeCallbacks(mTriggerLongPress);
+        }
+
+        if (mDisallowIntercept != null) {
+            mSrcIcon.removeCallbacks(mDisallowIntercept);
+        }
+    }
+
+    private void onLongPress() {
+        clearCallbacks();
+
+        final View src = mSrcIcon;
+        if (!src.isEnabled() || mLauncher.getShortcutIdsForItem((ItemInfo) src.getTag()).isEmpty()) {
+            // Ignore long-press if the view is disabled or doesn't have shortcuts.
+            return;
+        }
+
+        if (!onForwardingStarted()) {
+            return;
+        }
+
+        // Don't let the parent intercept our events.
+        src.getParent().requestDisallowInterceptTouchEvent(true);
+
+        // Make sure we cancel any ongoing source event stream.
+        final long now = SystemClock.uptimeMillis();
+        final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
+        src.onTouchEvent(e);
+        e.recycle();
+
+        mForwarding = true;
+    }
+
+    /**
+     * Handles forwarded motion events and determines when to stop
+     * forwarding.
+     *
+     * @param srcEvent motion event in source view coordinates
+     * @return true to continue forwarding motion events, false to cancel
+     */
+    private boolean onTouchForwarded(MotionEvent srcEvent) {
+        final View src = mSrcIcon;
+
+        final DeepShortcutsContainer dst = (DeepShortcutsContainer)
+                mDragLayer.findViewById(R.id.deep_shortcuts_container);
+        if (dst == null) {
+            return false;
+        }
+
+        // Convert event to destination-local coordinates.
+        final MotionEvent dstEvent = MotionEvent.obtainNoHistory(srcEvent);
+        Utilities.translateEventCoordinates(src, dst, dstEvent);
+
+        // Convert touch down event to destination-local coordinates.
+        // TODO: only create this once, or just store the x and y.
+        final MotionEvent touchDownEvent = MotionEvent.obtainNoHistory(mTouchDownEvent);
+        Utilities.translateEventCoordinates(src, dst, touchDownEvent);
+
+        // Forward converted event to destination view, then recycle it.
+        // TODO: don't create objects in onForwardedEvent.
+        final boolean handled = dst.onForwardedEvent(dstEvent, mActivePointerId, touchDownEvent);
+        dstEvent.recycle();
+        touchDownEvent.recycle();
+
+        // Always cancel forwarding when the touch stream ends.
+        final int action = srcEvent.getActionMasked();
+        final boolean keepForwarding = action != MotionEvent.ACTION_UP
+                && action != MotionEvent.ACTION_CANCEL;
+
+        return handled && keepForwarding;
+    }
+
+    private class DisallowIntercept implements Runnable {
+        @Override
+        public void run() {
+            final ViewParent parent = mSrcIcon.getParent();
+            parent.requestDisallowInterceptTouchEvent(true);
+        }
+    }
+
+    private class TriggerLongPress implements Runnable {
+        @Override
+        public void run() {
+            onLongPress();
+        }
+    }
+}