1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.launcher3;
18 
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.res.Resources;
22 import android.graphics.Bitmap;
23 import android.graphics.Point;
24 import android.graphics.PointF;
25 import android.graphics.Rect;
26 import android.os.Handler;
27 import android.os.IBinder;
28 import android.util.Log;
29 import android.view.HapticFeedbackConstants;
30 import android.view.KeyEvent;
31 import android.view.MotionEvent;
32 import android.view.VelocityTracker;
33 import android.view.View;
34 import android.view.ViewConfiguration;
35 import android.view.accessibility.AccessibilityManager;
36 import android.view.inputmethod.InputMethodManager;
37 
38 import com.android.launcher3.accessibility.DragViewStateAnnouncer;
39 import com.android.launcher3.util.Thunk;
40 
41 import java.util.ArrayList;
42 import java.util.HashSet;
43 
44 /**
45  * Class for initiating a drag within a view or across multiple views.
46  */
47 public class DragController {
48     private static final String TAG = "Launcher.DragController";
49 
50     /** Indicates the drag is a move.  */
51     public static int DRAG_ACTION_MOVE = 0;
52 
53     /** Indicates the drag is a copy.  */
54     public static int DRAG_ACTION_COPY = 1;
55 
56     public static final int SCROLL_DELAY = 500;
57     public static final int RESCROLL_DELAY = PagedView.PAGE_SNAP_ANIMATION_DURATION + 150;
58 
59     private static final boolean PROFILE_DRAWING_DURING_DRAG = false;
60 
61     private static final int SCROLL_OUTSIDE_ZONE = 0;
62     private static final int SCROLL_WAITING_IN_ZONE = 1;
63 
64     static final int SCROLL_NONE = -1;
65     static final int SCROLL_LEFT = 0;
66     static final int SCROLL_RIGHT = 1;
67 
68     private static final float MAX_FLING_DEGREES = 35f;
69 
70     @Thunk Launcher mLauncher;
71     private Handler mHandler;
72 
73     // temporaries to avoid gc thrash
74     private Rect mRectTemp = new Rect();
75     private final int[] mCoordinatesTemp = new int[2];
76     private final boolean mIsRtl;
77 
78     /** Whether or not we're dragging. */
79     private boolean mDragging;
80 
81     /** Whether or not this is an accessible drag operation */
82     private boolean mIsAccessibleDrag;
83 
84     /** X coordinate of the down event. */
85     private int mMotionDownX;
86 
87     /** Y coordinate of the down event. */
88     private int mMotionDownY;
89 
90     /** the area at the edge of the screen that makes the workspace go left
91      *   or right while you're dragging.
92      */
93     private int mScrollZone;
94 
95     private DropTarget.DragObject mDragObject;
96 
97     /** Who can receive drop events */
98     private ArrayList<DropTarget> mDropTargets = new ArrayList<DropTarget>();
99     private ArrayList<DragListener> mListeners = new ArrayList<DragListener>();
100     private DropTarget mFlingToDeleteDropTarget;
101 
102     /** The window token used as the parent for the DragView. */
103     private IBinder mWindowToken;
104 
105     /** The view that will be scrolled when dragging to the left and right edges of the screen. */
106     private View mScrollView;
107 
108     private View mMoveTarget;
109 
110     @Thunk DragScroller mDragScroller;
111     @Thunk int mScrollState = SCROLL_OUTSIDE_ZONE;
112     private ScrollRunnable mScrollRunnable = new ScrollRunnable();
113 
114     private DropTarget mLastDropTarget;
115 
116     private InputMethodManager mInputMethodManager;
117 
118     @Thunk int mLastTouch[] = new int[2];
119     @Thunk long mLastTouchUpTime = -1;
120     @Thunk int mDistanceSinceScroll = 0;
121 
122     private int mTmpPoint[] = new int[2];
123     private Rect mDragLayerRect = new Rect();
124 
125     protected int mFlingToDeleteThresholdVelocity;
126     private VelocityTracker mVelocityTracker;
127 
128     /**
129      * Interface to receive notifications when a drag starts or stops
130      */
131     public interface DragListener {
132         /**
133          * A drag has begun
134          *
135          * @param source An object representing where the drag originated
136          * @param info The data associated with the object that is being dragged
137          * @param dragAction The drag action: either {@link DragController#DRAG_ACTION_MOVE}
138          *        or {@link DragController#DRAG_ACTION_COPY}
139          */
onDragStart(DragSource source, Object info, int dragAction)140         void onDragStart(DragSource source, Object info, int dragAction);
141 
142         /**
143          * The drag has ended
144          */
onDragEnd()145         void onDragEnd();
146     }
147 
148     /**
149      * Used to create a new DragLayer from XML.
150      */
DragController(Launcher launcher)151     public DragController(Launcher launcher) {
152         Resources r = launcher.getResources();
153         mLauncher = launcher;
154         mHandler = new Handler();
155         mScrollZone = r.getDimensionPixelSize(R.dimen.scroll_zone);
156         mVelocityTracker = VelocityTracker.obtain();
157 
158         float density = r.getDisplayMetrics().density;
159         mFlingToDeleteThresholdVelocity =
160                 (int) (r.getInteger(R.integer.config_flingToDeleteMinVelocity) * density);
161         mIsRtl = Utilities.isRtl(r);
162     }
163 
dragging()164     public boolean dragging() {
165         return mDragging;
166     }
167 
168     /**
169      * Starts a drag.
170      *
171      * @param v The view that is being dragged
172      * @param bmp The bitmap that represents the view being dragged
173      * @param source An object representing where the drag originated
174      * @param dragInfo The data associated with the object that is being dragged
175      * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or
176      *        {@link #DRAG_ACTION_COPY}
177      * @param viewImageBounds the position of the image inside the view
178      * @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
179      *          Makes dragging feel more precise, e.g. you can clip out a transparent border
180      */
startDrag(View v, Bitmap bmp, DragSource source, Object dragInfo, Rect viewImageBounds, int dragAction, float initialDragViewScale)181     public void startDrag(View v, Bitmap bmp, DragSource source, Object dragInfo,
182             Rect viewImageBounds, int dragAction, float initialDragViewScale) {
183         int[] loc = mCoordinatesTemp;
184         mLauncher.getDragLayer().getLocationInDragLayer(v, loc);
185         int dragLayerX = loc[0] + viewImageBounds.left
186                 + (int) ((initialDragViewScale * bmp.getWidth() - bmp.getWidth()) / 2);
187         int dragLayerY = loc[1] + viewImageBounds.top
188                 + (int) ((initialDragViewScale * bmp.getHeight() - bmp.getHeight()) / 2);
189 
190         startDrag(bmp, dragLayerX, dragLayerY, source, dragInfo, dragAction, null,
191                 null, initialDragViewScale, false);
192 
193         if (dragAction == DRAG_ACTION_MOVE) {
194             v.setVisibility(View.GONE);
195         }
196     }
197 
198     /**
199      * Starts a drag.
200      *
201      * @param b The bitmap to display as the drag image.  It will be re-scaled to the
202      *          enlarged size.
203      * @param dragLayerX The x position in the DragLayer of the left-top of the bitmap.
204      * @param dragLayerY The y position in the DragLayer of the left-top of the bitmap.
205      * @param source An object representing where the drag originated
206      * @param dragInfo The data associated with the object that is being dragged
207      * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or
208      *        {@link #DRAG_ACTION_COPY}
209      * @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
210      *          Makes dragging feel more precise, e.g. you can clip out a transparent border
211      * @param accessible whether this drag should occur in accessibility mode
212      */
startDrag(Bitmap b, int dragLayerX, int dragLayerY, DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion, float initialDragViewScale, boolean accessible)213     public DragView startDrag(Bitmap b, int dragLayerX, int dragLayerY,
214             DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion,
215             float initialDragViewScale, boolean accessible) {
216         if (PROFILE_DRAWING_DURING_DRAG) {
217             android.os.Debug.startMethodTracing("Launcher");
218         }
219 
220         // Hide soft keyboard, if visible
221         if (mInputMethodManager == null) {
222             mInputMethodManager = (InputMethodManager)
223                     mLauncher.getSystemService(Context.INPUT_METHOD_SERVICE);
224         }
225         mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0);
226 
227         for (DragListener listener : mListeners) {
228             listener.onDragStart(source, dragInfo, dragAction);
229         }
230 
231         final int registrationX = mMotionDownX - dragLayerX;
232         final int registrationY = mMotionDownY - dragLayerY;
233 
234         final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;
235         final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;
236 
237         mDragging = true;
238         mIsAccessibleDrag = accessible;
239 
240         mDragObject = new DropTarget.DragObject();
241 
242         final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
243                 registrationY, 0, 0, b.getWidth(), b.getHeight(), initialDragViewScale);
244 
245         mDragObject.dragComplete = false;
246         if (mIsAccessibleDrag) {
247             // For an accessible drag, we assume the view is being dragged from the center.
248             mDragObject.xOffset = b.getWidth() / 2;
249             mDragObject.yOffset = b.getHeight() / 2;
250             mDragObject.accessibleDrag = true;
251         } else {
252             mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft);
253             mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop);
254             mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView);
255         }
256 
257         mDragObject.dragSource = source;
258         mDragObject.dragInfo = dragInfo;
259 
260         if (dragOffset != null) {
261             dragView.setDragVisualizeOffset(new Point(dragOffset));
262         }
263         if (dragRegion != null) {
264             dragView.setDragRegion(new Rect(dragRegion));
265         }
266 
267         mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
268         dragView.show(mMotionDownX, mMotionDownY);
269         handleMoveEvent(mMotionDownX, mMotionDownY);
270         return dragView;
271     }
272 
273     /**
274      * Draw the view into a bitmap.
275      */
getViewBitmap(View v)276     Bitmap getViewBitmap(View v) {
277         v.clearFocus();
278         v.setPressed(false);
279 
280         boolean willNotCache = v.willNotCacheDrawing();
281         v.setWillNotCacheDrawing(false);
282 
283         // Reset the drawing cache background color to fully transparent
284         // for the duration of this operation
285         int color = v.getDrawingCacheBackgroundColor();
286         v.setDrawingCacheBackgroundColor(0);
287         float alpha = v.getAlpha();
288         v.setAlpha(1.0f);
289 
290         if (color != 0) {
291             v.destroyDrawingCache();
292         }
293         v.buildDrawingCache();
294         Bitmap cacheBitmap = v.getDrawingCache();
295         if (cacheBitmap == null) {
296             Log.e(TAG, "failed getViewBitmap(" + v + ")", new RuntimeException());
297             return null;
298         }
299 
300         Bitmap bitmap = Bitmap.createBitmap(cacheBitmap);
301 
302         // Restore the view
303         v.destroyDrawingCache();
304         v.setAlpha(alpha);
305         v.setWillNotCacheDrawing(willNotCache);
306         v.setDrawingCacheBackgroundColor(color);
307 
308         return bitmap;
309     }
310 
311     /**
312      * Call this from a drag source view like this:
313      *
314      * <pre>
315      *  @Override
316      *  public boolean dispatchKeyEvent(KeyEvent event) {
317      *      return mDragController.dispatchKeyEvent(this, event)
318      *              || super.dispatchKeyEvent(event);
319      * </pre>
320      */
dispatchKeyEvent(KeyEvent event)321     public boolean dispatchKeyEvent(KeyEvent event) {
322         return mDragging;
323     }
324 
isDragging()325     public boolean isDragging() {
326         return mDragging;
327     }
328 
329     /**
330      * Stop dragging without dropping.
331      */
cancelDrag()332     public void cancelDrag() {
333         if (mDragging) {
334             if (mLastDropTarget != null) {
335                 mLastDropTarget.onDragExit(mDragObject);
336             }
337             mDragObject.deferDragViewCleanupPostAnimation = false;
338             mDragObject.cancelled = true;
339             mDragObject.dragComplete = true;
340             mDragObject.dragSource.onDropCompleted(null, mDragObject, false, false);
341         }
342         endDrag();
343     }
344 
onAppsRemoved(final HashSet<String> packageNames, HashSet<ComponentName> cns)345     public void onAppsRemoved(final HashSet<String> packageNames, HashSet<ComponentName> cns) {
346         // Cancel the current drag if we are removing an app that we are dragging
347         if (mDragObject != null) {
348             Object rawDragInfo = mDragObject.dragInfo;
349             if (rawDragInfo instanceof ShortcutInfo) {
350                 ShortcutInfo dragInfo = (ShortcutInfo) rawDragInfo;
351                 for (ComponentName componentName : cns) {
352                     if (dragInfo.intent != null) {
353                         ComponentName cn = dragInfo.intent.getComponent();
354                         boolean isSameComponent = cn != null && (cn.equals(componentName) ||
355                                 packageNames.contains(cn.getPackageName()));
356                         if (isSameComponent) {
357                             cancelDrag();
358                             return;
359                         }
360                     }
361                 }
362             }
363         }
364     }
365 
endDrag()366     private void endDrag() {
367         if (mDragging) {
368             mDragging = false;
369             mIsAccessibleDrag = false;
370             clearScrollRunnable();
371             boolean isDeferred = false;
372             if (mDragObject.dragView != null) {
373                 isDeferred = mDragObject.deferDragViewCleanupPostAnimation;
374                 if (!isDeferred) {
375                     mDragObject.dragView.remove();
376                 }
377                 mDragObject.dragView = null;
378             }
379 
380             // Only end the drag if we are not deferred
381             if (!isDeferred) {
382                 for (DragListener listener : new ArrayList<>(mListeners)) {
383                     listener.onDragEnd();
384                 }
385             }
386         }
387 
388         releaseVelocityTracker();
389     }
390 
391     /**
392      * This only gets called as a result of drag view cleanup being deferred in endDrag();
393      */
onDeferredEndDrag(DragView dragView)394     void onDeferredEndDrag(DragView dragView) {
395         dragView.remove();
396 
397         if (mDragObject.deferDragViewCleanupPostAnimation) {
398             // If we skipped calling onDragEnd() before, do it now
399             for (DragListener listener : new ArrayList<>(mListeners)) {
400                 listener.onDragEnd();
401             }
402         }
403     }
404 
onDeferredEndFling(DropTarget.DragObject d)405     public void onDeferredEndFling(DropTarget.DragObject d) {
406         d.dragSource.onFlingToDeleteCompleted();
407     }
408 
409     /**
410      * Clamps the position to the drag layer bounds.
411      */
getClampedDragLayerPos(float x, float y)412     private int[] getClampedDragLayerPos(float x, float y) {
413         mLauncher.getDragLayer().getLocalVisibleRect(mDragLayerRect);
414         mTmpPoint[0] = (int) Math.max(mDragLayerRect.left, Math.min(x, mDragLayerRect.right - 1));
415         mTmpPoint[1] = (int) Math.max(mDragLayerRect.top, Math.min(y, mDragLayerRect.bottom - 1));
416         return mTmpPoint;
417     }
418 
getLastGestureUpTime()419     long getLastGestureUpTime() {
420         if (mDragging) {
421             return System.currentTimeMillis();
422         } else {
423             return mLastTouchUpTime;
424         }
425     }
426 
resetLastGestureUpTime()427     void resetLastGestureUpTime() {
428         mLastTouchUpTime = -1;
429     }
430 
431     /**
432      * Call this from a drag source view.
433      */
onInterceptTouchEvent(MotionEvent ev)434     public boolean onInterceptTouchEvent(MotionEvent ev) {
435         @SuppressWarnings("all") // suppress dead code warning
436         final boolean debug = false;
437         if (debug) {
438             Log.d(Launcher.TAG, "DragController.onInterceptTouchEvent " + ev + " mDragging="
439                     + mDragging);
440         }
441 
442         if (mIsAccessibleDrag) {
443             return false;
444         }
445 
446         // Update the velocity tracker
447         acquireVelocityTrackerAndAddMovement(ev);
448 
449         final int action = ev.getAction();
450         final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
451         final int dragLayerX = dragLayerPos[0];
452         final int dragLayerY = dragLayerPos[1];
453 
454         switch (action) {
455             case MotionEvent.ACTION_MOVE:
456                 break;
457             case MotionEvent.ACTION_DOWN:
458                 // Remember location of down touch
459                 mMotionDownX = dragLayerX;
460                 mMotionDownY = dragLayerY;
461                 mLastDropTarget = null;
462                 break;
463             case MotionEvent.ACTION_UP:
464                 mLastTouchUpTime = System.currentTimeMillis();
465                 if (mDragging) {
466                     PointF vec = isFlingingToDelete(mDragObject.dragSource);
467                     if (!DeleteDropTarget.supportsDrop(mDragObject.dragInfo)) {
468                         vec = null;
469                     }
470                     if (vec != null) {
471                         dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec);
472                     } else {
473                         drop(dragLayerX, dragLayerY);
474                     }
475                 }
476                 endDrag();
477                 break;
478             case MotionEvent.ACTION_CANCEL:
479                 cancelDrag();
480                 break;
481         }
482 
483         return mDragging;
484     }
485 
486     /**
487      * Sets the view that should handle move events.
488      */
setMoveTarget(View view)489     void setMoveTarget(View view) {
490         mMoveTarget = view;
491     }
492 
dispatchUnhandledMove(View focused, int direction)493     public boolean dispatchUnhandledMove(View focused, int direction) {
494         return mMoveTarget != null && mMoveTarget.dispatchUnhandledMove(focused, direction);
495     }
496 
clearScrollRunnable()497     private void clearScrollRunnable() {
498         mHandler.removeCallbacks(mScrollRunnable);
499         if (mScrollState == SCROLL_WAITING_IN_ZONE) {
500             mScrollState = SCROLL_OUTSIDE_ZONE;
501             mScrollRunnable.setDirection(SCROLL_RIGHT);
502             mDragScroller.onExitScrollArea();
503             mLauncher.getDragLayer().onExitScrollArea();
504         }
505     }
506 
handleMoveEvent(int x, int y)507     private void handleMoveEvent(int x, int y) {
508         mDragObject.dragView.move(x, y);
509 
510         // Drop on someone?
511         final int[] coordinates = mCoordinatesTemp;
512         DropTarget dropTarget = findDropTarget(x, y, coordinates);
513         mDragObject.x = coordinates[0];
514         mDragObject.y = coordinates[1];
515         checkTouchMove(dropTarget);
516 
517         // Check if we are hovering over the scroll areas
518         mDistanceSinceScroll += Math.hypot(mLastTouch[0] - x, mLastTouch[1] - y);
519         mLastTouch[0] = x;
520         mLastTouch[1] = y;
521         checkScrollState(x, y);
522     }
523 
forceTouchMove()524     public void forceTouchMove() {
525         int[] dummyCoordinates = mCoordinatesTemp;
526         DropTarget dropTarget = findDropTarget(mLastTouch[0], mLastTouch[1], dummyCoordinates);
527         mDragObject.x = dummyCoordinates[0];
528         mDragObject.y = dummyCoordinates[1];
529         checkTouchMove(dropTarget);
530     }
531 
checkTouchMove(DropTarget dropTarget)532     private void checkTouchMove(DropTarget dropTarget) {
533         if (dropTarget != null) {
534             if (mLastDropTarget != dropTarget) {
535                 if (mLastDropTarget != null) {
536                     mLastDropTarget.onDragExit(mDragObject);
537                 }
538                 dropTarget.onDragEnter(mDragObject);
539             }
540             dropTarget.onDragOver(mDragObject);
541         } else {
542             if (mLastDropTarget != null) {
543                 mLastDropTarget.onDragExit(mDragObject);
544             }
545         }
546         mLastDropTarget = dropTarget;
547     }
548 
checkScrollState(int x, int y)549     @Thunk void checkScrollState(int x, int y) {
550         final int slop = ViewConfiguration.get(mLauncher).getScaledWindowTouchSlop();
551         final int delay = mDistanceSinceScroll < slop ? RESCROLL_DELAY : SCROLL_DELAY;
552         final DragLayer dragLayer = mLauncher.getDragLayer();
553         final int forwardDirection = mIsRtl ? SCROLL_RIGHT : SCROLL_LEFT;
554         final int backwardsDirection = mIsRtl ? SCROLL_LEFT : SCROLL_RIGHT;
555 
556         if (x < mScrollZone) {
557             if (mScrollState == SCROLL_OUTSIDE_ZONE) {
558                 mScrollState = SCROLL_WAITING_IN_ZONE;
559                 if (mDragScroller.onEnterScrollArea(x, y, forwardDirection)) {
560                     dragLayer.onEnterScrollArea(forwardDirection);
561                     mScrollRunnable.setDirection(forwardDirection);
562                     mHandler.postDelayed(mScrollRunnable, delay);
563                 }
564             }
565         } else if (x > mScrollView.getWidth() - mScrollZone) {
566             if (mScrollState == SCROLL_OUTSIDE_ZONE) {
567                 mScrollState = SCROLL_WAITING_IN_ZONE;
568                 if (mDragScroller.onEnterScrollArea(x, y, backwardsDirection)) {
569                     dragLayer.onEnterScrollArea(backwardsDirection);
570                     mScrollRunnable.setDirection(backwardsDirection);
571                     mHandler.postDelayed(mScrollRunnable, delay);
572                 }
573             }
574         } else {
575             clearScrollRunnable();
576         }
577     }
578 
579     /**
580      * Call this from a drag source view.
581      */
582     public boolean onTouchEvent(MotionEvent ev) {
583         if (!mDragging || mIsAccessibleDrag) {
584             return false;
585         }
586 
587         // Update the velocity tracker
588         acquireVelocityTrackerAndAddMovement(ev);
589 
590         final int action = ev.getAction();
591         final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
592         final int dragLayerX = dragLayerPos[0];
593         final int dragLayerY = dragLayerPos[1];
594 
595         switch (action) {
596         case MotionEvent.ACTION_DOWN:
597             // Remember where the motion event started
598             mMotionDownX = dragLayerX;
599             mMotionDownY = dragLayerY;
600 
601             if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) {
602                 mScrollState = SCROLL_WAITING_IN_ZONE;
603                 mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
604             } else {
605                 mScrollState = SCROLL_OUTSIDE_ZONE;
606             }
607             handleMoveEvent(dragLayerX, dragLayerY);
608             break;
609         case MotionEvent.ACTION_MOVE:
610             handleMoveEvent(dragLayerX, dragLayerY);
611             break;
612         case MotionEvent.ACTION_UP:
613             // Ensure that we've processed a move event at the current pointer location.
614             handleMoveEvent(dragLayerX, dragLayerY);
615             mHandler.removeCallbacks(mScrollRunnable);
616 
617             if (mDragging) {
618                 PointF vec = isFlingingToDelete(mDragObject.dragSource);
619                 if (!DeleteDropTarget.supportsDrop(mDragObject.dragInfo)) {
620                     vec = null;
621                 }
622                 if (vec != null) {
623                     dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec);
624                 } else {
625                     drop(dragLayerX, dragLayerY);
626                 }
627             }
628             endDrag();
629             break;
630         case MotionEvent.ACTION_CANCEL:
631             mHandler.removeCallbacks(mScrollRunnable);
632             cancelDrag();
633             break;
634         }
635 
636         return true;
637     }
638 
639     /**
640      * Since accessible drag and drop won't cause the same sequence of touch events, we manually
641      * inject the appropriate state.
642      */
643     public void prepareAccessibleDrag(int x, int y) {
644         mMotionDownX = x;
645         mMotionDownY = y;
646         mLastDropTarget = null;
647     }
648 
649     /**
650      * As above, since accessible drag and drop won't cause the same sequence of touch events,
651      * we manually ensure appropriate drag and drop events get emulated for accessible drag.
652      */
653     public void completeAccessibleDrag(int[] location) {
654         final int[] coordinates = mCoordinatesTemp;
655 
656         // We make sure that we prime the target for drop.
657         DropTarget dropTarget = findDropTarget(location[0], location[1], coordinates);
658         mDragObject.x = coordinates[0];
659         mDragObject.y = coordinates[1];
660         checkTouchMove(dropTarget);
661 
662         dropTarget.prepareAccessibilityDrop();
663         // Perform the drop
664         drop(location[0], location[1]);
665         endDrag();
666     }
667 
668     /**
669      * Determines whether the user flung the current item to delete it.
670      *
671      * @return the vector at which the item was flung, or null if no fling was detected.
672      */
673     private PointF isFlingingToDelete(DragSource source) {
674         if (mFlingToDeleteDropTarget == null) return null;
675         if (!source.supportsFlingToDelete()) return null;
676 
677         ViewConfiguration config = ViewConfiguration.get(mLauncher);
678         mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity());
679 
680         if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) {
681             // Do a quick dot product test to ensure that we are flinging upwards
682             PointF vel = new PointF(mVelocityTracker.getXVelocity(),
683                     mVelocityTracker.getYVelocity());
684             PointF upVec = new PointF(0f, -1f);
685             float theta = (float) Math.acos(((vel.x * upVec.x) + (vel.y * upVec.y)) /
686                     (vel.length() * upVec.length()));
687             if (theta <= Math.toRadians(MAX_FLING_DEGREES)) {
688                 return vel;
689             }
690         }
691         return null;
692     }
693 
694     private void dropOnFlingToDeleteTarget(float x, float y, PointF vel) {
695         final int[] coordinates = mCoordinatesTemp;
696 
697         mDragObject.x = coordinates[0];
698         mDragObject.y = coordinates[1];
699 
700         // Clean up dragging on the target if it's not the current fling delete target otherwise,
701         // start dragging to it.
702         if (mLastDropTarget != null && mFlingToDeleteDropTarget != mLastDropTarget) {
703             mLastDropTarget.onDragExit(mDragObject);
704         }
705 
706         // Drop onto the fling-to-delete target
707         boolean accepted = false;
708         mFlingToDeleteDropTarget.onDragEnter(mDragObject);
709         // We must set dragComplete to true _only_ after we "enter" the fling-to-delete target for
710         // "drop"
711         mDragObject.dragComplete = true;
712         mFlingToDeleteDropTarget.onDragExit(mDragObject);
713         if (mFlingToDeleteDropTarget.acceptDrop(mDragObject)) {
714             mFlingToDeleteDropTarget.onFlingToDelete(mDragObject, vel);
715             accepted = true;
716         }
717         mDragObject.dragSource.onDropCompleted((View) mFlingToDeleteDropTarget, mDragObject, true,
718                 accepted);
719     }
720 
721     private void drop(float x, float y) {
722         final int[] coordinates = mCoordinatesTemp;
723         final DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);
724 
725         mDragObject.x = coordinates[0];
726         mDragObject.y = coordinates[1];
727         boolean accepted = false;
728         if (dropTarget != null) {
729             mDragObject.dragComplete = true;
730             dropTarget.onDragExit(mDragObject);
731             if (dropTarget.acceptDrop(mDragObject)) {
732                 dropTarget.onDrop(mDragObject);
733                 accepted = true;
734             }
735         }
736         mDragObject.dragSource.onDropCompleted((View) dropTarget, mDragObject, false, accepted);
737     }
738 
739     private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) {
740         final Rect r = mRectTemp;
741 
742         final ArrayList<DropTarget> dropTargets = mDropTargets;
743         final int count = dropTargets.size();
744         for (int i=count-1; i>=0; i--) {
745             DropTarget target = dropTargets.get(i);
746             if (!target.isDropEnabled())
747                 continue;
748 
749             target.getHitRectRelativeToDragLayer(r);
750 
751             mDragObject.x = x;
752             mDragObject.y = y;
753             if (r.contains(x, y)) {
754 
755                 dropCoordinates[0] = x;
756                 dropCoordinates[1] = y;
757                 mLauncher.getDragLayer().mapCoordInSelfToDescendent((View) target, dropCoordinates);
758 
759                 return target;
760             }
761         }
762         return null;
763     }
764 
765     public void setDragScoller(DragScroller scroller) {
766         mDragScroller = scroller;
767     }
768 
769     public void setWindowToken(IBinder token) {
770         mWindowToken = token;
771     }
772 
773     /**
774      * Sets the drag listner which will be notified when a drag starts or ends.
775      */
776     public void addDragListener(DragListener l) {
777         mListeners.add(l);
778     }
779 
780     /**
781      * Remove a previously installed drag listener.
782      */
783     public void removeDragListener(DragListener l) {
784         mListeners.remove(l);
785     }
786 
787     /**
788      * Add a DropTarget to the list of potential places to receive drop events.
789      */
790     public void addDropTarget(DropTarget target) {
791         mDropTargets.add(target);
792     }
793 
794     /**
795      * Don't send drop events to <em>target</em> any more.
796      */
797     public void removeDropTarget(DropTarget target) {
798         mDropTargets.remove(target);
799     }
800 
801     /**
802      * Sets the current fling-to-delete drop target.
803      */
804     public void setFlingToDeleteDropTarget(DropTarget target) {
805         mFlingToDeleteDropTarget = target;
806     }
807 
808     private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
809         if (mVelocityTracker == null) {
810             mVelocityTracker = VelocityTracker.obtain();
811         }
812         mVelocityTracker.addMovement(ev);
813     }
814 
815     private void releaseVelocityTracker() {
816         if (mVelocityTracker != null) {
817             mVelocityTracker.recycle();
818             mVelocityTracker = null;
819         }
820     }
821 
822     /**
823      * Set which view scrolls for touch events near the edge of the screen.
824      */
825     public void setScrollView(View v) {
826         mScrollView = v;
827     }
828 
829     DragView getDragView() {
830         return mDragObject.dragView;
831     }
832 
833     private class ScrollRunnable implements Runnable {
834         private int mDirection;
835 
836         ScrollRunnable() {
837         }
838 
839         public void run() {
840             if (mDragScroller != null) {
841                 if (mDirection == SCROLL_LEFT) {
842                     mDragScroller.scrollLeft();
843                 } else {
844                     mDragScroller.scrollRight();
845                 }
846                 mScrollState = SCROLL_OUTSIDE_ZONE;
847                 mDistanceSinceScroll = 0;
848                 mDragScroller.onExitScrollArea();
849                 mLauncher.getDragLayer().onExitScrollArea();
850 
851                 if (isDragging()) {
852                     // Check the scroll again so that we can requeue the scroller if necessary
853                     checkScrollState(mLastTouch[0], mLastTouch[1]);
854                 }
855             }
856         }
857 
858         void setDirection(int direction) {
859             mDirection = direction;
860         }
861     }
862 }
863