Adding fling-to-delete.

- Also fixing issue where the drop target icon changes color slower than the text.

Change-Id: I0bfa59da5d202016342f1c3de419ebcafd81ff6f
diff --git a/src/com/android/launcher2/DragController.java b/src/com/android/launcher2/DragController.java
index 019a749..2a88925 100644
--- a/src/com/android/launcher2/DragController.java
+++ b/src/com/android/launcher2/DragController.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.IBinder;
@@ -26,6 +27,7 @@
 import android.util.Log;
 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;
@@ -60,6 +62,9 @@
     static final int SCROLL_LEFT = 0;
     static final int SCROLL_RIGHT = 1;
 
+    private static final float MAX_FLING_DEGREES = 35f;
+    private static final int FLING_TO_DELETE_THRESHOLD_Y_VELOCITY = -1400;
+
     private Launcher mLauncher;
     private Handler mHandler;
     private final Vibrator mVibrator = new Vibrator();
@@ -86,8 +91,8 @@
 
     /** 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;
@@ -111,6 +116,9 @@
     private int mTmpPoint[] = new int[2];
     private Rect mDragLayerRect = new Rect();
 
+    protected int mFlingToDeleteThresholdVelocity;
+    private VelocityTracker mVelocityTracker;
+
     /**
      * Interface to receive notifications when a drag starts or stops
      */
@@ -141,6 +149,10 @@
         mLauncher = launcher;
         mHandler = new Handler();
         mScrollZone = launcher.getResources().getDimensionPixelSize(R.dimen.scroll_zone);
+        mVelocityTracker = VelocityTracker.obtain();
+
+        float density = launcher.getResources().getDisplayMetrics().density;
+        mFlingToDeleteThresholdVelocity = (int) (FLING_TO_DELETE_THRESHOLD_Y_VELOCITY * density);
     }
 
     public boolean dragging() {
@@ -378,15 +390,35 @@
         if (mDragging) {
             mDragging = false;
             clearScrollRunnable();
-            for (DragListener listener : mListeners) {
-                listener.onDragEnd();
-            }
+            boolean isDeferred = false;
             if (mDragObject.dragView != null) {
-                if (!mDragObject.deferDragViewCleanupPostAnimation) {
+                isDeferred = mDragObject.deferDragViewCleanupPostAnimation;
+                if (!isDeferred) {
                     mDragObject.dragView.remove();
                 }
                 mDragObject.dragView = null;
             }
+
+            // Only end the drag if we are not deferred
+            if (!isDeferred) {
+                for (DragListener listener : mListeners) {
+                    listener.onDragEnd();
+                }
+            }
+        }
+
+        releaseVelocityTracker();
+    }
+
+    /**
+     * This only gets called as a result of drag view cleanup being deferred in endDrag();
+     */
+    void onDeferredEndDrag(DragView dragView) {
+        dragView.remove();
+
+        // If we skipped calling onDragEnd() before, do it now
+        for (DragListener listener : mListeners) {
+            listener.onDragEnd();
         }
     }
 
@@ -408,8 +440,11 @@
             Log.d(Launcher.TAG, "DragController.onInterceptTouchEvent " + ev + " mDragging="
                     + mDragging);
         }
-        final int action = ev.getAction();
 
+        // Update the velocity tracker
+        acquireVelocityTrackerAndAddMovement(ev);
+
+        final int action = ev.getAction();
         final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
         final int dragLayerX = dragLayerPos[0];
         final int dragLayerY = dragLayerPos[1];
@@ -425,7 +460,12 @@
                 break;
             case MotionEvent.ACTION_UP:
                 if (mDragging) {
-                    drop(dragLayerX, dragLayerY);
+                    PointF vec = isFlingingToDelete(mDragObject.dragSource);
+                    if (vec != null) {
+                        dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec);
+                    } else {
+                        drop(dragLayerX, dragLayerY);
+                    }
                 }
                 endDrag();
                 break;
@@ -529,6 +569,9 @@
             return false;
         }
 
+        // Update the velocity tracker
+        acquireVelocityTrackerAndAddMovement(ev);
+
         final int action = ev.getAction();
         final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
         final int dragLayerX = dragLayerPos[0];
@@ -553,10 +596,15 @@
         case MotionEvent.ACTION_UP:
             // Ensure that we've processed a move event at the current pointer location.
             handleMoveEvent(dragLayerX, dragLayerY);
-
             mHandler.removeCallbacks(mScrollRunnable);
+
             if (mDragging) {
-                drop(dragLayerX, dragLayerY);
+                PointF vec = isFlingingToDelete(mDragObject.dragSource);
+                if (vec != null) {
+                    dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec);
+                } else {
+                    drop(dragLayerX, dragLayerY);
+                }
             }
             endDrag();
             break;
@@ -569,6 +617,58 @@
         return true;
     }
 
+    /**
+     * 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());
+
+        if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) {
+            // Do a quick dot product test to ensure that we are flinging upwards
+            PointF vel = new PointF(mVelocityTracker.getXVelocity(),
+                    mVelocityTracker.getYVelocity());
+            PointF upVec = new PointF(0f, -1f);
+            float theta = (float) Math.acos(((vel.x * upVec.x) + (vel.y * upVec.y)) /
+                    (vel.length() * upVec.length()));
+            if (theta <= Math.toRadians(MAX_FLING_DEGREES)) {
+                return vel;
+            }
+        }
+        return null;
+    }
+
+    private void dropOnFlingToDeleteTarget(float x, float y, PointF vel) {
+        final int[] coordinates = mCoordinatesTemp;
+
+        mDragObject.x = coordinates[0];
+        mDragObject.y = coordinates[1];
+        mDragObject.dragComplete = true;
+
+        // Clean up dragging on the target if it's not the current fling delete target otherwise,
+        // start dragging to it.
+        if (mLastDropTarget != null && mFlingToDeleteDropTarget != mLastDropTarget) {
+            mLastDropTarget.onDragExit(mDragObject);
+        }
+
+        // Drop onto the fling-to-delete target
+        boolean accepted = false;
+        mFlingToDeleteDropTarget.onDragEnter(mDragObject);
+        mFlingToDeleteDropTarget.onDragExit(mDragObject);
+        if (mFlingToDeleteDropTarget.acceptDrop(mDragObject)) {
+            mFlingToDeleteDropTarget.onFlingToDelete(mDragObject, mDragObject.x, mDragObject.y,
+                    vel);
+            accepted = true;
+        }
+        mDragObject.dragSource.onDropCompleted((View) mFlingToDeleteDropTarget, mDragObject,
+                accepted);
+    }
+
     private void drop(float x, float y) {
         final int[] coordinates = mCoordinatesTemp;
         final DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);
@@ -659,6 +759,27 @@
     }
 
     /**
+     * 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) {