Refactoring FlingToDelete

> Moving all fling related logic to FlingToDeleteHelper from DragController
> Removing fling related methods from DragSource and DropTarget
> Moving fling animation logic from DeleteDropTarget to FlingAnimation
> Simplifying DropTargetBar to directly look for all valid drop targets.
  This makes it easier to add new DropTarget in xml.

Change-Id: I7214d2d30c907ab93c80d92d9f9be6dda2d63354
diff --git a/src/com/android/launcher3/AnotherWindowDropTarget.java b/src/com/android/launcher3/AnotherWindowDropTarget.java
index 7074f78..052e5d0 100644
--- a/src/com/android/launcher3/AnotherWindowDropTarget.java
+++ b/src/com/android/launcher3/AnotherWindowDropTarget.java
@@ -48,9 +48,6 @@
     public void onDragExit(DragObject dragObject) {}
 
     @Override
-    public void onFlingToDelete(DragObject dragObject, PointF vec) {}
-
-    @Override
     public boolean acceptDrop(DragObject dragObject) {
         return dragObject.dragInfo instanceof ShortcutInfo;
     }
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index 60a2cc3..e613b3b 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -28,7 +28,6 @@
 import android.content.res.TypedArray;
 import android.graphics.ColorMatrix;
 import android.graphics.ColorMatrixColorFilter;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
@@ -119,9 +118,6 @@
     }
 
     @Override
-    public void onFlingToDelete(DragObject d, PointF vec) { }
-
-    @Override
     public final void onDragEnter(DragObject d) {
         d.dragView.setColor(mHoverColor);
         if (Utilities.ATLEAST_LOLLIPOP) {
@@ -243,10 +239,7 @@
         final Rect from = new Rect();
         dragLayer.getViewRectRelativeToSelf(d.dragView, from);
 
-        int width = mDrawable.getIntrinsicWidth();
-        int height = mDrawable.getIntrinsicHeight();
-        final Rect to = getIconRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight(),
-                width, height);
+        final Rect to = getIconRect(d);
         final float scale = (float) to.width() / from.width();
         mDropTargetBar.deferOnDragEnd();
 
@@ -268,7 +261,7 @@
     @Override
     public void prepareAccessibilityDrop() { }
 
-    @Thunk abstract void completeDrop(DragObject d);
+    public abstract void completeDrop(DragObject d);
 
     @Override
     public void getHitRectRelativeToDragLayer(android.graphics.Rect outRect) {
@@ -280,7 +273,11 @@
         outRect.offsetTo(coords[0], coords[1]);
     }
 
-    protected Rect getIconRect(int viewWidth, int viewHeight, int drawableWidth, int drawableHeight) {
+    public Rect getIconRect(DragObject dragObject) {
+        int viewWidth = dragObject.dragView.getMeasuredWidth();
+        int viewHeight = dragObject.dragView.getMeasuredHeight();
+        int drawableWidth = mDrawable.getIntrinsicWidth();
+        int drawableHeight = mDrawable.getIntrinsicHeight();
         DragLayer dragLayer = mLauncher.getDragLayer();
 
         // Find the rect to animate to (the view is center aligned)
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 705f841..9097ed2 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -16,19 +16,13 @@
 
 package com.android.launcher3;
 
-import android.animation.TimeInterpolator;
 import android.content.Context;
-import android.graphics.PointF;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.View;
-import android.view.animation.AnimationUtils;
 
-import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.folder.Folder;
-import com.android.launcher3.util.FlingAnimation;
-import com.android.launcher3.util.Thunk;
 
 public class DeleteDropTarget extends ButtonDropTarget {
 
@@ -78,7 +72,7 @@
     }
 
     @Override
-    @Thunk void completeDrop(DragObject d) {
+    public void completeDrop(DragObject d) {
         ItemInfo item = d.dragInfo;
         if ((d.dragSource instanceof Workspace) || (d.dragSource instanceof Folder)) {
             removeWorkspaceOrFolderItem(mLauncher, item, null);
@@ -96,53 +90,4 @@
         launcher.getWorkspace().stripEmptyScreens();
         launcher.getDragLayer().announceForAccessibility(launcher.getString(R.string.item_removed));
     }
-
-    @Override
-    public void onFlingToDelete(final DragObject d, PointF vel) {
-        // Don't highlight the icon as it's animating
-        d.dragView.setColor(0);
-
-        final DragLayer dragLayer = mLauncher.getDragLayer();
-        FlingAnimation fling = new FlingAnimation(d, vel,
-                getIconRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight(),
-                        mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight()),
-                        dragLayer);
-
-        final int duration = fling.getDuration();
-        final long startTime = AnimationUtils.currentAnimationTimeMillis();
-
-        // NOTE: Because it takes time for the first frame of animation to actually be
-        // called and we expect the animation to be a continuation of the fling, we have
-        // to account for the time that has elapsed since the fling finished.  And since
-        // we don't have a startDelay, we will always get call to update when we call
-        // start() (which we want to ignore).
-        final TimeInterpolator tInterpolator = new TimeInterpolator() {
-            private int mCount = -1;
-            private float mOffset = 0f;
-
-            @Override
-            public float getInterpolation(float t) {
-                if (mCount < 0) {
-                    mCount++;
-                } else if (mCount == 0) {
-                    mOffset = Math.min(0.5f, (float) (AnimationUtils.currentAnimationTimeMillis() -
-                            startTime) / duration);
-                    mCount++;
-                }
-                return Math.min(1f, mOffset + t);
-            }
-        };
-
-        Runnable onAnimationEndRunnable = new Runnable() {
-            @Override
-            public void run() {
-                mLauncher.exitSpringLoadedDragMode();
-                completeDrop(d);
-                mLauncher.getDragController().onDeferredEndFling(d);
-            }
-        };
-
-        dragLayer.animateView(d.dragView, fling, duration, tInterpolator, onAnimationEndRunnable,
-                DragLayer.ANIMATION_END_DISAPPEAR, null);
-    }
 }
diff --git a/src/com/android/launcher3/DragSource.java b/src/com/android/launcher3/DragSource.java
index 2fb495f..dcd8f58 100644
--- a/src/com/android/launcher3/DragSource.java
+++ b/src/com/android/launcher3/DragSource.java
@@ -27,11 +27,6 @@
 public interface DragSource extends LogContainerProvider {
 
     /**
-     * @return whether items dragged from this source supports
-     */
-    boolean supportsFlingToDelete();
-
-    /**
      * @return whether items dragged from this source supports 'App Info'
      */
     boolean supportsAppInfoDropTarget();
@@ -48,13 +43,6 @@
     float getIntrinsicIconScaleFactor();
 
     /**
-     * A callback specifically made back to the source after an item from this source has been flung
-     * to be deleted on a DropTarget.  In such a situation, this method will be called after
-     * onDropCompleted, and more importantly, after the fling animation has completed.
-     */
-    void onFlingToDeleteCompleted();
-
-    /**
      * A callback made back to the source after an item from this source has been dropped on a
      * DropTarget.
      */
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index e91fc15..7d047d7 100644
--- a/src/com/android/launcher3/DropTarget.java
+++ b/src/com/android/launcher3/DropTarget.java
@@ -16,12 +16,10 @@
 
 package com.android.launcher3;
 
-import com.android.launcher3.dragndrop.DragView;
-
-import android.graphics.PointF;
 import android.graphics.Rect;
 
 import com.android.launcher3.accessibility.DragViewStateAnnouncer;
+import com.android.launcher3.dragndrop.DragView;
 
 /**
  * Interface defining an object that can receive a drag.
@@ -117,13 +115,6 @@
     void onDragExit(DragObject dragObject);
 
     /**
-     * Handle an object being dropped as a result of flinging to delete and will be called in place
-     * of onDrop().  (This is only called on objects that are set as the DragController's
-     * fling-to-delete target.
-     */
-    void onFlingToDelete(DragObject dragObject, PointF vec);
-
-    /**
      * Check if a drop action can occur at, or near, the requested location.
      * This will be called just before onDrop.
      * @return True if the drop will be accepted, false otherwise.
diff --git a/src/com/android/launcher3/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java
index 42bab47..0840b70 100644
--- a/src/com/android/launcher3/DropTargetBar.java
+++ b/src/com/android/launcher3/DropTargetBar.java
@@ -21,6 +21,7 @@
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewDebug;
+import android.view.ViewGroup;
 import android.view.ViewPropertyAnimator;
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.AccelerateInterpolator;
@@ -56,11 +57,6 @@
 
     private ViewPropertyAnimator mCurrentAnimation;
 
-    // Drop targets
-    private ButtonDropTarget mDeleteDropTarget;
-    private ButtonDropTarget mAppInfoDropTarget;
-    private ButtonDropTarget mUninstallDropTarget;
-
     public DropTargetBar(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
@@ -73,30 +69,27 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
 
-        // Get the individual components
-        mDeleteDropTarget = (ButtonDropTarget) findViewById(R.id.delete_target_text);
-        mAppInfoDropTarget = (ButtonDropTarget) findViewById(R.id.info_target_text);
-        mUninstallDropTarget = (ButtonDropTarget) findViewById(R.id.uninstall_target_text);
-
-        mDeleteDropTarget.setDropTargetBar(this);
-        mAppInfoDropTarget.setDropTargetBar(this);
-        mUninstallDropTarget.setDropTargetBar(this);
-
         // Initialize with hidden state
         setAlpha(0f);
     }
 
     public void setup(DragController dragController) {
         dragController.addDragListener(this);
-        dragController.setFlingToDeleteDropTarget(mDeleteDropTarget);
+        setupButtonDropTarget(this, dragController);
+    }
 
-        dragController.addDragListener(mDeleteDropTarget);
-        dragController.addDragListener(mAppInfoDropTarget);
-        dragController.addDragListener(mUninstallDropTarget);
-
-        dragController.addDropTarget(mDeleteDropTarget);
-        dragController.addDropTarget(mAppInfoDropTarget);
-        dragController.addDropTarget(mUninstallDropTarget);
+    private void setupButtonDropTarget(View view, DragController dragController) {
+        if (view instanceof ButtonDropTarget) {
+            ButtonDropTarget bdt = (ButtonDropTarget) view;
+            bdt.setDropTargetBar(this);
+            dragController.addDragListener(bdt);
+            dragController.addDropTarget(bdt);
+        } else if (view instanceof ViewGroup) {
+            ViewGroup vg = (ViewGroup) view;
+            for (int i = vg.getChildCount() - 1; i >= 0; i--) {
+                setupButtonDropTarget(vg.getChildAt(i), dragController);
+            }
+        }
     }
 
     private void animateToVisibility(boolean isVisible) {
diff --git a/src/com/android/launcher3/InfoDropTarget.java b/src/com/android/launcher3/InfoDropTarget.java
index 398c9d2..6a6d285 100644
--- a/src/com/android/launcher3/InfoDropTarget.java
+++ b/src/com/android/launcher3/InfoDropTarget.java
@@ -49,7 +49,7 @@
     }
 
     @Override
-    void completeDrop(DragObject d) {
+    public void completeDrop(DragObject d) {
         DropTargetResultCallback callback = d.dragSource instanceof DropTargetResultCallback
                 ? (DropTargetResultCallback) d.dragSource : null;
         startDetailsActivityForInfo(d.dragInfo, mLauncher, callback);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index accc3cd..7775a6b 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -3068,7 +3068,7 @@
                 || mState == State.WIDGETS_SPRING_LOADED;
     }
 
-    void exitSpringLoadedDragMode() {
+    public void exitSpringLoadedDragMode() {
         if (mState == State.APPS_SPRING_LOADED) {
             showAppsView(true /* animated */,
                     false /* updatePredictedApps */, false /* focusSearchBar */);
diff --git a/src/com/android/launcher3/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java
index 7ea9aca..00c613a 100644
--- a/src/com/android/launcher3/UninstallDropTarget.java
+++ b/src/com/android/launcher3/UninstallDropTarget.java
@@ -95,7 +95,7 @@
     }
 
     @Override
-    void completeDrop(final DragObject d) {
+    public void completeDrop(final DragObject d) {
         DropTargetResultCallback callback = d.dragSource instanceof DropTargetResultCallback
                 ? (DropTargetResultCallback) d.dragSource : null;
         startUninstallActivity(mLauncher, d.dragInfo, callback);
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 13bea20..49e14e4 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -33,7 +33,6 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Point;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
@@ -3722,11 +3721,6 @@
     }
 
     @Override
-    public boolean supportsFlingToDelete() {
-        return true;
-    }
-
-    @Override
     public boolean supportsAppInfoDropTarget() {
         return !FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND;
     }
@@ -3736,16 +3730,6 @@
         return true;
     }
 
-    @Override
-    public void onFlingToDelete(DragObject d, PointF vec) {
-        // Do nothing
-    }
-
-    @Override
-    public void onFlingToDeleteCompleted() {
-        // Do nothing
-    }
-
     public boolean isDropEnabled() {
         return true;
     }
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index f98e027..6fae7b1 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -402,11 +402,6 @@
     }
 
     @Override
-    public boolean supportsFlingToDelete() {
-        return true;
-    }
-
-    @Override
     public boolean supportsAppInfoDropTarget() {
         return true;
     }
@@ -423,14 +418,6 @@
     }
 
     @Override
-    public void onFlingToDeleteCompleted() {
-        // We just dismiss the drag when we fling, so cleanup here
-        mLauncher.exitSpringLoadedDragModeDelayed(true,
-                Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
-        mLauncher.unlockScreenOrientation(false);
-    }
-
-    @Override
     public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
             boolean success) {
         if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
diff --git a/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java b/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java
index e968f36..1623010 100644
--- a/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java
+++ b/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java
@@ -37,11 +37,6 @@
     }
 
     @Override
-    public boolean supportsFlingToDelete() {
-        return false;
-    }
-
-    @Override
     public boolean supportsAppInfoDropTarget() {
         return false;
     }
@@ -57,10 +52,6 @@
     }
 
     @Override
-    public void onFlingToDeleteCompleted() {
-    }
-
-    @Override
     public void onDropCompleted(View target, DragObject d,
             boolean isFlingToDelete, boolean success) {
         if (!success) {
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 67ef5fc..a5a54c1 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -17,11 +17,9 @@
 package com.android.launcher3.dragndrop;
 
 import android.content.ComponentName;
-import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Point;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.IBinder;
@@ -29,7 +27,6 @@
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
-import android.view.VelocityTracker;
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.inputmethod.InputMethodManager;
@@ -54,8 +51,6 @@
  * Class for initiating a drag within a view or across multiple views.
  */
 public class DragController implements DragDriver.EventListener, TouchController {
-    private static final String TAG = "Launcher.DragController";
-
     public static final int SCROLL_DELAY = 500;
     public static final int RESCROLL_DELAY = PagedView.PAGE_SNAP_ANIMATION_DURATION + 150;
 
@@ -68,10 +63,9 @@
     public static final int SCROLL_LEFT = 0;
     public static final int SCROLL_RIGHT = 1;
 
-    private static final float MAX_FLING_DEGREES = 35f;
-
     @Thunk Launcher mLauncher;
     private Handler mHandler;
+    private FlingToDeleteHelper mFlingToDeleteHelper;
 
     // temporaries to avoid gc thrash
     private Rect mRectTemp = new Rect();
@@ -103,7 +97,6 @@
     /** Who can receive drop events */
     private ArrayList<DropTarget> mDropTargets = new ArrayList<DropTarget>();
     private ArrayList<DragListener> mListeners = new ArrayList<DragListener>();
-    private DropTarget mFlingToDeleteDropTarget;
 
     /** The window token used as the parent for the DragView. */
     private IBinder mWindowToken;
@@ -119,8 +112,6 @@
 
     private DropTarget mLastDropTarget;
 
-    private InputMethodManager mInputMethodManager;
-
     @Thunk int mLastTouch[] = new int[2];
     @Thunk long mLastTouchUpTime = -1;
     @Thunk int mDistanceSinceScroll = 0;
@@ -128,9 +119,6 @@
     private int mTmpPoint[] = new int[2];
     private Rect mDragLayerRect = new Rect();
 
-    protected final int mFlingToDeleteThresholdVelocity;
-    private VelocityTracker mVelocityTracker;
-
     private boolean mIsInPreDrag;
 
     /**
@@ -159,11 +147,8 @@
         mLauncher = launcher;
         mHandler = new Handler();
         mScrollZone = r.getDimensionPixelSize(R.dimen.scroll_zone);
-        mVelocityTracker = VelocityTracker.obtain();
-
-        mFlingToDeleteThresholdVelocity =
-                r.getDimensionPixelSize(R.dimen.drag_flingToDeleteMinVelocity);
         mIsRtl = Utilities.isRtl(r);
+        mFlingToDeleteHelper = new FlingToDeleteHelper(launcher);
     }
 
     /**
@@ -208,11 +193,8 @@
         }
 
         // Hide soft keyboard, if visible
-        if (mInputMethodManager == null) {
-            mInputMethodManager = (InputMethodManager)
-                    mLauncher.getSystemService(Context.INPUT_METHOD_SERVICE);
-        }
-        mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0);
+        mLauncher.getSystemService(InputMethodManager.class)
+                .hideSoftInputFromWindow(mWindowToken, 0);
 
         mOptions = options;
         if (mOptions.systemDndStartPoint != null) {
@@ -369,7 +351,7 @@
             }
         }
 
-        releaseVelocityTracker();
+        mFlingToDeleteHelper.releaseVelocityTracker();
     }
 
     public void animateDragViewToOriginalPosition(final Runnable onComplete,
@@ -411,10 +393,6 @@
         }
     }
 
-    public void onDeferredEndFling(DropTarget.DragObject d) {
-        d.dragSource.onFlingToDeleteCompleted();
-    }
-
     /**
      * Clamps the position to the drag layer bounds.
      */
@@ -453,22 +431,16 @@
     }
 
     @Override
-    public void onDriverDragEnd(float x, float y, DropTarget dropTargetOverride) {
+    public void onDriverDragEnd(float x, float y) {
         DropTarget dropTarget;
-        PointF vec = null;
-
-        if (dropTargetOverride != null) {
-            dropTarget = dropTargetOverride;
+        Runnable flingAnimation = mFlingToDeleteHelper.getFlingAnimation(mDragObject);
+        if (flingAnimation != null) {
+            dropTarget = mFlingToDeleteHelper.getDropTarget();
         } else {
-            vec = isFlingingToDelete(mDragObject.dragSource);
-            if (vec != null) {
-                dropTarget = mFlingToDeleteDropTarget;
-            } else {
-                dropTarget = findDropTarget((int) x, (int) y, mCoordinatesTemp);
-            }
+            dropTarget = findDropTarget((int) x, (int) y, mCoordinatesTemp);
         }
 
-        drop(dropTarget, x, y, vec);
+        drop(dropTarget, flingAnimation);
 
         endDrag();
     }
@@ -487,7 +459,7 @@
         }
 
         // Update the velocity tracker
-        acquireVelocityTrackerAndAddMovement(ev);
+        mFlingToDeleteHelper.recordMotionEvent(ev);
 
         final int action = ev.getAction();
         final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
@@ -635,7 +607,7 @@
         }
 
         // Update the velocity tracker
-        acquireVelocityTrackerAndAddMovement(ev);
+        mFlingToDeleteHelper.recordMotionEvent(ev);
 
         final int action = ev.getAction();
         final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
@@ -688,47 +660,12 @@
 
         dropTarget.prepareAccessibilityDrop();
         // Perform the drop
-        drop(dropTarget, location[0], location[1], null);
+        drop(dropTarget, null);
         endDrag();
     }
 
-    /**
-     * Determines whether the user flung the current item to delete it.
-     *
-     * @return the vector at which the item was flung, or null if no fling was detected.
-     */
-    private PointF isFlingingToDelete(DragSource source) {
-        if (mFlingToDeleteDropTarget == null) return null;
-        if (!source.supportsFlingToDelete()) return null;
-
-        ViewConfiguration config = ViewConfiguration.get(mLauncher);
-        mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity());
-        PointF vel = new PointF(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
-        float theta = MAX_FLING_DEGREES + 1;
-        if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) {
-            // Do a quick dot product test to ensure that we are flinging upwards
-            PointF upVec = new PointF(0f, -1f);
-            theta = getAngleBetweenVectors(vel, upVec);
-        } else if (mLauncher.getDeviceProfile().isVerticalBarLayout() &&
-                mVelocityTracker.getXVelocity() < mFlingToDeleteThresholdVelocity) {
-            // Remove icon is on left side instead of top, so check if we are flinging to the left.
-            PointF leftVec = new PointF(-1f, 0f);
-            theta = getAngleBetweenVectors(vel, leftVec);
-        }
-        if (theta <= Math.toRadians(MAX_FLING_DEGREES)) {
-            return vel;
-        }
-        return null;
-    }
-
-    private float getAngleBetweenVectors(PointF vec1, PointF vec2) {
-        return (float) Math.acos(((vec1.x * vec2.x) + (vec1.y * vec2.y)) /
-                (vec1.length() * vec2.length()));
-    }
-
-    private void drop(DropTarget dropTarget, float x, float y, PointF flingVel) {
+    private void drop(DropTarget dropTarget, Runnable flingAnimation) {
         final int[] coordinates = mCoordinatesTemp;
-
         mDragObject.x = coordinates[0];
         mDragObject.y = coordinates[1];
 
@@ -750,8 +687,8 @@
         if (dropTarget != null) {
             dropTarget.onDragExit(mDragObject);
             if (dropTarget.acceptDrop(mDragObject)) {
-                if (flingVel != null) {
-                    dropTarget.onFlingToDelete(mDragObject, flingVel);
+                if (flingAnimation != null) {
+                    flingAnimation.run();
                 } else if (!mIsInPreDrag) {
                     dropTarget.onDrop(mDragObject);
                 }
@@ -762,7 +699,7 @@
         mLauncher.getUserEventDispatcher().logDragNDrop(mDragObject, dropTargetAsView);
         if (!mIsInPreDrag) {
             mDragObject.dragSource.onDropCompleted(
-                    dropTargetAsView, mDragObject, flingVel != null, accepted);
+                    dropTargetAsView, mDragObject, flingAnimation != null, accepted);
         }
     }
 
@@ -829,27 +766,6 @@
     }
 
     /**
-     * Sets the current fling-to-delete drop target.
-     */
-    public void setFlingToDeleteDropTarget(DropTarget target) {
-        mFlingToDeleteDropTarget = target;
-    }
-
-    private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
-        if (mVelocityTracker == null) {
-            mVelocityTracker = VelocityTracker.obtain();
-        }
-        mVelocityTracker.addMovement(ev);
-    }
-
-    private void releaseVelocityTracker() {
-        if (mVelocityTracker != null) {
-            mVelocityTracker.recycle();
-            mVelocityTracker = null;
-        }
-    }
-
-    /**
      * Set which view scrolls for touch events near the edge of the screen.
      */
     public void setScrollView(View v) {
diff --git a/src/com/android/launcher3/dragndrop/DragDriver.java b/src/com/android/launcher3/dragndrop/DragDriver.java
index d0c8e16..a90cfff 100644
--- a/src/com/android/launcher3/dragndrop/DragDriver.java
+++ b/src/com/android/launcher3/dragndrop/DragDriver.java
@@ -23,7 +23,6 @@
 import android.view.DragEvent;
 import android.view.MotionEvent;
 
-import com.android.launcher3.DropTarget;
 import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.InstallShortcutReceiver;
 import com.android.launcher3.ShortcutInfo;
@@ -40,7 +39,7 @@
     public interface EventListener {
         void onDriverDragMove(float x, float y);
         void onDriverDragExitWindow();
-        void onDriverDragEnd(float x, float y, DropTarget dropTargetOverride);
+        void onDriverDragEnd(float x, float y);
         void onDriverDragCancel();
     }
 
@@ -62,7 +61,7 @@
                 break;
             case MotionEvent.ACTION_UP:
                 mEventListener.onDriverDragMove(ev.getX(), ev.getY());
-                mEventListener.onDriverDragEnd(ev.getX(), ev.getY(), null);
+                mEventListener.onDriverDragEnd(ev.getX(), ev.getY());
                 break;
             case MotionEvent.ACTION_CANCEL:
                 mEventListener.onDriverDragCancel();
@@ -80,7 +79,7 @@
 
         switch (action) {
             case MotionEvent.ACTION_UP:
-                mEventListener.onDriverDragEnd(ev.getX(), ev.getY(), null);
+                mEventListener.onDriverDragEnd(ev.getX(), ev.getY());
                 break;
             case MotionEvent.ACTION_CANCEL:
                 mEventListener.onDriverDragCancel();
@@ -160,7 +159,7 @@
 
             case DragEvent.ACTION_DRAG_ENDED:
                 if (mReceivedDropEvent) {
-                    mEventListener.onDriverDragEnd(mLastX, mLastY, null);
+                    mEventListener.onDriverDragEnd(mLastX, mLastY);
                 } else {
                     mEventListener.onDriverDragCancel();
                 }
diff --git a/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java b/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
new file mode 100644
index 0000000..a2aa27d
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
@@ -0,0 +1,109 @@
+/*
+ * 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.dragndrop;
+
+import android.graphics.PointF;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+
+import com.android.launcher3.ButtonDropTarget;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.util.FlingAnimation;
+
+/**
+ * Utility class to manage fling to delete action during drag and drop.
+ */
+public class FlingToDeleteHelper {
+
+    private static final float MAX_FLING_DEGREES = 35f;
+
+    private final Launcher mLauncher;
+    private final int mFlingToDeleteThresholdVelocity;
+
+    private ButtonDropTarget mDropTarget;
+    private VelocityTracker mVelocityTracker;
+
+    public FlingToDeleteHelper(Launcher launcher) {
+        mLauncher = launcher;
+        mFlingToDeleteThresholdVelocity = launcher.getResources()
+                .getDimensionPixelSize(R.dimen.drag_flingToDeleteMinVelocity);
+    }
+
+    public void recordMotionEvent(MotionEvent ev) {
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        }
+        mVelocityTracker.addMovement(ev);
+    }
+
+    public void releaseVelocityTracker() {
+        if (mVelocityTracker != null) {
+            mVelocityTracker.recycle();
+            mVelocityTracker = null;
+        }
+    }
+
+    public DropTarget getDropTarget() {
+        return mDropTarget;
+    }
+
+    public Runnable getFlingAnimation(DropTarget.DragObject dragObject) {
+        PointF vel = isFlingingToDelete();
+        if (vel == null) {
+            return null;
+        }
+        return new FlingAnimation(dragObject, vel, mDropTarget, mLauncher);
+    }
+
+    /**
+     * Determines whether the user flung the current item to delete it.
+     *
+     * @return the vector at which the item was flung, or null if no fling was detected.
+     */
+    private PointF isFlingingToDelete() {
+        if (mDropTarget == null) {
+            mDropTarget = (ButtonDropTarget) mLauncher.findViewById(R.id.delete_target_text);
+        }
+        if (mDropTarget == null || !mDropTarget.isDropEnabled()) return null;
+        ViewConfiguration config = ViewConfiguration.get(mLauncher);
+        mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity());
+        PointF vel = new PointF(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
+        float theta = MAX_FLING_DEGREES + 1;
+        if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) {
+            // Do a quick dot product test to ensure that we are flinging upwards
+            PointF upVec = new PointF(0f, -1f);
+            theta = getAngleBetweenVectors(vel, upVec);
+        } else if (mLauncher.getDeviceProfile().isVerticalBarLayout() &&
+                mVelocityTracker.getXVelocity() < mFlingToDeleteThresholdVelocity) {
+            // Remove icon is on left side instead of top, so check if we are flinging to the left.
+            PointF leftVec = new PointF(-1f, 0f);
+            theta = getAngleBetweenVectors(vel, leftVec);
+        }
+        if (theta <= Math.toRadians(MAX_FLING_DEGREES)) {
+            return vel;
+        }
+        return null;
+    }
+
+    private float getAngleBetweenVectors(PointF vec1, PointF vec2) {
+        return (float) Math.acos(((vec1.x * vec2.x) + (vec1.y * vec2.y)) /
+                (vec1.length() * vec2.length()));
+    }
+}
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index a81b4ca..315f511 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -25,12 +25,10 @@
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.Resources;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Build;
 import android.text.InputType;
 import android.text.Selection;
-import android.text.Spannable;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.ActionMode;
@@ -51,7 +49,6 @@
 import com.android.launcher3.AbstractFloatingView;
 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;
@@ -1010,11 +1007,6 @@
     }
 
     @Override
-    public boolean supportsFlingToDelete() {
-        return true;
-    }
-
-    @Override
     public boolean supportsAppInfoDropTarget() {
         return !FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND;
     }
@@ -1024,16 +1016,6 @@
         return true;
     }
 
-    @Override
-    public void onFlingToDelete(DragObject d, PointF vec) {
-        // Do nothing
-    }
-
-    @Override
-    public void onFlingToDeleteCompleted() {
-        // Do nothing
-    }
-
     private void updateItemLocationsInDatabaseBatch() {
         ArrayList<View> list = getItemsInReadingOrder();
         ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
index 314a862..a253492 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
@@ -475,11 +475,6 @@
     }
 
     @Override
-    public boolean supportsFlingToDelete() {
-        return true;
-    }
-
-    @Override
     public boolean supportsAppInfoDropTarget() {
         return true;
     }
@@ -495,11 +490,6 @@
     }
 
     @Override
-    public void onFlingToDeleteCompleted() {
-        // Don't care; ignore.
-    }
-
-    @Override
     public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
             boolean success) {
         if (!success) {
diff --git a/src/com/android/launcher3/util/FlingAnimation.java b/src/com/android/launcher3/util/FlingAnimation.java
index da8bae7..d475ee9 100644
--- a/src/com/android/launcher3/util/FlingAnimation.java
+++ b/src/com/android/launcher3/util/FlingAnimation.java
@@ -5,13 +5,16 @@
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.graphics.PointF;
 import android.graphics.Rect;
+import android.view.animation.AnimationUtils;
 import android.view.animation.DecelerateInterpolator;
 
+import com.android.launcher3.ButtonDropTarget;
 import com.android.launcher3.DropTarget.DragObject;
+import com.android.launcher3.Launcher;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragView;
 
-public class FlingAnimation implements AnimatorUpdateListener {
+public class FlingAnimation implements AnimatorUpdateListener, Runnable {
 
     /**
      * Maximum acceleration in one dimension (pixels per milliseconds)
@@ -19,40 +22,86 @@
     private static final float MAX_ACCELERATION = 0.5f;
     private static final int DRAG_END_DELAY = 300;
 
+    private final ButtonDropTarget mDropTarget;
+    private final Launcher mLauncher;
+
     protected final DragObject mDragObject;
-    protected final Rect mIconRect;
     protected final DragLayer mDragLayer;
-    protected final Rect mFrom;
-    protected final int mDuration;
-    protected final float mUX, mUY;
-    protected final float mAnimationTimeFraction;
     protected final TimeInterpolator mAlphaInterpolator = new DecelerateInterpolator(0.75f);
+    protected final float mUX, mUY;
+
+    protected Rect mIconRect;
+    protected Rect mFrom;
+    protected int mDuration;
+    protected float mAnimationTimeFraction;
 
     protected float mAX, mAY;
 
-    /**
-     * @param vel initial fling velocity in pixels per second.
-     */
-    public FlingAnimation(DragObject d, PointF vel, Rect iconRect, DragLayer dragLayer) {
+    public FlingAnimation(DragObject d, PointF vel, ButtonDropTarget dropTarget, Launcher launcher) {
+        mDropTarget = dropTarget;
+        mLauncher = launcher;
         mDragObject = d;
         mUX = vel.x / 1000;
         mUY = vel.y / 1000;
-        mIconRect = iconRect;
+        mDragLayer = mLauncher.getDragLayer();
+    }
 
-        mDragLayer = dragLayer;
+    @Override
+    public void run() {
+        mIconRect = mDropTarget.getIconRect(mDragObject);
+
+        // Initiate from
         mFrom = new Rect();
-        dragLayer.getViewRectRelativeToSelf(d.dragView, mFrom);
-
-        float scale = d.dragView.getScaleX();
-        float xOffset = ((scale - 1f) * d.dragView.getMeasuredWidth()) / 2f;
-        float yOffset = ((scale - 1f) * d.dragView.getMeasuredHeight()) / 2f;
+        mDragLayer.getViewRectRelativeToSelf(mDragObject.dragView, mFrom);
+        float scale = mDragObject.dragView.getScaleX();
+        float xOffset = ((scale - 1f) * mDragObject.dragView.getMeasuredWidth()) / 2f;
+        float yOffset = ((scale - 1f) * mDragObject.dragView.getMeasuredHeight()) / 2f;
         mFrom.left += xOffset;
         mFrom.right -= xOffset;
         mFrom.top += yOffset;
         mFrom.bottom -= yOffset;
+        mDuration = Math.abs(mUY) > Math.abs(mUX) ? initFlingUpDuration() : initFlingLeftDuration();
 
-        mDuration = Math.abs(vel.y) > Math.abs(vel.x) ? initFlingUpDuration() : initFlingLeftDuration();
         mAnimationTimeFraction = ((float) mDuration) / (mDuration + DRAG_END_DELAY);
+
+        // Don't highlight the icon as it's animating
+        mDragObject.dragView.setColor(0);
+
+        final int duration = mDuration + DRAG_END_DELAY;
+        final long startTime = AnimationUtils.currentAnimationTimeMillis();
+
+        // NOTE: Because it takes time for the first frame of animation to actually be
+        // called and we expect the animation to be a continuation of the fling, we have
+        // to account for the time that has elapsed since the fling finished.  And since
+        // we don't have a startDelay, we will always get call to update when we call
+        // start() (which we want to ignore).
+        final TimeInterpolator tInterpolator = new TimeInterpolator() {
+            private int mCount = -1;
+            private float mOffset = 0f;
+
+            @Override
+            public float getInterpolation(float t) {
+                if (mCount < 0) {
+                    mCount++;
+                } else if (mCount == 0) {
+                    mOffset = Math.min(0.5f, (float) (AnimationUtils.currentAnimationTimeMillis() -
+                            startTime) / duration);
+                    mCount++;
+                }
+                return Math.min(1f, mOffset + t);
+            }
+        };
+
+        Runnable onAnimationEndRunnable = new Runnable() {
+            @Override
+            public void run() {
+                mLauncher.exitSpringLoadedDragMode();
+                mDropTarget.completeDrop(mDragObject);
+            }
+        };
+
+        mDragLayer.animateView(mDragObject.dragView, this, duration, tInterpolator,
+                onAnimationEndRunnable, DragLayer.ANIMATION_END_DISAPPEAR, null);
     }
 
     /**
@@ -111,10 +160,6 @@
         return (int) Math.round(t);
     }
 
-    public final int getDuration() {
-        return mDuration + DRAG_END_DELAY;
-    }
-
     @Override
     public void onAnimationUpdate(ValueAnimator animation) {
         float t = animation.getAnimatedFraction();
diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java
index 2e12942..165d12e 100644
--- a/src/com/android/launcher3/widget/WidgetsContainerView.java
+++ b/src/com/android/launcher3/widget/WidgetsContainerView.java
@@ -31,8 +31,6 @@
 import com.android.launcher3.DeleteDropTarget;
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.folder.Folder;
 import com.android.launcher3.IconCache;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
@@ -42,10 +40,11 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.WidgetPreviewLoader;
 import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.folder.Folder;
 import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.model.PackageItemInfo;
 import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.MultiHashMap;
@@ -246,11 +245,6 @@
     //
 
     @Override
-    public boolean supportsFlingToDelete() {
-        return true;
-    }
-
-    @Override
     public boolean supportsAppInfoDropTarget() {
         return true;
     }
@@ -270,14 +264,6 @@
     }
 
     @Override
-    public void onFlingToDeleteCompleted() {
-        // We just dismiss the drag when we fling, so cleanup here
-        mLauncher.exitSpringLoadedDragModeDelayed(true,
-                Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
-        mLauncher.unlockScreenOrientation(false);
-    }
-
-    @Override
     public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete,
             boolean success) {
         if (LOGD) {