Merge "Fade QSB with the workspace as the overlay enters" into ub-launcher3-calgary
diff --git a/res/drawable/deep_shortcuts_drag_handle.xml b/res/drawable/deep_shortcuts_drag_handle.xml
index d5fca2e..99d2b07 100644
--- a/res/drawable/deep_shortcuts_drag_handle.xml
+++ b/res/drawable/deep_shortcuts_drag_handle.xml
@@ -15,12 +15,12 @@
 -->
 
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
+        android:width="@dimen/deep_shortcut_drag_handle_size"
+        android:height="@dimen/deep_shortcut_drag_handle_size"
         android:viewportWidth="24.0"
         android:viewportHeight="24.0">
 
     <path
         android:pathData="M20 9H4v2h16V9zM4 15h16v-2H4v2z"
-        android:fillColor="#757575"/>
+        android:fillColor="#4D000000"/>
 </vector>
\ No newline at end of file
diff --git a/res/values/config.xml b/res/values/config.xml
index bdedff0..9438042 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -26,6 +26,7 @@
     <!-- Fade/zoom in/out duration & scale in a Launcher overlay transition.
          Note: This should be less than the config_overlayTransitionTime as they happen together. -->
     <integer name="config_overlayRevealTime">220</integer>
+    <integer name="config_overlaySlideRevealTime">320</integer>
     <integer name="config_overlayTransitionTime">300</integer>
     <integer name="config_overlayItemsAlphaStagger">60</integer>
 
@@ -101,4 +102,5 @@
     <item type="id" name="action_move_screen_backwards" />
     <item type="id" name="action_move_screen_forwards" />
     <item type="id" name="action_resize" />
+    <item type="id" name="action_deep_shortcuts" />
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 6897269..7436478 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -155,7 +155,7 @@
 
 <!-- Deep shortcuts -->
     <dimen name="deep_shortcuts_elevation">9dp</dimen>
-    <dimen name="bg_pill_width">180dp</dimen>
+    <dimen name="bg_pill_width">208dp</dimen>
     <dimen name="bg_pill_height">48dp</dimen>
     <dimen name="bg_pill_radius">24dp</dimen>
     <dimen name="deep_shortcuts_spacing">4dp</dimen>
@@ -164,7 +164,18 @@
          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>
-    <dimen name="deep_shortcuts_arrow_horizontal_offset">19dp</dimen>
+    <dimen name="deep_shortcut_padding_start">6dp</dimen>
+    <dimen name="deep_shortcut_padding_end">16dp</dimen>
+    <dimen name="deep_shortcut_drawable_padding">8dp</dimen>
     <dimen name="deep_shortcut_anim_translation_y">5dp</dimen>
+    <dimen name="deep_shortcut_drag_handle_size">16dp</dimen>
+    <dimen name="deep_shortcuts_arrow_width">10dp</dimen>
+    <dimen name="deep_shortcuts_arrow_height">8dp</dimen>
+    <dimen name="deep_shortcuts_arrow_vertical_offset">-2dp</dimen>
+    <!-- deep_shortcut_padding_start + deep_shortcut_icon_size / 2 - deep_shortcuts_arrow_width / 2-->
+    <!-- Note that this works for right-aligned shortcuts, too, because
+         deep_shortcut_padding_end + deep_shortcut_drag_handle_size / 2 - deep_shortcuts_arrow_width / 2
+         also happens to equal 19dp-->
+    <dimen name="deep_shortcuts_arrow_horizontal_offset">19dp</dimen>
 
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 7bd9ff7..9f30752 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -249,4 +249,7 @@
     <!-- Accessibility confirmation for widget resize. -->
     <string name="widget_resized">Widget resized to width <xliff:g id="number" example="2">%1$s</xliff:g> height <xliff:g id="number" example="1">%2$s</xliff:g></string>
 
+    <!-- Accessibility action to show quick actions menu for an icon. [CHAR_LIMIT=30] -->
+    <string name="action_deep_shortcut">Shortcuts</string>
+
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 627c433..a647cf2 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -80,9 +80,9 @@
     <style name="Icon.DeepShortcut">
         <item name="android:gravity">start|center_vertical</item>
         <item name="android:elevation">@dimen/deep_shortcuts_elevation</item>
-        <item name="android:paddingLeft">7dp</item>
-        <item name="android:paddingRight">12dp</item>
-        <item name="android:drawablePadding">9dp</item>
+        <item name="android:paddingStart">@dimen/deep_shortcut_padding_start</item>
+        <item name="android:paddingEnd">@dimen/deep_shortcut_padding_end</item>
+        <item name="android:drawablePadding">@dimen/deep_shortcut_drawable_padding</item>
         <item name="android:textColor">@color/quantum_panel_text_color</item>
         <item name="android:shadowRadius">0</item>
         <item name="customShadows">false</item>
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index c0c6673..33b3ad3 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -56,11 +56,12 @@
 
     private static SparseArray<Theme> sPreloaderThemes = new SparseArray<Theme>(2);
 
-    private static final float SHADOW_LARGE_RADIUS = 4.0f;
-    private static final float SHADOW_SMALL_RADIUS = 1.75f;
-    private static final float SHADOW_Y_OFFSET = 2.0f;
-    private static final int SHADOW_LARGE_COLOUR = 0xDD000000;
-    private static final int SHADOW_SMALL_COLOUR = 0xCC000000;
+    // Dimensions in DP
+    private static final float AMBIENT_SHADOW_RADIUS = 2.5f;
+    private static final float KEY_SHADOW_RADIUS = 1f;
+    private static final float KEY_SHADOW_OFFSET = 0.5f;
+    private static final int AMBIENT_SHADOW_COLOR = 0x33000000;
+    private static final int KEY_SHADOW_COLOR = 0x66000000;
 
     private static final int DISPLAY_WORKSPACE = 0;
     private static final int DISPLAY_ALL_APPS = 1;
@@ -136,6 +137,10 @@
             // Draw the background itself as the parent is drawn twice.
             mBackground = getBackground();
             setBackground(null);
+
+            // Set shadow layer as the larger shadow to that the textView does not clip the shadow.
+            float density = getResources().getDisplayMetrics().density;
+            setShadowLayer(density * AMBIENT_SHADOW_RADIUS, 0, 0, AMBIENT_SHADOW_COLOR);
         } else {
             mBackground = null;
         }
@@ -144,10 +149,6 @@
         mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
 
         mOutlineHelper = HolographicOutlineHelper.obtain(getContext());
-        if (mCustomShadowsEnabled) {
-            setShadowLayer(SHADOW_LARGE_RADIUS, 0.0f, SHADOW_Y_OFFSET, SHADOW_LARGE_COLOUR);
-        }
-
         setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
     }
 
@@ -408,13 +409,15 @@
         }
 
         // We enhance the shadow by drawing the shadow twice
-        getPaint().setShadowLayer(SHADOW_LARGE_RADIUS, 0.0f, SHADOW_Y_OFFSET, SHADOW_LARGE_COLOUR);
+        float density = getResources().getDisplayMetrics().density;
+        getPaint().setShadowLayer(density * AMBIENT_SHADOW_RADIUS, 0, 0, AMBIENT_SHADOW_COLOR);
         super.draw(canvas);
         canvas.save(Canvas.CLIP_SAVE_FLAG);
         canvas.clipRect(getScrollX(), getScrollY() + getExtendedPaddingTop(),
                 getScrollX() + getWidth(),
                 getScrollY() + getHeight(), Region.Op.INTERSECT);
-        getPaint().setShadowLayer(SHADOW_SMALL_RADIUS, 0.0f, 0.0f, SHADOW_SMALL_COLOUR);
+        getPaint().setShadowLayer(
+                density * KEY_SHADOW_RADIUS, 0.0f, density * KEY_SHADOW_OFFSET, KEY_SHADOW_COLOR);
         super.draw(canvas);
         canvas.restore();
     }
@@ -649,6 +652,13 @@
     }
 
     /**
+     * Returns true if the view can show custom shortcuts.
+     */
+    public boolean hasDeepShortcuts() {
+        return !mLauncher.getShortcutIdsForItem((ItemInfo) getTag()).isEmpty();
+    }
+
+    /**
      * Returns the start delay when animating between certain {@link FastBitmapDrawable} states.
      */
     private static int getStartDelayForStateChange(final FastBitmapDrawable.State fromState,
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 921e90c..df87cc2 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -348,7 +348,7 @@
 
         public ShortcutInfo getShortcutInfo() {
             if (activityInfo != null) {
-                return ShortcutInfo.fromActivityInfo(activityInfo, mContext);
+                return new ShortcutInfo(activityInfo, mContext);
             } else {
                 return LauncherAppState.getInstance().getModel().infoFromShortcutIntent(mContext, data);
             }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index f8b7c74..f22219f 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -997,8 +997,8 @@
             // Don't update the predicted apps if the user is returning to launcher in the apps
             // view after launching an app, as they may be depending on the UI to be static to
             // switch to another app, otherwise, if it was
-            showAppsView(false /* animated */, false /* resetListToTop */,
-                    !launchedFromApp /* updatePredictedApps */, false /* focusSearchBar */);
+            showAppsView(false /* animated */, !launchedFromApp /* updatePredictedApps */,
+                    false /* focusSearchBar */);
         } else if (mOnResumeState == State.WIDGETS) {
             showWidgetsView(false, false);
         }
@@ -2601,8 +2601,8 @@
         if (!isAppsViewVisible()) {
             getUserEventDispatcher().logActionOnControl(LauncherLogProto.Action.TAP,
                     LauncherLogProto.ALL_APPS_BUTTON);
-            showAppsView(true /* animated */, false /* resetListToTop */,
-                    true /* updatePredictedApps */, false /* focusSearchBar */);
+            showAppsView(true /* animated */, true /* updatePredictedApps */,
+                    false /* focusSearchBar */);
         }
     }
 
@@ -2611,7 +2611,7 @@
         if (!isAppsViewVisible()) {
             getUserEventDispatcher().logActionOnControl(LauncherLogProto.Action.LONGPRESS,
                     LauncherLogProto.ALL_APPS_BUTTON);
-            showAppsView(true /* animated */, false /* resetListToTop */,
+            showAppsView(true /* animated */,
                     true /* updatePredictedApps */, true /* focusSearchBar */);
         }
     }
@@ -3296,7 +3296,7 @@
     public boolean showWorkspace(boolean animated, Runnable onCompleteRunnable) {
         boolean changed = mState != State.WORKSPACE ||
                 mWorkspace.getState() != Workspace.State.NORMAL;
-        if (changed) {
+        if (changed || mAllAppsController.isTransitioning()) {
             mWorkspace.setVisibility(View.VISIBLE);
             mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
                     Workspace.State.NORMAL, animated, onCompleteRunnable);
@@ -3355,12 +3355,9 @@
     /**
      * Shows the apps view.
      */
-    public void showAppsView(boolean animated, boolean resetListToTop, boolean updatePredictedApps,
+    public void showAppsView(boolean animated, boolean updatePredictedApps,
             boolean focusSearchBar) {
         markAppsViewShown();
-        if (resetListToTop) {
-            mAppsView.scrollToTop();
-        }
         if (updatePredictedApps) {
             tryAndUpdatePredictedApps();
         }
@@ -3393,8 +3390,10 @@
     // TODO: calling method should use the return value so that when {@code false} is returned
     // the workspace transition doesn't fall into invalid state.
     private boolean showAppsOrWidgets(State toState, boolean animated, boolean focusSearchBar) {
-        if (mState != State.WORKSPACE &&  mState != State.APPS_SPRING_LOADED &&
-                mState != State.WIDGETS_SPRING_LOADED) {
+        if (!(mState == State.WORKSPACE ||
+                mState == State.APPS_SPRING_LOADED ||
+                mState == State.WIDGETS_SPRING_LOADED ||
+                (mState == State.APPS && mAllAppsController.isTransitioning()))) {
             return false;
         }
         if (toState != State.APPS && toState != State.WIDGETS) {
@@ -3485,7 +3484,7 @@
 
     void exitSpringLoadedDragMode() {
         if (mState == State.APPS_SPRING_LOADED) {
-            showAppsView(true /* animated */, false /* resetListToTop */,
+            showAppsView(true /* animated */,
                     false /* updatePredictedApps */, false /* focusSearchBar */);
         } else if (mState == State.WIDGETS_SPRING_LOADED) {
             showWidgetsView(true, false);
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index ddd60c8..4561111 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -1957,8 +1957,7 @@
                                         }
                                     }
                                     incrementPinnedShortcutCount(key, shouldPin);
-                                    info = ShortcutInfo.fromDeepShortcutInfo(
-                                            pinnedShortcut, context);
+                                    info = new ShortcutInfo(pinnedShortcut, context);
                                 } else { // item type == ITEM_TYPE_SHORTCUT
                                     info = getShortcutInfo(c, context, titleIndex, cursorIconInfo);
 
@@ -3291,6 +3290,22 @@
         }
     }
 
+    /**
+     * Repopulates the shortcut info, possibly updating any icon already on the workspace.
+     */
+    public void updateShortcutInfo(final ShortcutInfoCompat fullDetail, final ShortcutInfo info) {
+        enqueueItemUpdatedTask(new Runnable() {
+            @Override
+            public void run() {
+                info.updateFromDeepShortcutInfo(
+                        fullDetail, LauncherAppState.getInstance().getContext());
+                ArrayList<ShortcutInfo> update = new ArrayList<ShortcutInfo>();
+                update.add(info);
+                bindUpdatedShortcuts(update, fullDetail.getUserHandle());
+            }
+        });
+    }
+
     private class ShortcutsChangedTask implements Runnable {
         private String mPackageName;
         private List<ShortcutInfoCompat> mShortcuts;
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
index 1fe0813..b0358e8 100644
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
@@ -207,7 +207,8 @@
             Log.e(TAG, "Unexpected call to startAnimationToWorkspace");
         }
 
-        if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED) {
+        if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED
+                || mAllAppsController.isTransitioning()) {
             int animType = CIRCULAR_REVEAL;
             if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
                 animType = PULLUP;
@@ -236,6 +237,8 @@
         final Resources res = mLauncher.getResources();
         final boolean material = Utilities.ATLEAST_LOLLIPOP;
         final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
+        final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime);
+
         final int itemsAlphaStagger = res.getInteger(R.integer.config_overlayItemsAlphaStagger);
 
         final View fromView = mLauncher.getWorkspace();
@@ -434,7 +437,7 @@
                       pCb.onTransitionComplete();
                   }
             });
-            mAllAppsController.animateToAllApps(animation, revealDuration, false);
+            mAllAppsController.animateToAllApps(animation, revealDurationSlide);
 
             dispatchOnLauncherTransitionPrepare(fromView, animated, false);
             dispatchOnLauncherTransitionPrepare(toView, animated, false);
@@ -679,6 +682,7 @@
         final Resources res = mLauncher.getResources();
         final boolean material = Utilities.ATLEAST_LOLLIPOP;
         final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
+        final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime);
         final int itemsAlphaStagger =
                 res.getInteger(R.integer.config_overlayItemsAlphaStagger);
 
@@ -898,9 +902,8 @@
                 @Override
                 public void onAnimationEnd(Animator animation) {
                     if (canceled) return;
-                    dispatchOnLauncherTransitionEnd(fromView, animated, false);
-                    dispatchOnLauncherTransitionEnd(toView, animated, false);
-
+                    dispatchOnLauncherTransitionEnd(fromView, animated, true);
+                    dispatchOnLauncherTransitionEnd(toView, animated, true);
                     // Run any queued runnables
                     if (onCompleteRunnable != null) {
                         onCompleteRunnable.run();
@@ -918,7 +921,7 @@
                 }
 
             });
-            mAllAppsController.animateToWorkspace(animation, revealDuration, false);
+            mAllAppsController.animateToWorkspace(animation, revealDurationSlide);
 
             // Dispatch the prepare transition signal
             dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index a9f73c2..0cc5a1b 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -162,19 +162,17 @@
         this.user = user;
     }
 
-    public ShortcutInfo(Context context, ShortcutInfo info) {
+    public ShortcutInfo(ShortcutInfo info) {
         super(info);
-        title = Utilities.trim(info.title);
+        title = info.title;
         intent = new Intent(info.intent);
-        if (info.iconResource != null) {
-            iconResource = new Intent.ShortcutIconResource();
-            iconResource.packageName = info.iconResource.packageName;
-            iconResource.resourceName = info.iconResource.resourceName;
-        }
+        iconResource = info.iconResource;
         mIcon = info.mIcon; // TODO: should make a copy here.  maybe we don't need this ctor at all
         flags = info.flags;
-        user = info.user;
         status = info.status;
+        mInstallProgress = info.mInstallProgress;
+        isDisabled = info.isDisabled;
+        usingFallbackIcon = info.usingFallbackIcon;
     }
 
     /** TODO: Remove this.  It's only called by ApplicationInfo.makeShortcut. */
@@ -186,6 +184,28 @@
         isDisabled = info.isDisabled;
     }
 
+    public ShortcutInfo(LauncherActivityInfoCompat info, Context context) {
+        user = info.getUser();
+        title = Utilities.trim(info.getLabel());
+        contentDescription = UserManagerCompat.getInstance(context)
+                .getBadgedLabelForUser(info.getLabel(), info.getUser());
+        intent = AppInfo.makeLaunchIntent(context, info, info.getUser());
+        itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+        flags = AppInfo.initFlags(info);
+    }
+
+    /**
+     * Creates a {@link ShortcutInfo} from a {@link ShortcutInfoCompat}.
+     */
+    @TargetApi(Build.VERSION_CODES.N)
+    public ShortcutInfo(ShortcutInfoCompat shortcutInfo, Context context) {
+        user = shortcutInfo.getUserHandle();
+        itemType = LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+        intent = shortcutInfo.makeIntent(context);
+        flags = 0;
+        updateFromDeepShortcutInfo(shortcutInfo, context);
+    }
+
     public void setIcon(Bitmap b) {
         mIcon = b;
     }
@@ -265,33 +285,6 @@
         return usingLowResIcon && container >= 0 && rank >= FolderIcon.NUM_ITEMS_IN_PREVIEW;
     }
 
-    public static ShortcutInfo fromActivityInfo(LauncherActivityInfoCompat info, Context context) {
-        final ShortcutInfo shortcut = new ShortcutInfo();
-        shortcut.user = info.getUser();
-        shortcut.title = Utilities.trim(info.getLabel());
-        shortcut.contentDescription = UserManagerCompat.getInstance(context)
-                .getBadgedLabelForUser(info.getLabel(), info.getUser());
-        shortcut.intent = AppInfo.makeLaunchIntent(context, info, info.getUser());
-        shortcut.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
-        shortcut.flags = AppInfo.initFlags(info);
-        return shortcut;
-    }
-
-    /**
-     * Creates a {@link ShortcutInfo} from a {@link ShortcutInfoCompat}. Pardon the overloaded name.
-     */
-    @TargetApi(Build.VERSION_CODES.N)
-    public static ShortcutInfo fromDeepShortcutInfo(ShortcutInfoCompat shortcutInfo,
-            Context context) {
-        ShortcutInfo si = new ShortcutInfo();
-        si.user = shortcutInfo.getUserHandle();
-        si.itemType = LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
-        si.intent = shortcutInfo.makeIntent(context);
-        si.flags = 0;
-        si.updateFromDeepShortcutInfo(shortcutInfo, context);
-        return si;
-    }
-
     public void updateFromDeepShortcutInfo(ShortcutInfoCompat shortcutInfo, Context context) {
         title = shortcutInfo.getShortLabel();
 
@@ -306,11 +299,14 @@
         Drawable unbadgedIcon = launcherAppState.getShortcutManager()
                 .getShortcutIconDrawable(shortcutInfo,
                         launcherAppState.getInvariantDeviceProfile().fillResIconDpi);
-        Bitmap icon = unbadgedIcon == null ? null
-                : Utilities.createBadgedIconBitmapWithShadow(unbadgedIcon, user, context);
+        Bitmap icon = unbadgedIcon == null ? null : getBadgedIcon(unbadgedIcon, context);
         setIcon(icon != null ? icon : launcherAppState.getIconCache().getDefaultIcon(user));
     }
 
+    protected Bitmap getBadgedIcon(Drawable unbadgedIcon, Context context) {
+        return Utilities.createBadgedIconBitmapWithShadow(unbadgedIcon, user, context);
+    }
+
     /** Returns the ShortcutInfo id associated with the deep shortcut. */
     public String getDeepShortcutId() {
         return itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT ?
@@ -322,4 +318,3 @@
         return isDisabled != 0;
     }
 }
-
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index ef78195..f09b7cc 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -264,18 +264,25 @@
     }
 
     /**
+     * Creates a normalized bitmap suitable for the all apps view. The bitmap is also visually
+     * normalized with other icons and has enough spacing to add shadow.
+     */
+    public static Bitmap createScaledBitmapWithoutShadow(Drawable icon, Context context) {
+        RectF iconBounds = new RectF();
+        float scale = FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ?
+                1 : IconNormalizer.getInstance().getScale(icon, iconBounds);
+        scale = Math.min(scale, ShadowGenerator.getScaleForBounds(iconBounds));
+        return createIconBitmap(icon, context, scale);
+    }
+
+    /**
      * Same as {@link #createBadgedIconBitmap} but adds a shadow before badging the icon
      */
     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
     public static Bitmap createBadgedIconBitmapWithShadow(
             Drawable icon, UserHandleCompat user, Context context) {
-        RectF iconBounds = new RectF();
-        float scale = FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ?
-                1 : IconNormalizer.getInstance().getScale(icon, iconBounds);
-        scale = Math.min(scale, ShadowGenerator.getScaleForBounds(iconBounds));
-
-        Bitmap bitmap = createIconBitmap(icon, context, scale);
-        bitmap = ShadowGenerator.getInstance().recreateIcon(bitmap);
+        Bitmap bitmap = ShadowGenerator.getInstance().recreateIcon(
+                createScaledBitmapWithoutShadow(icon, context));
         if (Utilities.ATLEAST_LOLLIPOP && user != null
                 && !UserHandleCompat.myUserHandle().equals(user)) {
             BitmapDrawable drawable = new FixedSizeBitmapDrawable(bitmap);
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 121acc6..3c057e6 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -2305,11 +2305,19 @@
     }
 
     public void beginDragShared(View child, DragSource source, boolean accessible) {
-        beginDragShared(child, new Point(), source, accessible, new DragPreviewProvider(child));
+        Object dragObject = child.getTag();
+        if (!(dragObject instanceof ItemInfo)) {
+            String msg = "Drag started with a view that has no tag set. This "
+                    + "will cause a crash (issue 11627249) down the line. "
+                    + "View: " + child + "  tag: " + child.getTag();
+            throw new IllegalStateException(msg);
+        }
+        beginDragShared(child, new Point(), source, accessible,
+                (ItemInfo) dragObject, new DragPreviewProvider(child));
     }
 
     public void beginDragShared(View child, Point relativeTouchPos, DragSource source,
-                                boolean accessible, DragPreviewProvider previewProvider) {
+            boolean accessible, ItemInfo dragObject, DragPreviewProvider previewProvider) {
         child.clearFocus();
         child.setPressed(false);
 
@@ -2366,20 +2374,12 @@
             icon.clearPressedBackground();
         }
 
-        Object dragObject = child.getTag();
-        if (!(dragObject instanceof ItemInfo)) {
-            String msg = "Drag started with a view that has no tag set. This "
-                    + "will cause a crash (issue 11627249) down the line. "
-                    + "View: " + child + "  tag: " + child.getTag();
-            throw new IllegalStateException(msg);
-        }
-
         if (child.getParent() instanceof ShortcutAndWidgetContainer) {
             mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
         }
 
         DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source,
-                (ItemInfo) dragObject, DragController.DRAG_ACTION_MOVE, dragVisualizeOffset,
+                dragObject, DragController.DRAG_ACTION_MOVE, dragVisualizeOffset,
                 dragRect, scale, accessible);
         dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
 
@@ -2588,6 +2588,7 @@
                 fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale,
                         postAnimationRunnable);
             } else {
+                fi.prepareCreate(v);
                 fi.addItem(destInfo);
                 fi.addItem(sourceInfo);
             }
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 6bf8abf..0562cf5 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -18,6 +18,7 @@
 
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.AppWidgetResizeFrame;
+import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeleteDropTarget;
 import com.android.launcher3.DragSource;
@@ -36,6 +37,8 @@
 import com.android.launcher3.UninstallDropTarget;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.dragndrop.DragController.DragListener;
+import com.android.launcher3.shortcuts.DeepShortcutTextView;
+import com.android.launcher3.shortcuts.DeepShortcutsContainer;
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
@@ -45,13 +48,14 @@
 
     private static final String TAG = "LauncherAccessibilityDelegate";
 
-    private static final int REMOVE = R.id.action_remove;
-    private static final int INFO = R.id.action_info;
-    private static final int UNINSTALL = R.id.action_uninstall;
-    private static final int ADD_TO_WORKSPACE = R.id.action_add_to_workspace;
-    private static final int MOVE = R.id.action_move;
-    private static final int MOVE_TO_WORKSPACE = R.id.action_move_to_workspace;
-    private static final int RESIZE = R.id.action_resize;
+    protected static final int REMOVE = R.id.action_remove;
+    protected static final int INFO = R.id.action_info;
+    protected static final int UNINSTALL = R.id.action_uninstall;
+    protected static final int ADD_TO_WORKSPACE = R.id.action_add_to_workspace;
+    protected static final int MOVE = R.id.action_move;
+    protected static final int MOVE_TO_WORKSPACE = R.id.action_move_to_workspace;
+    protected static final int RESIZE = R.id.action_resize;
+    protected static final int DEEP_SHORTCUTS = R.id.action_deep_shortcuts;
 
     public enum DragType {
         ICON,
@@ -65,7 +69,7 @@
         public View item;
     }
 
-    private final SparseArray<AccessibilityAction> mActions = new SparseArray<>();
+    protected final SparseArray<AccessibilityAction> mActions = new SparseArray<>();
     @Thunk final Launcher mLauncher;
 
     private DragInfo mDragInfo = null;
@@ -88,14 +92,24 @@
                 launcher.getText(R.string.action_move_to_workspace)));
         mActions.put(RESIZE, new AccessibilityAction(RESIZE,
                         launcher.getText(R.string.action_resize)));
+        mActions.put(DEEP_SHORTCUTS, new AccessibilityAction(DEEP_SHORTCUTS,
+                launcher.getText(R.string.action_deep_shortcut)));
     }
 
     @Override
     public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfo(host, info);
+        addActions(host, info);
+    }
+
+    protected void addActions(View host, AccessibilityNodeInfo info) {
         if (!(host.getTag() instanceof ItemInfo)) return;
         ItemInfo item = (ItemInfo) host.getTag();
 
+        if (host instanceof BubbleTextView && ((BubbleTextView) host).hasDeepShortcuts()) {
+            info.addAction(mActions.get(DEEP_SHORTCUTS));
+        }
+
         if (DeleteDropTarget.supportsAccessibleDrop(item)) {
             info.addAction(mActions.get(REMOVE));
         }
@@ -215,6 +229,9 @@
                     }
                 })
                 .show();
+            return true;
+        } else if (action == DEEP_SHORTCUTS) {
+            return DeepShortcutsContainer.showForIcon((BubbleTextView) host) != null;
         }
         return false;
     }
@@ -397,7 +414,7 @@
     /**
      * Find empty space on the workspace and returns the screenId.
      */
-    private long findSpaceOnWorkspace(ItemInfo info, int[] outCoordinates) {
+    protected long findSpaceOnWorkspace(ItemInfo info, int[] outCoordinates) {
         Workspace workspace = mLauncher.getWorkspace();
         ArrayList<Long> workspaceScreens = workspace.getScreenOrder();
         long screenId;
diff --git a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
new file mode 100644
index 0000000..ff70279
--- /dev/null
+++ b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
@@ -0,0 +1,73 @@
+/*
+ * 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.accessibility;
+
+import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.R;
+import com.android.launcher3.ShortcutInfo;
+
+import java.util.ArrayList;
+
+/**
+ * Extension of {@link LauncherAccessibilityDelegate} with actions specific to shortcuts in
+ * deep shortcuts menu.
+ */
+public class ShortcutMenuAccessibilityDelegate extends LauncherAccessibilityDelegate {
+
+    public ShortcutMenuAccessibilityDelegate(Launcher launcher) {
+        super(launcher);
+    }
+
+    @Override
+    protected void addActions(View host, AccessibilityNodeInfo info) {
+        info.addAction(mActions.get(ADD_TO_WORKSPACE));
+    }
+
+    @Override
+    public boolean performAction(View host, ItemInfo item, int action) {
+        if (action == ADD_TO_WORKSPACE) {
+            final ShortcutInfo info = (ShortcutInfo) item;
+            final int[] coordinates = new int[2];
+            final long screenId = findSpaceOnWorkspace(item, coordinates);
+            Runnable onComplete = new Runnable() {
+                @Override
+                public void run() {
+                    LauncherModel.addItemToDatabase(mLauncher, info,
+                            LauncherSettings.Favorites.CONTAINER_DESKTOP,
+                            screenId, coordinates[0], coordinates[1]);
+                    ArrayList<ItemInfo> itemList = new ArrayList<>();
+                    itemList.add(info);
+                    mLauncher.bindItems(itemList, 0, itemList.size(), true);
+                    mLauncher.closeShortcutsContainer();
+                    announceConfirmation(R.string.item_added_to_workspace);
+                }
+            };
+
+            if (!mLauncher.showWorkspace(true, onComplete)) {
+                onComplete.run();
+            }
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 428f784..d860189 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -348,9 +348,10 @@
         mAppsRecyclerView.preMeasureViews(mAdapter);
         mAdapter.setIconFocusListener(focusedItemDecorator.getFocusListener());
 
-        // TODO(hyunyoungs): clean up setting the content and the reveal view.
         if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
             getRevealView().setVisibility(View.VISIBLE);
+            getContentView().setVisibility(View.VISIBLE);
+            getContentView().setBackground(null);
         }
     }
 
diff --git a/src/com/android/launcher3/allapps/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/AllAppsSearchBarController.java
index e75210b..b965d74 100644
--- a/src/com/android/launcher3/allapps/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/AllAppsSearchBarController.java
@@ -140,10 +140,15 @@
      * Resets the search bar state.
      */
     public void reset() {
-        mQuery = null;
         unfocusSearchField();
         mCb.clearSearchResult();
         mInput.setText("");
+        // We need to reset this after we clear the input text
+        mQuery = null;
+        hideKeyboard();
+    }
+
+    protected void hideKeyboard() {
         mInputMethodManager.hideSoftInputFromWindow(mInput.getWindowToken(), 0);
     }
 
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 78280f7..0e9cac8 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -6,12 +6,12 @@
 import android.animation.AnimatorSet;
 import android.animation.ArgbEvaluator;
 import android.animation.ObjectAnimator;
+import android.support.v4.view.animation.FastOutSlowInInterpolator;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.AnimationUtils;
-import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 
 import com.android.launcher3.DeviceProfile;
@@ -43,7 +43,8 @@
     private static final boolean DBG = false;
 
     private final Interpolator mAccelInterpolator = new AccelerateInterpolator(2f);
-    private final Interpolator mDecelInterpolator = new DecelerateInterpolator(1f);
+    private final Interpolator mFastOutSlowInInterpolator = new FastOutSlowInInterpolator();
+    private final Interpolator mScrollInterpolator = new PagedView.ScrollInterpolator();
 
     private static final float ANIMATION_DURATION = 1200;
 
@@ -153,7 +154,7 @@
         if (mDetector.isSettlingState() && (isInDisallowRecatchBottomZone() || isInDisallowRecatchTopZone())) {
             return false;
         }
-        return mDetector.shouldIntercept();
+        return mDetector.isDraggingOrSettling();
     }
 
     private boolean shouldPossiblyIntercept(MotionEvent ev) {
@@ -222,27 +223,19 @@
                             LauncherLogProto.Action.FLING,
                             LauncherLogProto.Action.UP,
                             LauncherLogProto.HOTSEAT);
-                    mLauncher.showAppsView(true, true, false, false);
-                } else {
-                    animateToAllApps(mCurrentAnimation, mAnimationDuration, true);
                 }
+                mLauncher.showAppsView(true /* animated */,
+                        false /* updatePredictedApps */,
+                        false /* focusSearchBar */);
             } else {
                 calculateDuration(velocity, Math.abs(mShiftRange - mAppsView.getTranslationY()));
-                if (mLauncher.isAllAppsVisible()) {
-                    mLauncher.showWorkspace(true);
-                } else {
-                    animateToWorkspace(mCurrentAnimation, mAnimationDuration, true);
-                }
+                mLauncher.showWorkspace(true);
             }
             // snap to top or bottom using the release velocity
         } else {
             if (mAppsView.getTranslationY() > mShiftRange / 2) {
                 calculateDuration(velocity, Math.abs(mShiftRange - mAppsView.getTranslationY()));
-                if (mLauncher.isAllAppsVisible()) {
-                    mLauncher.showWorkspace(true);
-                } else {
-                    animateToWorkspace(mCurrentAnimation, mAnimationDuration, true);
-                }
+                mLauncher.showWorkspace(true);
             } else {
                 calculateDuration(velocity, Math.abs(mAppsView.getTranslationY()));
                 if (!mLauncher.isAllAppsVisible()) {
@@ -250,15 +243,18 @@
                             LauncherLogProto.Action.SWIPE,
                             LauncherLogProto.Action.UP,
                             LauncherLogProto.HOTSEAT);
-                    mLauncher.showAppsView(true, true, false, false);
-                } else {
-                    animateToAllApps(mCurrentAnimation, mAnimationDuration, true);
                 }
-
+                mLauncher.showAppsView(true, /* animated */
+                        false /* updatePredictedApps */,
+                        false /* focusSearchBar */);
             }
         }
     }
 
+    public boolean isTransitioning() {
+        return mDetector.isDraggingOrSettling();
+    }
+
     /**
      * @param start {@code true} if start of new drag.
      */
@@ -267,15 +263,11 @@
             // Initialize values that should not change until #onDragEnd
             mStatusBarHeight = mLauncher.getDragLayer().getInsets().top;
             mHotseat.setVisibility(View.VISIBLE);
-            mHotseat.bringToFront();
             if (!mLauncher.isAllAppsVisible()) {
                 mLauncher.tryAndUpdatePredictedApps();
                 mHotseatBackgroundColor = mHotseat.getBackgroundDrawableColor();
                 mHotseat.setBackgroundTransparent(true /* transparent */);
                 mAppsView.setVisibility(View.VISIBLE);
-                mAppsView.getContentView().setVisibility(View.VISIBLE);
-                mAppsView.getContentView().setBackground(null);
-                mAppsView.getRevealView().setVisibility(View.VISIBLE);
                 mAppsView.setRevealDrawableColor(mHotseatBackgroundColor);
             }
         }
@@ -354,7 +346,8 @@
         }
     }
 
-    public void animateToAllApps(AnimatorSet animationOut, long duration, boolean start) {
+    public void animateToAllApps(AnimatorSet animationOut, long duration) {
+        Interpolator interpolator;
         if (animationOut == null) {
             return;
         }
@@ -362,12 +355,15 @@
             preparePull(true);
             mAnimationDuration = duration;
             mShiftStart = mAppsView.getTranslationY();
+            interpolator = mFastOutSlowInInterpolator;
+        } else {
+            interpolator = mScrollInterpolator;
         }
         final float fromAllAppsTop = mAppsView.getTranslationY();
         ObjectAnimator driftAndAlpha = ObjectAnimator.ofFloat(this, "progress",
                 fromAllAppsTop / mShiftRange, 0f);
         driftAndAlpha.setDuration(mAnimationDuration);
-        driftAndAlpha.setInterpolator(new PagedView.ScrollInterpolator());
+        driftAndAlpha.setInterpolator(interpolator);
         animationOut.play(driftAndAlpha);
 
         animationOut.addListener(new AnimatorListenerAdapter() {
@@ -390,9 +386,6 @@
             }
         });
         mCurrentAnimation = animationOut;
-        if (start) {
-            mCurrentAnimation.start();
-        }
     }
 
     public void showDiscoveryBounce() {
@@ -425,21 +418,25 @@
         });
     }
 
-    public void animateToWorkspace(AnimatorSet animationOut, long duration, boolean start) {
+    public void animateToWorkspace(AnimatorSet animationOut, long duration) {
         if (animationOut == null) {
             return;
         }
+        Interpolator interpolator;
         if (mDetector.isIdleState()) {
             preparePull(true);
             mAnimationDuration = duration;
             mShiftStart = mAppsView.getTranslationY();
+            interpolator = mFastOutSlowInInterpolator;
+        } else {
+            interpolator = mScrollInterpolator;
         }
         final float fromAllAppsTop = mAppsView.getTranslationY();
 
         ObjectAnimator driftAndAlpha = ObjectAnimator.ofFloat(this, "progress",
                 fromAllAppsTop / mShiftRange, 1f);
         driftAndAlpha.setDuration(mAnimationDuration);
-        driftAndAlpha.setInterpolator(new PagedView.ScrollInterpolator());
+        driftAndAlpha.setInterpolator(interpolator);
         animationOut.play(driftAndAlpha);
 
         animationOut.addListener(new AnimatorListenerAdapter() {
@@ -462,9 +459,6 @@
             }
         });
         mCurrentAnimation = animationOut;
-        if (start) {
-            mCurrentAnimation.start();
-        }
     }
 
     public void finishPullUp() {
@@ -530,6 +524,7 @@
         mCaretAnimator.setDuration(mCaretAnimationDuration);
         mCaretAnimator.setInterpolator(mCaretInterpolator);
         mHotseat.addOnLayoutChangeListener(this);
+        mHotseat.bringToFront();
     }
 
     @Override
diff --git a/src/com/android/launcher3/allapps/VerticalPullDetector.java b/src/com/android/launcher3/allapps/VerticalPullDetector.java
index 0e8ba7f..8bb845a 100644
--- a/src/com/android/launcher3/allapps/VerticalPullDetector.java
+++ b/src/com/android/launcher3/allapps/VerticalPullDetector.java
@@ -72,7 +72,7 @@
         mState = newState;
     }
 
-    public boolean shouldIntercept() {
+    public boolean isDraggingOrSettling() {
         return mState == ScrollState.DRAGGING || mState == ScrollState.SETTLING;
     }
 
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index d08cf54..eebbfe8 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -262,14 +262,19 @@
         }
     };
 
+    public Drawable prepareCreate(final View destView) {
+        Drawable animateDrawable = getTopDrawable((TextView) destView);
+        computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
+                destView.getMeasuredWidth());
+        return animateDrawable;
+    }
+
     public void performCreateAnimation(final ShortcutInfo destInfo, final View destView,
             final ShortcutInfo srcInfo, final DragView srcView, Rect dstRect,
             float scaleRelativeToDragLayer, Runnable postAnimationRunnable) {
 
         // These correspond two the drawable and view that the icon was dropped _onto_
-        Drawable animateDrawable = getTopDrawable((TextView) destView);
-        computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
-                destView.getMeasuredWidth());
+        Drawable animateDrawable = prepareCreate(destView);
 
         mReferenceDrawable = animateDrawable;
 
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 82c79a9..3df1d24 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -197,7 +197,7 @@
      */
     public int allocateRankForNewItem(ShortcutInfo info) {
         int rank = getItemCount();
-        ArrayList<View> views = new ArrayList<View>(mFolder.getItemsInReadingOrder());
+        ArrayList<View> views = new ArrayList<>(mFolder.getItemsInReadingOrder());
         views.add(rank, null);
         arrangeChildren(views, views.size(), false);
         setCurrentPage(rank / mMaxItemsPerPage);
diff --git a/src/com/android/launcher3/graphics/TriangleShape.java b/src/com/android/launcher3/graphics/TriangleShape.java
new file mode 100644
index 0000000..cce4e3c
--- /dev/null
+++ b/src/com/android/launcher3/graphics/TriangleShape.java
@@ -0,0 +1,56 @@
+/*
+ * 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.graphics;
+
+import android.graphics.Outline;
+import android.graphics.Path;
+import android.graphics.drawable.shapes.PathShape;
+import android.support.annotation.NonNull;
+
+/**
+ * Wrapper around {@link android.graphics.drawable.shapes.PathShape}
+ * that creates a shape with a triangular path (pointing up or down).
+ */
+public class TriangleShape extends PathShape {
+    private Path mTriangularPath;
+
+    public TriangleShape(Path path, float stdWidth, float stdHeight) {
+        super(path, stdWidth, stdHeight);
+        mTriangularPath = path;
+    }
+
+    public static TriangleShape create(float width, float height, boolean isPointingUp) {
+        Path triangularPath = new Path();
+        if (isPointingUp) {
+            triangularPath.moveTo(0, height);
+            triangularPath.lineTo(width, height);
+            triangularPath.lineTo(width / 2, 0);
+            triangularPath.close();
+        } else {
+            triangularPath.moveTo(0, 0);
+            triangularPath.lineTo(width / 2, height);
+            triangularPath.lineTo(width, 0);
+            triangularPath.close();
+        }
+        return new TriangleShape(triangularPath, width, height);
+    }
+
+    @Override
+    public void getOutline(@NonNull Outline outline) {
+        outline.setConvexPath(mTriangularPath);
+    }
+}
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorLineCaret.java b/src/com/android/launcher3/pageindicators/PageIndicatorLineCaret.java
index 0b94791..0d771ad 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorLineCaret.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorLineCaret.java
@@ -9,14 +9,12 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
-import android.graphics.Rect;
 import android.os.Handler;
 import android.os.Looper;
 import android.support.v4.graphics.ColorUtils;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Property;
-import android.view.TouchDelegate;
 import android.view.View;
 import android.view.ViewConfiguration;
 
@@ -24,6 +22,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.dynamicui.ExtractedColors;
+import com.android.launcher3.util.TransformingTouchDelegate;
 
 /**
  * A PageIndicator that briefly shows a fraction of a line when moving between pages.
@@ -61,7 +60,7 @@
     private Paint mLinePaint;
     private Launcher mLauncher;
     private final int mLineHeight;
-    private final Rect mTouchHitRect = new Rect();
+    private final TransformingTouchDelegate mTouchDelegate;
     private final int mTouchExtensionHeight;
     private final int mCaretSizePx;
     private final int mCaretWorkspaceOffsetPx;
@@ -142,6 +141,13 @@
         mLineHeight = res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_line_height);
         mTouchExtensionHeight = res.getDimensionPixelSize(
                 R.dimen.dynamic_grid_page_indicator_extra_touch_height);
+        mTouchDelegate = new TransformingTouchDelegate(this);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mLauncher.getDragLayer().setTouchDelegate(mTouchDelegate);
     }
 
     @Override
@@ -157,9 +163,8 @@
         View parent = mLauncher.getDragLayer();
         sTempCoords[0] = sTempCoords[1] = 0;
         Utilities.getDescendantCoordRelativeToAncestor(this, parent, sTempCoords, true);
-        mTouchHitRect.set(sTempCoords[0], sTempCoords[1], sTempCoords[0] + this.getWidth(),
+        mTouchDelegate.setBounds(sTempCoords[0], sTempCoords[1], sTempCoords[0] + this.getWidth(),
                 sTempCoords[1] + getHeight() + mTouchExtensionHeight);
-        parent.setTouchDelegate(new TouchDelegate(mTouchHitRect, this));
     }
 
     @Override
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutView.java b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
index f9dd336..7cb2d43 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
@@ -23,7 +23,6 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Rect;
-import android.support.annotation.IntDef;
 import android.util.AttributeSet;
 import android.view.animation.DecelerateInterpolator;
 import android.widget.FrameLayout;
@@ -40,26 +39,8 @@
  */
 public class DeepShortcutView extends FrameLayout {
 
-    private static final float HOVER_SCALE = 1.05f;
-
-    // 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 mRadius;
     private Rect mPillRect;
-    private int mTop;
-    private boolean mIsHoveringOver = false;
-    private LauncherViewPropertyAnimator mHoverAnimator;
 
     private BubbleTextView mBubbleText;
 
@@ -74,10 +55,8 @@
     public DeepShortcutView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
 
-        mSpacing = getResources().getDimensionPixelSize(R.dimen.deep_shortcuts_spacing);
         mRadius = getResources().getDimensionPixelSize(R.dimen.bg_pill_radius);
         mPillRect = new Rect();
-        mHoverAnimator = new LauncherViewPropertyAnimator(this);
     }
 
     @Override
@@ -110,11 +89,8 @@
 
     /**
      * Creates an animator to play when the shortcut container is being opened.
-     *
-     * @param animationIndex The index at which this animation will be started
-     *                       relative to other DeepShortcutView open animations.
      */
-    public Animator createOpenAnimation(int animationIndex, boolean isContainerAboveIcon) {
+    public Animator createOpenAnimation(long animationDelay, boolean isContainerAboveIcon) {
         final Resources res = getResources();
         setVisibility(INVISIBLE);
 
@@ -140,59 +116,9 @@
                 .scaleX(1).scaleY(1);
 
         openAnimation.playTogether(reveal, translationY, scale);
-        openAnimation.setStartDelay(animationIndex * res.getInteger(
-                R.integer.config_deepShortcutOpenStagger));
+        openAnimation.setStartDelay(animationDelay);
         openAnimation.setDuration(res.getInteger(R.integer.config_deepShortcutOpenDuration));
         openAnimation.setInterpolator(new DecelerateInterpolator());
         return openAnimation;
     }
-
-    /**
-     * 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.
-     */
-    private void animateHoverState() {
-        if (mHoverAnimator.isRunning()) {
-            return;
-        }
-        float scale = mIsHoveringOver ? HOVER_SCALE : 1f;
-        float translateY = (HOVER_SCALE - 1f) * getHeight() * mTranslationDirection;
-        mHoverAnimator.scaleX(scale).scaleY(scale).translationY(translateY)
-                .setDuration(getResources().getInteger(R.integer.config_deepShortcutHoverDuration))
-                .start();
-    }
-
-    public boolean isHoveringOver() {
-        return mIsHoveringOver;
-    }
 }
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
index c9eee81..a693f15 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
@@ -17,19 +17,24 @@
 package com.android.launcher3.shortcuts;
 
 import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.annotation.TargetApi;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.ShapeDrawable;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.HapticFeedbackConstants;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
@@ -44,15 +49,18 @@
 import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherViewPropertyAnimator;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
+import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.graphics.ScaledPreviewProvider;
+import com.android.launcher3.graphics.TriangleShape;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
@@ -70,10 +78,12 @@
         UserEventDispatcher.LaunchSourceProvider {
     private static final String TAG = "ShortcutsContainer";
 
-    private Launcher mLauncher;
-    private DeepShortcutManager mDeepShortcutsManager;
+    private final Launcher mLauncher;
+    private final DeepShortcutManager mDeepShortcutsManager;
     private final int mDragDeadzone;
     private final int mStartDragThreshold;
+    private final ShortcutMenuAccessibilityDelegate mAccessibilityDelegate;
+
     private BubbleTextView mDeferredDragIcon;
     private int mActivePointerId;
     private int[] mTouchDown = null;
@@ -85,7 +95,11 @@
     private Point mIconLastTouchPos = new Point();
     private boolean mIsLeftAligned;
     private boolean mIsAboveIcon;
-    private boolean mIsAnimatingOpen;
+    private View mArrow;
+    private boolean mSrcIconDragStarted;
+    private LauncherViewPropertyAnimator mArrowHoverAnimator;
+    private boolean mIsRtl;
+    private int mArrowHorizontalOffset;
 
     /**
      * Sorts shortcuts in rank order, with manifest shortcuts coming before dynamic shortcuts.
@@ -115,6 +129,8 @@
         mDragDeadzone = ViewConfiguration.get(context).getScaledTouchSlop();
         mStartDragThreshold = getResources().getDimensionPixelSize(
                 R.dimen.deep_shortcuts_start_drag_threshold);
+        mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(mLauncher);
+        mIsRtl = Utilities.isRtl(getResources());
     }
 
     public DeepShortcutsContainer(Context context, AttributeSet attrs) {
@@ -128,17 +144,32 @@
     public void populateAndShow(final BubbleTextView originalIcon, final List<String> ids) {
         // Add dummy views first, and populate with real shortcut info when ready.
         final int spacing = getResources().getDimensionPixelSize(R.dimen.deep_shortcuts_spacing);
-        final LayoutInflater inflator = mLauncher.getLayoutInflater();
+        final LayoutInflater inflater = mLauncher.getLayoutInflater();
         for (int i = 0; i < ids.size(); i++) {
-            final View shortcut = inflator.inflate(R.layout.deep_shortcut, this, false);
+            final DeepShortcutView shortcut =
+                    (DeepShortcutView) inflater.inflate(R.layout.deep_shortcut, this, false);
             if (i < ids.size() - 1) {
                 ((LayoutParams) shortcut.getLayoutParams()).bottomMargin = spacing;
             }
+            shortcut.getBubbleText().setAccessibilityDelegate(mAccessibilityDelegate);
             addView(shortcut);
         }
 
         measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
-        animateOpen(originalIcon);
+        orientAboutIcon(originalIcon);
+
+        // Add the arrow.
+        final Resources resources = getResources();
+        final int arrowWidth = resources.getDimensionPixelSize(R.dimen.deep_shortcuts_arrow_width);
+        final int arrowHeight = resources.getDimensionPixelSize(R.dimen.deep_shortcuts_arrow_height);
+        mArrowHorizontalOffset = resources.getDimensionPixelSize(
+                R.dimen.deep_shortcuts_arrow_horizontal_offset);
+        final int arrowVerticalOffset = resources.getDimensionPixelSize(
+                R.dimen.deep_shortcuts_arrow_vertical_offset);
+        mArrow = addArrowView(mArrowHorizontalOffset, arrowVerticalOffset, arrowWidth, arrowHeight);
+        mArrowHoverAnimator = new LauncherViewPropertyAnimator(mArrow);
+
+        animateOpen();
 
         deferDrag(originalIcon);
 
@@ -159,8 +190,8 @@
                 Collections.sort(shortcuts, shortcutsComparator);
                 for (int i = 0; i < shortcuts.size(); i++) {
                     final ShortcutInfoCompat shortcut = shortcuts.get(i);
-                    final ShortcutInfo launcherShortcutInfo = ShortcutInfo
-                            .fromDeepShortcutInfo(shortcut, mLauncher);
+                    final ShortcutInfo launcherShortcutInfo =
+                            new UnbadgedShortcutInfo(shortcut, mLauncher);
                     CharSequence shortLabel = shortcut.getShortLabel();
                     CharSequence longLabel = shortcut.getLongLabel();
                     uiHandler.post(new UpdateShortcutChild(i, launcherShortcutInfo,
@@ -203,34 +234,44 @@
     }
 
     private DeepShortcutView getShortcutAt(int index) {
+        if (!mIsAboveIcon) {
+            // Opening down, so arrow is the first view.
+            index++;
+        }
         return (DeepShortcutView) getChildAt(index);
     }
 
-    private void animateOpen(BubbleTextView originalIcon) {
-        orientAboutIcon(originalIcon);
+    private int getShortcutCount() {
+        // All children except the arrow are shortcuts.
+        return getChildCount() - 1;
+    }
 
+    private void animateOpen() {
         setVisibility(View.VISIBLE);
 
         final AnimatorSet shortcutAnims = LauncherAnimUtils.createAnimatorSet();
-        final int numShortcuts = getChildCount();
-        final int arrowOffset = getResources().getDimensionPixelSize(
-                R.dimen.deep_shortcuts_arrow_horizontal_offset);
-        final int pivotX = mIsLeftAligned ? arrowOffset : getMeasuredWidth() - arrowOffset;
+        final int shortcutCount = getShortcutCount();
+        final int pivotX = mIsLeftAligned ? mArrowHorizontalOffset
+                : getMeasuredWidth() - mArrowHorizontalOffset;
         final int pivotY = getShortcutAt(0).getMeasuredHeight() / 2;
-        for (int i = 0; i < numShortcuts; i++) {
+        for (int i = 0; i < shortcutCount; i++) {
             DeepShortcutView deepShortcutView = getShortcutAt(i);
             deepShortcutView.setPivotX(pivotX);
             deepShortcutView.setPivotY(pivotY);
-            int animationIndex = mIsAboveIcon ? numShortcuts - i - 1 : i;
-            shortcutAnims.play(deepShortcutView.createOpenAnimation(animationIndex, mIsAboveIcon));
+            int animationIndex = mIsAboveIcon ? shortcutCount - i - 1 : i;
+            long animationDelay = animationIndex * getResources().getInteger(
+                    R.integer.config_deepShortcutOpenStagger);
+            shortcutAnims.play(deepShortcutView.createOpenAnimation(animationDelay, mIsAboveIcon));
         }
-        shortcutAnims.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mIsAnimatingOpen = false;
-            }
-        });
-        mIsAnimatingOpen = true;
+        mArrow.setScaleX(0);
+        mArrow.setScaleY(0);
+        final long shortcutAnimDuration = shortcutAnims.getChildAnimations().get(0).getDuration();
+        final long arrowScaleDelay = shortcutAnimDuration / 6;
+        final long arrowScaleDuration = shortcutAnimDuration - arrowScaleDelay;
+        Animator arrowScale = new LauncherViewPropertyAnimator(mArrow).scaleX(1).scaleY(1);
+        arrowScale.setStartDelay(arrowScaleDelay);
+        arrowScale.setDuration(arrowScaleDuration);
+        shortcutAnims.play(arrowScale);
         shortcutAnims.start();
     }
 
@@ -245,8 +286,6 @@
      *
      * So we always align left if there is enough horizontal space
      * and align above if there is enough vertical space.
-     *
-     * TODO: draw arrow based on orientation.
      */
     private void orientAboutIcon(BubbleTextView icon) {
         int width = getMeasuredWidth();
@@ -257,20 +296,40 @@
         Rect insets = dragLayer.getInsets();
 
         // Align left (right in RTL) if there is room.
-        boolean isRtl = Utilities.isRtl(getResources());
         int leftAlignedX = mTempRect.left + icon.getPaddingLeft();
         int rightAlignedX = mTempRect.right - width - icon.getPaddingRight();
         int x = leftAlignedX;
         boolean canBeLeftAligned = leftAlignedX + width < dragLayer.getRight() - insets.right;
         boolean canBeRightAligned = rightAlignedX > dragLayer.getLeft() + insets.left;
-        if (!canBeLeftAligned || (isRtl && canBeRightAligned)) {
+        if (!canBeLeftAligned || (mIsRtl && canBeRightAligned)) {
             x = rightAlignedX;
         }
         mIsLeftAligned = x == leftAlignedX;
-        if (isRtl) {
+        if (mIsRtl) {
             x -= dragLayer.getWidth() - width;
         }
 
+        // Offset x so that the arrow and shortcut icons are center-aligned with the original icon.
+        int iconWidth = icon.getWidth() - icon.getTotalPaddingLeft() - icon.getTotalPaddingRight();
+        iconWidth *= icon.getScaleX();
+        Resources resources = getResources();
+        int xOffset;
+        if (isAlignedWithStart()) {
+            // Aligning with the shortcut icon.
+            int shortcutIconWidth = resources.getDimensionPixelSize(R.dimen.deep_shortcut_icon_size);
+            int shortcutPaddingStart = resources.getDimensionPixelSize(
+                    R.dimen.deep_shortcut_padding_start);
+            xOffset = iconWidth / 2 - shortcutIconWidth / 2 - shortcutPaddingStart;
+        } else {
+            // Aligning with the drag handle.
+            int shortcutDragHandleWidth = resources.getDimensionPixelSize(
+                    R.dimen.deep_shortcut_drag_handle_size);
+            int shortcutPaddingEnd = resources.getDimensionPixelSize(
+                    R.dimen.deep_shortcut_padding_end);
+            xOffset = iconWidth / 2 - shortcutDragHandleWidth / 2 - shortcutPaddingEnd;
+        }
+        x += mIsLeftAligned ? xOffset : -xOffset;
+
         // Open above icon if there is room.
         int y = mTempRect.top - height;
         mIsAboveIcon = mTempRect.top - height > dragLayer.getTop() + insets.top;
@@ -285,6 +344,40 @@
         setY(y);
     }
 
+    private boolean isAlignedWithStart() {
+        return mIsLeftAligned && !mIsRtl || !mIsLeftAligned && mIsRtl;
+    }
+
+    /**
+     * Adds an arrow view pointing at the original icon.
+     * @param horizontalOffset the horizontal offset of the arrow, so that it
+     *                              points at the center of the original icon
+     */
+    private View addArrowView(int horizontalOffset, int verticalOffset, int width, int height) {
+        LinearLayout.LayoutParams layoutParams = new LayoutParams(width, height);
+        if (mIsLeftAligned) {
+            layoutParams.gravity = Gravity.LEFT;
+            layoutParams.leftMargin = horizontalOffset;
+        } else {
+            layoutParams.gravity = Gravity.RIGHT;
+            layoutParams.rightMargin = horizontalOffset;
+        }
+        if (mIsAboveIcon) {
+            layoutParams.topMargin = verticalOffset;
+        } else {
+            layoutParams.bottomMargin = verticalOffset;
+        }
+
+        View arrowView = new View(getContext());
+        ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
+                width, height, !mIsAboveIcon));
+        arrowDrawable.getPaint().setColor(Color.WHITE);
+        arrowView.setBackground(arrowDrawable);
+        arrowView.setElevation(getElevation());
+        addView(arrowView, mIsAboveIcon ? getChildCount() : 0, layoutParams);
+        return arrowView;
+    }
+
     private void deferDrag(BubbleTextView originalIcon) {
         mDeferredDragIcon = originalIcon;
         showDragView(originalIcon);
@@ -339,7 +432,7 @@
         Utilities.translateEventCoordinates(this, mLauncher.getDragLayer(), ev);
         final int dragLayerX = (int) ev.getX();
         final int dragLayerY = (int) ev.getY();
-        int childCount = getChildCount();
+        int shortcutCount = getShortcutCount();
         if (action == MotionEvent.ACTION_MOVE) {
             if (mLastX != 0 || mLastY != 0) {
                 mDistanceDragged += Math.hypot(mLastX - x, mLastY - y);
@@ -347,49 +440,27 @@
             mLastX = x;
             mLastY = y;
 
-            boolean containerContainsTouch = x >= 0 && y >= 0 && x < getWidth() && y < getHeight();
-            if (shouldStartDeferredDrag((int) x, (int) y, containerContainsTouch)) {
-                cleanupDeferredDrag();
+            if (shouldStartDeferredDrag((int) x, (int) y)) {
+            DeepShortcutView topShortcut = getShortcutAt(0);
+            DeepShortcutView bottomShortcut = getShortcutAt(shortcutCount - 1);
+                mSrcIconDragStarted = true;
+                cleanupDeferredDrag(true);
                 mDeferredDragIcon.getParent().requestDisallowInterceptTouchEvent(false);
                 mDeferredDragIcon.getOnLongClickListener().onLongClick(mDeferredDragIcon);
                 mLauncher.getDragController().onTouchEvent(ev);
                 return true;
-            } else {
-                // Determine whether touch is over a shortcut.
-                boolean hoveringOverShortcut = false;
-                for (int i = 0; i < childCount && !mIsAnimatingOpen; i++) {
-                    DeepShortcutView shortcut = getShortcutAt(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 (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 (action == MotionEvent.ACTION_UP) {
-            cleanupDeferredDrag();
-            // Launch a shortcut if user was hovering over it.
-            for (int i = 0; i < childCount; i++) {
-                DeepShortcutView shortcut = getShortcutAt(i);
-                if (shortcut.isHoveringOver()) {
-                    shortcut.getBubbleText().performClick();
-                    break;
-                }
-            }
+            cleanupDeferredDrag(true);
         } else if (action == MotionEvent.ACTION_CANCEL) {
-            cleanupDeferredDrag();
+            // Do not change the source icon visibility if we are already dragging the source icon.
+            cleanupDeferredDrag(!mSrcIconDragStarted);
         }
         return true;
     }
@@ -399,26 +470,23 @@
      * 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.
+     * - Start the drag if the touch passes a certain distance from the original touch down.
      *
      * @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) {
-        int closestEdgeY = mIsAboveIcon ? getMeasuredHeight() : 0;
-        double distToEdge = Math.abs(mTouchDown[1] - closestEdgeY);
-        double newDistToEdge = Math.hypot(x - mTouchDown[0], y - closestEdgeY);
-        return !containerContainsTouch && (newDistToEdge - distToEdge > mStartDragThreshold);
+    private boolean shouldStartDeferredDrag(int x, int y) {
+        double distFromTouchDown = Math.hypot(x - mTouchDown[0], y - mTouchDown[1]);
+        return distFromTouchDown > mStartDragThreshold;
     }
 
-    public void cleanupDeferredDrag() {
+    public void cleanupDeferredDrag(boolean updateSrcVisibility) {
         if (mDragView != null) {
             mDragView.remove();
         }
-        mDeferredDragIcon.setVisibility(VISIBLE);
+        if (updateSrcVisibility) {
+            mDeferredDragIcon.setVisibility(VISIBLE);
+        }
     }
 
     @Override
@@ -439,8 +507,14 @@
         // Return if global dragging is not enabled
         if (!mLauncher.isDraggingEnabled()) return false;
 
+        UnbadgedShortcutInfo unbadgedInfo = (UnbadgedShortcutInfo) v.getTag();
+        ShortcutInfo badged = new ShortcutInfo(unbadgedInfo);
+        // Queue an update task on the worker thread. This ensures that the badged
+        // shortcut eventually gets its icon updated.
+        mLauncher.getModel().updateShortcutInfo(unbadgedInfo.mDetail, badged);
+
         // Long clicked on a shortcut.
-        mLauncher.getWorkspace().beginDragShared(v, mIconLastTouchPos, this, false,
+        mLauncher.getWorkspace().beginDragShared(v, mIconLastTouchPos, this, false, badged,
                 new ScaledPreviewProvider(v));
         // TODO: support dragging from within folder without having to close it
         mLauncher.closeFolder();
@@ -501,4 +575,43 @@
         // TODO: add target.rank
         targetParent.containerType = LauncherLogProto.DEEPSHORTCUTS;
     }
+
+    /**
+     * Shows the shortcuts container for {@param icon}
+     * @return the container if shown or null.
+     */
+    public static DeepShortcutsContainer showForIcon(BubbleTextView icon) {
+        Launcher launcher = Launcher.getLauncher(icon.getContext());
+        List<String> ids = launcher.getShortcutIdsForItem((ItemInfo) icon.getTag());
+        if (!ids.isEmpty()) {
+            // There are shortcuts associated with the app, so defer its drag.
+            final DeepShortcutsContainer container =
+                    (DeepShortcutsContainer) launcher.getLayoutInflater().inflate(
+                            R.layout.deep_shortcuts_container, launcher.getDragLayer(), false);
+            container.setVisibility(View.INVISIBLE);
+            launcher.getDragLayer().addView(container);
+            container.populateAndShow(icon, ids);
+            icon.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
+                    HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+            return container;
+        }
+        return null;
+    }
+
+    /**
+     * Extension of {@link ShortcutInfo} which does not badge the icons.
+     */
+    private static class UnbadgedShortcutInfo extends ShortcutInfo {
+        private final ShortcutInfoCompat mDetail;
+
+        public UnbadgedShortcutInfo(ShortcutInfoCompat shortcutInfo, Context context) {
+            super(shortcutInfo, context);
+            mDetail = shortcutInfo;
+        }
+
+        @Override
+        protected Bitmap getBadgedIcon(Drawable unbadgedIcon, Context context) {
+            return Utilities.createScaledBitmapWithoutShadow(unbadgedIcon, context);
+        }
+    }
 }
diff --git a/src/com/android/launcher3/shortcuts/ShortcutsContainerListener.java b/src/com/android/launcher3/shortcuts/ShortcutsContainerListener.java
index f3b0d04..507939a 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutsContainerListener.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutsContainerListener.java
@@ -1,24 +1,16 @@
 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.
@@ -29,18 +21,12 @@
     /** 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;
 
@@ -58,11 +44,11 @@
 
     /** If true, the gesture is not handled. The value is reset when next gesture starts. */
     private boolean mIgnoreCurrentGesture;
+    private DeepShortcutsContainer mShortcutsContainer;
 
     public ShortcutsContainerListener(BubbleTextView icon) {
         mSrcIcon = icon;
         mScaledTouchSlop = ViewConfiguration.get(icon.getContext()).getScaledTouchSlop();
-        mTapTimeout = ViewConfiguration.getTapTimeout();
 
         mLongPressTimeout = CheckLongPressHelper.DEFAULT_LONG_PRESS_TIMEOUT;
 
@@ -78,8 +64,7 @@
         if (event.getAction() == MotionEvent.ACTION_DOWN) {
             // There are no shortcuts associated with this item,
             // so return to normal touch handling.
-            mIgnoreCurrentGesture =
-                    (mLauncher.getShortcutIdsForItem((ItemInfo) v.getTag())).isEmpty();
+            mIgnoreCurrentGesture = !mSrcIcon.hasDeepShortcuts();
 
             mTouchDown[0] = (int) event.getX();
             mTouchDown[1] = (int) event.getY();
@@ -120,10 +105,6 @@
     public void onViewDetachedFromWindow(View v) {
         mForwarding = false;
         mActivePointerId = MotionEvent.INVALID_POINTER_ID;
-
-        if (mDisallowIntercept != null) {
-            mSrcIcon.removeCallbacks(mDisallowIntercept);
-        }
     }
 
     /**
@@ -134,21 +115,8 @@
      * @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;
+        mShortcutsContainer = DeepShortcutsContainer.showForIcon(mSrcIcon);
+        return mShortcutsContainer != null;
     }
 
     /**
@@ -157,6 +125,7 @@
      * @return true to stop forwarding, false otherwise
      */
     protected boolean onForwardingStopped() {
+        mShortcutsContainer = null;
         return true;
     }
 
@@ -177,11 +146,6 @@
             case MotionEvent.ACTION_DOWN:
                 mActivePointerId = srcEvent.getPointerId(0);
 
-                if (mDisallowIntercept == null) {
-                    mDisallowIntercept = new DisallowIntercept();
-                }
-                src.postDelayed(mDisallowIntercept, mTapTimeout);
-
                 if (mTriggerLongPress == null) {
                     mTriggerLongPress = new TriggerLongPress();
                 }
@@ -214,17 +178,13 @@
         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()) {
+        final BubbleTextView src = mSrcIcon;
+        if (!src.isEnabled() || !src.hasDeepShortcuts()) {
             // Ignore long-press if the view is disabled or doesn't have shortcuts.
             return;
         }
@@ -254,8 +214,7 @@
      */
     private boolean onTouchForwarded(MotionEvent srcEvent) {
         final View src = mSrcIcon;
-
-        final DeepShortcutsContainer dst = mLauncher.getOpenShortcutsContainer();
+        final DeepShortcutsContainer dst = mShortcutsContainer;
         if (dst == null) {
             return false;
         }
@@ -285,14 +244,6 @@
         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() {
diff --git a/src/com/android/launcher3/util/ManagedProfileHeuristic.java b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
index 7dbc0e7a..6661429 100644
--- a/src/com/android/launcher3/util/ManagedProfileHeuristic.java
+++ b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
@@ -111,7 +111,7 @@
             for (int i = 0; i < count; i++) {
                 LauncherActivityInstallInfo info = apps.get(i);
 
-                ShortcutInfo si = ShortcutInfo.fromActivityInfo(info.info, mContext);
+                ShortcutInfo si = new ShortcutInfo(info.info, mContext);
                 ((info.installTime <= folderCreationTime) ? workFolderApps : homescreenApps).add(si);
             }
 
diff --git a/src/com/android/launcher3/util/TransformingTouchDelegate.java b/src/com/android/launcher3/util/TransformingTouchDelegate.java
new file mode 100644
index 0000000..da1de5e
--- /dev/null
+++ b/src/com/android/launcher3/util/TransformingTouchDelegate.java
@@ -0,0 +1,87 @@
+/*
+ * 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.util;
+
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.view.MotionEvent;
+import android.view.TouchDelegate;
+import android.view.View;
+
+/**
+ * This class differs from the framework {@link TouchDelegate} in that it transforms the
+ * coordinates of the motion event to the provided bounds.
+ *
+ * You can also modify the bounds post construction. Since the bounds are available during layout,
+ * this avoids new object creation during every layout.
+ */
+public class TransformingTouchDelegate extends TouchDelegate {
+    private static final Rect sTempRect = new Rect();
+
+    private final RectF mBounds;
+
+    private View mDelegateView;
+    private boolean mDelegateTargeted;
+
+    public TransformingTouchDelegate(View delegateView) {
+        super(sTempRect, delegateView);
+
+        mDelegateView = delegateView;
+        mBounds = new RectF();
+    }
+
+    public void setBounds(int left, int top, int right, int bottom) {
+        mBounds.set(left, top, right, bottom);
+    }
+
+    public void setDelegateView(View view) {
+        mDelegateView = view;
+    }
+
+    /**
+     * Will forward touch events to the delegate view if the event is within the bounds
+     * specified in the constructor.
+     *
+     * @param event The touch event to forward
+     * @return True if the event was forwarded to the delegate, false otherwise.
+     */
+    public boolean onTouchEvent(MotionEvent event) {
+        boolean sendToDelegate = false;
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                mDelegateTargeted = mBounds.contains(event.getX(), event.getY());
+                if (mDelegateTargeted) {
+                    sendToDelegate = true;
+                }
+                break;
+            case MotionEvent.ACTION_MOVE:
+                sendToDelegate = mDelegateTargeted;
+                break;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                sendToDelegate = mDelegateTargeted;
+                mDelegateTargeted = false;
+                break;
+        }
+        boolean handled = false;
+        if (sendToDelegate) {
+            event.offsetLocation(-mBounds.left, -mBounds.top);
+            handled = mDelegateView.dispatchTouchEvent(event);
+            event.offsetLocation(mBounds.left, mBounds.top);
+        }
+        return handled;
+    }
+}