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