resolve merge conflicts of 6e74e89 to ub-launcher3-master

Change-Id: I34e449ca3a91ee06e1983ac3a83cb7ca53567a91
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 33466a8..367cee4 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -164,7 +164,7 @@
     <dimen name="bg_pill_height">48dp</dimen>
     <dimen name="bg_pill_radius">24dp</dimen>
     <dimen name="deep_shortcuts_spacing">4dp</dimen>
-    <dimen name="deep_shortcuts_drag_view_scale">6dp</dimen>
+    <dimen name="deferred_drag_view_scale">6dp</dimen>
     <!-- an icon with shortcuts must be dragged this far before the container is removed. -->
     <dimen name="deep_shortcuts_start_drag_threshold">16dp</dimen>
     <dimen name="deep_shortcut_icon_size">36dp</dimen>
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 1618313..99c16e7 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -3109,7 +3109,17 @@
                                         longClickCellInfo.cellX, longClickCellInfo.cellY));
                 if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) {
                     // User long pressed on an item
-                    mWorkspace.startDrag(longClickCellInfo, new DragOptions());
+                    DragOptions dragOptions = new DragOptions();
+                    if (itemUnderLongClick instanceof BubbleTextView) {
+                        BubbleTextView icon = (BubbleTextView) itemUnderLongClick;
+                        if (icon.hasDeepShortcuts()) {
+                            DeepShortcutsContainer dsc = DeepShortcutsContainer.showForIcon(icon);
+                            if (dsc != null) {
+                                dragOptions.deferDragCondition = dsc.createDeferDragCondition(null);
+                            }
+                        }
+                    }
+                    mWorkspace.startDrag(longClickCellInfo, dragOptions);
                 }
             }
         }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index ee4fc91..977395f 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -73,8 +73,6 @@
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.graphics.DragPreviewProvider;
-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.ItemInfoMatcher;
@@ -1174,10 +1172,6 @@
         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);
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 8cb493a..18574aa 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -36,6 +36,7 @@
 
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.BaseContainerView;
+import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeleteDropTarget;
 import com.android.launcher3.DeviceProfile;
@@ -53,6 +54,7 @@
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.graphics.TintedDrawableSpan;
 import com.android.launcher3.keyboard.FocusedItemDecorator;
+import com.android.launcher3.shortcuts.DeepShortcutsContainer;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.ComponentKey;
 
@@ -445,13 +447,32 @@
 
         if (!mLauncher.isAppsViewVisible() ||
                 mLauncher.getWorkspace().isSwitchingState()) return false;
-        // Return if global dragging is not enabled
+        // Return if global dragging is not enabled or we are already dragging
         if (!mLauncher.isDraggingEnabled()) return false;
+        if (mLauncher.getDragController().isDragging()) return false;
 
         // Start the drag
-        mLauncher.getWorkspace().beginDragShared(v, this, new DragOptions());
-        // Enter spring loaded mode
-        mLauncher.enterSpringLoadedDragMode();
+        DragOptions dragOptions = new DragOptions();
+        if (v instanceof BubbleTextView) {
+            final BubbleTextView icon = (BubbleTextView) v;
+            if (icon.hasDeepShortcuts()) {
+                DeepShortcutsContainer dsc = DeepShortcutsContainer.showForIcon(icon);
+                if (dsc != null) {
+                    dragOptions.deferDragCondition = dsc.createDeferDragCondition(new Runnable() {
+                        @Override
+                        public void run() {
+                            icon.setVisibility(VISIBLE);
+                        }
+                    });
+                }
+            }
+        }
+        mLauncher.getWorkspace().beginDragShared(v, this, dragOptions);
+        if (FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) {
+            // Enter spring loaded mode (the new workspace does this in
+            // onDragStart(), so we don't want to do it here)
+            mLauncher.enterSpringLoadedDragMode();
+        }
 
         return false;
     }
@@ -501,7 +522,7 @@
         // target layout we were dropping on.
         if (!success) {
             boolean showOutOfSpaceMessage = false;
-            if (target instanceof Workspace) {
+            if (target instanceof Workspace && !mLauncher.getDragController().isDeferringDrag()) {
                 int currentScreen = mLauncher.getCurrentWorkspaceScreen();
                 Workspace workspace = (Workspace) target;
                 CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 39ab58b..7b6aef1 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -42,8 +42,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
-import com.android.launcher3.shortcuts.ShortcutsContainerListener;
 
 import java.util.HashMap;
 import java.util.List;
@@ -503,10 +501,6 @@
                 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;
             }
@@ -514,10 +508,6 @@
                 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/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 77a957a..c7b66e7 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -131,6 +131,8 @@
     protected final int mFlingToDeleteThresholdVelocity;
     private VelocityTracker mVelocityTracker;
 
+    private boolean mIsDragDeferred;
+
     /**
      * Interface to receive notifications when a drag starts or stops
      */
@@ -228,9 +230,14 @@
 
         mDragObject = new DropTarget.DragObject();
 
+        mIsDragDeferred = !mOptions.deferDragCondition.shouldStartDeferredDrag(0);
+
         final Resources res = mLauncher.getResources();
-        final float scaleDps = FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND ?
-                res.getDimensionPixelSize(R.dimen.dragViewScale) : 0f;
+        final float scaleDps = FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND
+                ? res.getDimensionPixelSize(R.dimen.dragViewScale)
+                : mIsDragDeferred
+                    ? res.getDimensionPixelSize(R.dimen.deferred_drag_view_scale)
+                    : 0f;
         final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
                 registrationY, initialDragViewScale, scaleDps);
 
@@ -264,8 +271,10 @@
         dragView.show(mMotionDownX, mMotionDownY);
         mDistanceSinceScroll = 0;
 
-        for (DragListener listener : new ArrayList<>(mListeners)) {
-            listener.onDragStart(mDragObject, mOptions);
+        if (!mIsDragDeferred) {
+            startDeferredDrag();
+        } else {
+            mOptions.deferDragCondition.onDeferredDragStart();
         }
 
         mLastTouch[0] = mMotionDownX;
@@ -275,8 +284,16 @@
         return dragView;
     }
 
-    public Point getMotionDown() {
-        return new Point(mMotionDownX, mMotionDownY);
+    public boolean isDeferringDrag() {
+        return mIsDragDeferred;
+    }
+
+    public void startDeferredDrag() {
+        for (DragListener listener : new ArrayList<>(mListeners)) {
+            listener.onDragStart(mDragObject, mOptions);
+        }
+        mOptions.deferDragCondition.onDragStart();
+        mIsDragDeferred = false;
     }
 
     /**
@@ -518,6 +535,11 @@
         mLastTouch[0] = x;
         mLastTouch[1] = y;
         checkScrollState(x, y);
+
+        if (mIsDragDeferred && mOptions.deferDragCondition.shouldStartDeferredDrag(
+                Math.hypot(x - mMotionDownX, y - mMotionDownY))) {
+            startDeferredDrag();
+        }
     }
 
     public float getDistanceDragged() {
@@ -715,6 +737,9 @@
         mDragObject.dragSource.onDropCompleted(
                 dropTargetAsView, mDragObject, flingVel != null, accepted);
         mLauncher.getUserEventDispatcher().logDragNDrop(mDragObject, dropTargetAsView);
+        if (mIsDragDeferred) {
+            mOptions.deferDragCondition.onDropBeforeDeferredDrag();
+        }
     }
 
     private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) {
diff --git a/src/com/android/launcher3/dragndrop/DragOptions.java b/src/com/android/launcher3/dragndrop/DragOptions.java
index 3d52a48..dbf46f3 100644
--- a/src/com/android/launcher3/dragndrop/DragOptions.java
+++ b/src/com/android/launcher3/dragndrop/DragOptions.java
@@ -28,4 +28,44 @@
 
     /** Specifies the start location for the system DnD, null when using internal DnD */
     public Point systemDndStartPoint = null;
+
+    /** Determines when a deferred drag should start. By default, drags aren't deferred at all. */
+    public DeferDragCondition deferDragCondition = new DeferDragCondition();
+
+    /**
+     * Specifies a condition that must be met before DragListener#onDragStart() is called.
+     * By default, there is no condition and onDragStart() is called immediately following
+     * DragController#startDrag().
+     *
+     * This condition can be overridden, and callbacks are provided for the following cases:
+     * - The drag starts, but onDragStart() is deferred (onDeferredDragStart()).
+     * - The drag ends before the condition is met (onDropBeforeDeferredDrag()).
+     * - The condition is met (onDragStart()).
+     */
+    public static class DeferDragCondition {
+        public boolean shouldStartDeferredDrag(double distanceDragged) {
+            return true;
+        }
+
+        /**
+         * The drag has started, but onDragStart() is deferred.
+         * This happens when shouldStartDeferredDrag() returns true.
+         */
+        public void onDeferredDragStart() {
+            // Do nothing.
+        }
+
+        /**
+         * User dropped before the deferred condition was met,
+         * i.e. before shouldStartDeferredDrag() returned true.
+         */
+        public void onDropBeforeDeferredDrag() {
+            // Do nothing
+        }
+
+        /** onDragStart() has been called, now we are in a normal drag. */
+        public void onDragStart() {
+            // Do nothing
+        }
+    }
 }
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index b64d12c..a666b56 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -51,6 +51,7 @@
 
 import com.android.launcher3.Alarm;
 import com.android.launcher3.AppInfo;
+import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DragSource;
@@ -77,6 +78,7 @@
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.pageindicators.PageIndicatorDots;
+import com.android.launcher3.shortcuts.DeepShortcutsContainer;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.CircleRevealOutlineProvider;
@@ -279,7 +281,17 @@
     public boolean onLongClick(View v) {
         // Return if global dragging is not enabled
         if (!mLauncher.isDraggingEnabled()) return true;
-        return startDrag(v, new DragOptions());
+        DragOptions dragOptions = new DragOptions();
+        if (v instanceof BubbleTextView) {
+            BubbleTextView icon = (BubbleTextView) v;
+            if (icon.hasDeepShortcuts()) {
+                DeepShortcutsContainer dsc = DeepShortcutsContainer.showForIcon(icon);
+                if (dsc != null) {
+                    dragOptions.deferDragCondition = dsc.createDeferDragCondition(null);
+                }
+            }
+        }
+        return startDrag(v, dragOptions);
     }
 
     public boolean startDrag(View v, DragOptions options) {
@@ -1297,7 +1309,9 @@
             mIsExternalDrag = false;
         } else {
             currentDragView = mCurrentDragView;
-            mContent.addViewForRank(currentDragView, si, mEmptyCellRank);
+            if (!mDragController.isDeferringDrag()) {
+                mContent.addViewForRank(currentDragView, si, mEmptyCellRank);
+            }
         }
 
         if (d.dragView.hasDrawn()) {
@@ -1318,9 +1332,11 @@
         mItemsInvalidated = true;
         rearrangeChildren();
 
-        // Temporarily suppress the listener, as we did all the work already here.
-        try (SuppressInfoChanges s = new SuppressInfoChanges()) {
-            mInfo.add(si, false);
+        if (!mDragController.isDeferringDrag()) {
+            // Temporarily suppress the listener, as we did all the work already here.
+            try (SuppressInfoChanges s = new SuppressInfoChanges()) {
+                mInfo.add(si, false);
+            }
         }
 
         // Clear the drag info, as it is no longer being dragged.
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 1171d48..c6b0671 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -44,10 +44,8 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace.ItemOperator;
 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.pageindicators.PageIndicator;
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
@@ -236,10 +234,6 @@
         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/DeepShortcutsContainer.java b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
index d825cb8..1aa1b9e 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
@@ -34,16 +34,13 @@
 import android.os.Looper;
 import android.util.AttributeSet;
 import android.view.Gravity;
-import android.view.HapticFeedbackConstants;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewConfiguration;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.DecelerateInterpolator;
 import android.widget.LinearLayout;
 
-import com.android.launcher3.AppInfo;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget;
@@ -59,7 +56,6 @@
 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;
@@ -86,18 +82,12 @@
 
     private final Launcher mLauncher;
     private final DeepShortcutManager mDeepShortcutsManager;
-    private final int mDragDeadzone;
     private final int mStartDragThreshold;
     private final ShortcutMenuAccessibilityDelegate mAccessibilityDelegate;
+    private final boolean mIsRtl;
 
     private BubbleTextView mDeferredDragIcon;
-    private int mActivePointerId;
-    private int[] 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;
@@ -107,16 +97,11 @@
     private boolean mDeferContainerRemoval;
     private boolean mIsOpen;
 
-    private boolean mSrcIconDragStarted;
-    private boolean mIsRtl;
-    private int mArrowHorizontalOffset;
-
     public DeepShortcutsContainer(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         mLauncher = Launcher.getLauncher(context);
         mDeepShortcutsManager = LauncherAppState.getInstance().getShortcutManager();
 
-        mDragDeadzone = ViewConfiguration.get(context).getScaledTouchSlop();
         mStartDragThreshold = getResources().getDimensionPixelSize(
                 R.dimen.deep_shortcuts_start_drag_threshold);
         mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(mLauncher);
@@ -135,7 +120,7 @@
         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(
+        final int arrowHorizontalOffset = resources.getDimensionPixelSize(
                 R.dimen.deep_shortcuts_arrow_horizontal_offset);
         final int arrowVerticalOffset = resources.getDimensionPixelSize(
                 R.dimen.deep_shortcuts_arrow_vertical_offset);
@@ -160,7 +145,7 @@
         orientAboutIcon(originalIcon, arrowHeight + arrowVerticalOffset);
 
         // Add the arrow.
-        mArrow = addArrowView(mArrowHorizontalOffset, arrowVerticalOffset, arrowWidth, arrowHeight);
+        mArrow = addArrowView(arrowHorizontalOffset, arrowVerticalOffset, arrowWidth, arrowHeight);
         mArrow.setPivotX(arrowWidth / 2);
         mArrow.setPivotY(mIsAboveIcon ? 0 : arrowHeight);
 
@@ -348,7 +333,6 @@
         mIsAboveIcon = y > dragLayer.getTop() + insets.top;
         if (!mIsAboveIcon) {
             y = mTempRect.top + icon.getPaddingTop() + iconHeight;
-            icon.setTextVisibility(false);
         }
 
         // Insets are added later, so subtract them now.
@@ -394,7 +378,6 @@
 
     private void deferDrag(BubbleTextView originalIcon) {
         mDeferredDragIcon = originalIcon;
-        showDragView(originalIcon);
         mLauncher.getDragController().addDragListener(this);
     }
 
@@ -402,103 +385,39 @@
         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 = LauncherIcons.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, 1f, scaleDps);
-        mLastX = mLastY = mDistanceDragged = 0;
-        mDragView.show(motionDownX, motionDownY);
-    }
-
-    public boolean onForwardedEvent(MotionEvent ev, int activePointerId, int[] touchDown) {
-        mActivePointerId = activePointerId;
-        mTouchDown = touchDown;
-        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();
-        if (action == MotionEvent.ACTION_MOVE) {
-            if (mLastX != 0 || mLastY != 0) {
-                mDistanceDragged += Math.hypot(mLastX - x, mLastY - y);
-            }
-            mLastX = x;
-            mLastY = y;
-
-            if (shouldStartDeferredDrag((int) x, (int) y)) {
-                mSrcIconDragStarted = true;
-                cleanupDeferredDrag(true);
-                mDeferredDragIcon.getParent().requestDisallowInterceptTouchEvent(false);
-                mDeferredDragIcon.getOnLongClickListener().onLongClick(mDeferredDragIcon);
-                mLauncher.getDragController().onControllerTouchEvent(ev);
-                return true;
-            } 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(true);
-            mLauncher.getUserEventDispatcher().logDeepShortcutsOpen(mDeferredDragIcon);
-        } else if (action == MotionEvent.ACTION_CANCEL) {
-            // Do not change the source icon visibility if we are already dragging the source icon.
-            cleanupDeferredDrag(!mSrcIconDragStarted);
-        }
-        return true;
-    }
-
     /**
-     * Determines whether the deferred drag should be started based on touch coordinates
-     * relative to the original icon and the shortcuts container.
+     * Determines when the deferred drag should be started.
      *
      * Current behavior:
      * - 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) {
-        double distFromTouchDown = Math.hypot(x - mTouchDown[0], y - mTouchDown[1]);
-        return distFromTouchDown > mStartDragThreshold;
-    }
+    public DragOptions.DeferDragCondition createDeferDragCondition(final Runnable onDragStart) {
+        return new DragOptions.DeferDragCondition() {
+            @Override
+            public boolean shouldStartDeferredDrag(double distanceDragged) {
+                return distanceDragged > mStartDragThreshold;
+            }
 
-    private void cleanupDeferredDrag(boolean updateSrcVisibility) {
-        if (mDragView != null) {
-            mDragView.remove();
-        }
-        if (updateSrcVisibility) {
-            mDeferredDragIcon.setVisibility(VISIBLE);
-        }
+            @Override
+            public void onDeferredDragStart() {
+                mDeferredDragIcon.setVisibility(INVISIBLE);
+            }
+
+            @Override
+            public void onDropBeforeDeferredDrag() {
+                mLauncher.getUserEventDispatcher().logDeepShortcutsOpen(mDeferredDragIcon);
+                if (!mIsAboveIcon) {
+                    mDeferredDragIcon.setTextVisibility(false);
+                }
+            }
+
+            @Override
+            public void onDragStart() {
+                if (onDragStart != null) {
+                    onDragStart.run();
+                }
+            }
+        };
     }
 
     @Override
@@ -582,9 +501,7 @@
 
     @Override
     public void onDragEnd() {
-        if (mIsOpen) {
-            animateClose();
-        } else {
+        if (!mIsOpen) {
             if (mOpenCloseAnimator != null) {
                 // Close animation is running.
                 mDeferContainerRemoval = false;
@@ -595,6 +512,7 @@
                 }
             }
         }
+        mDeferredDragIcon.setVisibility(VISIBLE);
     }
 
     @Override
@@ -702,8 +620,6 @@
         }
         mIsOpen = false;
         mDeferContainerRemoval = false;
-        // Make the original icon visible in All Apps, but not in Workspace or Folders.
-        cleanupDeferredDrag(mDeferredDragIcon.getTag() instanceof AppInfo);
         boolean isInHotseat = ((ItemInfo) mDeferredDragIcon.getTag()).container
                 == LauncherSettings.Favorites.CONTAINER_HOTSEAT;
         mDeferredDragIcon.setTextVisibility(!isInHotseat);
@@ -735,8 +651,6 @@
             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;
diff --git a/src/com/android/launcher3/shortcuts/ShortcutsContainerListener.java b/src/com/android/launcher3/shortcuts/ShortcutsContainerListener.java
deleted file mode 100644
index 31f0969..0000000
--- a/src/com/android/launcher3/shortcuts/ShortcutsContainerListener.java
+++ /dev/null
@@ -1,252 +0,0 @@
-package com.android.launcher3.shortcuts;
-
-import android.os.SystemClock;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.CheckLongPressHelper;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.dragndrop.DragLayer;
-
-/**
- * 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 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 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;
-    /** The coordinates of the touch down, relative to the shortcuts container. */
-    private final int[] mTouchDown;
-    private boolean mHasMappedTouchDownToContainerCoord;
-
-    /** 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();
-
-        mLongPressTimeout = CheckLongPressHelper.DEFAULT_LONG_PRESS_TIMEOUT;
-
-        icon.addOnAttachStateChangeListener(this);
-
-        mLauncher = Launcher.getLauncher(mSrcIcon.getContext());
-        mDragLayer = mLauncher.getDragLayer();
-        mTouchDown = new int[2];
-    }
-
-    @Override
-    public boolean onTouch(View v, MotionEvent event) {
-        if (event.getAction() == MotionEvent.ACTION_DOWN) {
-            // There are no shortcuts associated with this item,
-            // so return to normal touch handling.
-            mIgnoreCurrentGesture = !mSrcIcon.hasDeepShortcuts();
-
-            mTouchDown[0] = (int) event.getX();
-            mTouchDown[1] = (int) event.getY();
-            mDragLayer.getDescendantCoordRelativeToSelf(mSrcIcon, mTouchDown);
-            mHasMappedTouchDownToContainerCoord = false;
-        }
-
-        if (mIgnoreCurrentGesture) {
-            return false;
-        }
-
-        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;
-    }
-
-    /**
-     * 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() {
-        mShortcutsContainer = DeepShortcutsContainer.showForIcon(mSrcIcon);
-        return mShortcutsContainer != null;
-    }
-
-    /**
-     * Called when forwarding would like to stop.
-     *
-     * @return true to stop forwarding, false otherwise
-     */
-    protected boolean onForwardingStopped() {
-        mShortcutsContainer = null;
-        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 (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);
-        }
-    }
-
-    private void onLongPress() {
-        clearCallbacks();
-
-        final BubbleTextView src = mSrcIcon;
-        if (!src.isEnabled() || !src.hasDeepShortcuts()) {
-            // 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 = mShortcutsContainer;
-        if (dst == null) {
-            return false;
-        }
-        // Always cancel forwarding when the touch stream ends.
-        final int action = srcEvent.getActionMasked();
-        final boolean keepForwarding = action != MotionEvent.ACTION_UP
-                && action != MotionEvent.ACTION_CANCEL;
-        if (!dst.isLaidOut()) {
-            return keepForwarding;
-        }
-
-        // 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.
-        if (!mHasMappedTouchDownToContainerCoord) {
-            mDragLayer.mapCoordInSelfToDescendent(dst, mTouchDown);
-            mHasMappedTouchDownToContainerCoord = true;
-        }
-
-        // Forward converted event to destination view, then recycle it.
-        final boolean handled = dst.onForwardedEvent(dstEvent, mActivePointerId, mTouchDown);
-        dstEvent.recycle();
-
-        return handled && keepForwarding;
-    }
-
-    private class TriggerLongPress implements Runnable {
-        @Override
-        public void run() {
-            onLongPress();
-        }
-    }
-}