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