• 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, Point extraPadding, float initialDragViewScale)177     public void startDrag(View v, Bitmap bmp, DragSource source, Object dragInfo, int dragAction,
178             Point extraPadding, float initialDragViewScale) {
179         int[] loc = mCoordinatesTemp;
180         mLauncher.getDragLayer().getLocationInDragLayer(v, loc);
181         int viewExtraPaddingLeft = extraPadding != null ? extraPadding.x : 0;
182         int viewExtraPaddingTop = extraPadding != null ? extraPadding.y : 0;
183         int dragLayerX = loc[0] + v.getPaddingLeft() + viewExtraPaddingLeft +
184                 (int) ((initialDragViewScale * bmp.getWidth() - bmp.getWidth()) / 2);
185         int dragLayerY = loc[1] + v.getPaddingTop() + viewExtraPaddingTop +
186                 (int) ((initialDragViewScale * bmp.getHeight() - bmp.getHeight()) / 2);
187 
188         startDrag(bmp, dragLayerX, dragLayerY, source, dragInfo, dragAction, null,
189                 null, initialDragViewScale);
190 
191         if (dragAction == DRAG_ACTION_MOVE) {
192             v.setVisibility(View.GONE);
193         }
194     }
195 
196     /**
197      * Starts a drag.
198      *
199      * @param b The bitmap to display as the drag image.  It will be re-scaled to the
200      *          enlarged size.
201      * @param dragLayerX The x position in the DragLayer of the left-top of the bitmap.
202      * @param dragLayerY The y position in the DragLayer of the left-top of the bitmap.
203      * @param source An object representing where the drag originated
204      * @param dragInfo The data associated with the object that is being dragged
205      * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or
206      *        {@link #DRAG_ACTION_COPY}
207      * @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
208      *          Makes dragging feel more precise, e.g. you can clip out a transparent border
209      */
startDrag(Bitmap b, int dragLayerX, int dragLayerY, DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion, float initialDragViewScale)210     public void startDrag(Bitmap b, int dragLayerX, int dragLayerY,
211             DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion,
212             float initialDragViewScale) {
213         if (PROFILE_DRAWING_DURING_DRAG) {
214             android.os.Debug.startMethodTracing("Launcher");
215         }
216 
217         // Hide soft keyboard, if visible
218         if (mInputMethodManager == null) {
219             mInputMethodManager = (InputMethodManager)
220                     mLauncher.getSystemService(Context.INPUT_METHOD_SERVICE);
221         }
222         mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0);
223 
224         for (DragListener listener : mListeners) {
225             listener.onDragStart(source, dragInfo, dragAction);
226         }
227 
228         final int registrationX = mMotionDownX - dragLayerX;
229         final int registrationY = mMotionDownY - dragLayerY;
230 
231         final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;
232         final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;
233 
234         mDragging = true;
235 
236         mDragObject = new DropTarget.DragObject();
237 
238         mDragObject.dragComplete = false;
239         mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft);
240         mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop);
241         mDragObject.dragSource = source;
242         mDragObject.dragInfo = dragInfo;
243 
244         mVibrator.vibrate(VIBRATE_DURATION);
245 
246         final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
247                 registrationY, 0, 0, b.getWidth(), b.getHeight(), initialDragViewScale);
248 
249         if (dragOffset != null) {
250             dragView.setDragVisualizeOffset(new Point(dragOffset));
251         }
252         if (dragRegion != null) {
253             dragView.setDragRegion(new Rect(dragRegion));
254         }
255 
256         dragView.show(mMotionDownX, mMotionDownY);
257         handleMoveEvent(mMotionDownX, mMotionDownY);
258     }
259 
260     /**
261      * Draw the view into a bitmap.
262      */
getViewBitmap(View v)263     Bitmap getViewBitmap(View v) {
264         v.clearFocus();
265         v.setPressed(false);
266 
267         boolean willNotCache = v.willNotCacheDrawing();
268         v.setWillNotCacheDrawing(false);
269 
270         // Reset the drawing cache background color to fully transparent
271         // for the duration of this operation
272         int color = v.getDrawingCacheBackgroundColor();
273         v.setDrawingCacheBackgroundColor(0);
274         float alpha = v.getAlpha();
275         v.setAlpha(1.0f);
276 
277         if (color != 0) {
278             v.destroyDrawingCache();
279         }
280         v.buildDrawingCache();
281         Bitmap cacheBitmap = v.getDrawingCache();
282         if (cacheBitmap == null) {
283             Log.e(TAG, "failed getViewBitmap(" + v + ")", new RuntimeException());
284             return null;
285         }
286 
287         Bitmap bitmap = Bitmap.createBitmap(cacheBitmap);
288 
289         // Restore the view
290         v.destroyDrawingCache();
291         v.setAlpha(alpha);
292         v.setWillNotCacheDrawing(willNotCache);
293         v.setDrawingCacheBackgroundColor(color);
294 
295         return bitmap;
296     }
297 
298     /**
299      * Call this from a drag source view like this:
300      *
301      * <pre>
302      *  @Override
303      *  public boolean dispatchKeyEvent(KeyEvent event) {
304      *      return mDragController.dispatchKeyEvent(this, event)
305      *              || super.dispatchKeyEvent(event);
306      * </pre>
307      */
dispatchKeyEvent(KeyEvent event)308     public boolean dispatchKeyEvent(KeyEvent event) {
309         return mDragging;
310     }
311 
isDragging()312     public boolean isDragging() {
313         return mDragging;
314     }
315 
316     /**
317      * Stop dragging without dropping.
318      */
cancelDrag()319     public void cancelDrag() {
320         if (mDragging) {
321             if (mLastDropTarget != null) {
322                 mLastDropTarget.onDragExit(mDragObject);
323             }
324             mDragObject.deferDragViewCleanupPostAnimation = false;
325             mDragObject.cancelled = true;
326             mDragObject.dragComplete = true;
327             mDragObject.dragSource.onDropCompleted(null, mDragObject, false, false);
328         }
329         endDrag();
330     }
onAppsRemoved(ArrayList<ApplicationInfo> appInfos, Context context)331     public void onAppsRemoved(ArrayList<ApplicationInfo> appInfos, Context context) {
332         // Cancel the current drag if we are removing an app that we are dragging
333         if (mDragObject != null) {
334             Object rawDragInfo = mDragObject.dragInfo;
335             if (rawDragInfo instanceof ShortcutInfo) {
336                 ShortcutInfo dragInfo = (ShortcutInfo) rawDragInfo;
337                 for (ApplicationInfo info : appInfos) {
338                     // Added null checks to prevent NPE we've seen in the wild
339                     if (dragInfo != null &&
340                         dragInfo.intent != null) {
341                         boolean isSameComponent =
342                                 dragInfo.intent.getComponent().equals(info.componentName);
343                         if (isSameComponent) {
344                             cancelDrag();
345                             return;
346                         }
347                     }
348                 }
349             }
350         }
351     }
352 
endDrag()353     private void endDrag() {
354         if (mDragging) {
355             mDragging = false;
356             clearScrollRunnable();
357             boolean isDeferred = false;
358             if (mDragObject.dragView != null) {
359                 isDeferred = mDragObject.deferDragViewCleanupPostAnimation;
360                 if (!isDeferred) {
361                     mDragObject.dragView.remove();
362                 }
363                 mDragObject.dragView = null;
364             }
365 
366             // Only end the drag if we are not deferred
367             if (!isDeferred) {
368                 for (DragListener listener : mListeners) {
369                     listener.onDragEnd();
370                 }
371             }
372         }
373 
374         releaseVelocityTracker();
375     }
376 
377     /**
378      * This only gets called as a result of drag view cleanup being deferred in endDrag();
379      */
onDeferredEndDrag(DragView dragView)380     void onDeferredEndDrag(DragView dragView) {
381         dragView.remove();
382 
383         // If we skipped calling onDragEnd() before, do it now
384         for (DragListener listener : mListeners) {
385             listener.onDragEnd();
386         }
387     }
388 
onDeferredEndFling(DropTarget.DragObject d)389     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     long getLastGestureUpTime() {
404         if (mDragging) {
405             return System.currentTimeMillis();
406         } else {
407             return mLastTouchUpTime;
408         }
409     }
410 
resetLastGestureUpTime()411     void resetLastGestureUpTime() {
412         mLastTouchUpTime = -1;
413     }
414 
415     /**
416      * Call this from a drag source view.
417      */
onInterceptTouchEvent(MotionEvent ev)418     public boolean onInterceptTouchEvent(MotionEvent ev) {
419         @SuppressWarnings("all") // suppress dead code warning
420         final boolean debug = false;
421         if (debug) {
422             Log.d(Launcher.TAG, "DragController.onInterceptTouchEvent " + ev + " mDragging="
423                     + mDragging);
424         }
425 
426         // Update the velocity tracker
427         acquireVelocityTrackerAndAddMovement(ev);
428 
429         final int action = ev.getAction();
430         final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
431         final int dragLayerX = dragLayerPos[0];
432         final int dragLayerY = dragLayerPos[1];
433 
434         switch (action) {
435             case MotionEvent.ACTION_MOVE:
436                 break;
437             case MotionEvent.ACTION_DOWN:
438                 // Remember location of down touch
439                 mMotionDownX = dragLayerX;
440                 mMotionDownY = dragLayerY;
441                 mLastDropTarget = null;
442                 break;
443             case MotionEvent.ACTION_UP:
444                 mLastTouchUpTime = System.currentTimeMillis();
445                 if (mDragging) {
446                     PointF vec = isFlingingToDelete(mDragObject.dragSource);
447                     if (vec != null) {
448                         dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec);
449                     } else {
450                         drop(dragLayerX, dragLayerY);
451                     }
452                 }
453                 endDrag();
454                 break;
455             case MotionEvent.ACTION_CANCEL:
456                 cancelDrag();
457                 break;
458         }
459 
460         return mDragging;
461     }
462 
463     /**
464      * Sets the view that should handle move events.
465      */
setMoveTarget(View view)466     void setMoveTarget(View view) {
467         mMoveTarget = view;
468     }
469 
dispatchUnhandledMove(View focused, int direction)470     public boolean dispatchUnhandledMove(View focused, int direction) {
471         return mMoveTarget != null && mMoveTarget.dispatchUnhandledMove(focused, direction);
472     }
473 
clearScrollRunnable()474     private void clearScrollRunnable() {
475         mHandler.removeCallbacks(mScrollRunnable);
476         if (mScrollState == SCROLL_WAITING_IN_ZONE) {
477             mScrollState = SCROLL_OUTSIDE_ZONE;
478             mScrollRunnable.setDirection(SCROLL_RIGHT);
479             mDragScroller.onExitScrollArea();
480             mLauncher.getDragLayer().onExitScrollArea();
481         }
482     }
483 
handleMoveEvent(int x, int y)484     private void handleMoveEvent(int x, int y) {
485         mDragObject.dragView.move(x, y);
486 
487         // Drop on someone?
488         final int[] coordinates = mCoordinatesTemp;
489         DropTarget dropTarget = findDropTarget(x, y, coordinates);
490         mDragObject.x = coordinates[0];
491         mDragObject.y = coordinates[1];
492         checkTouchMove(dropTarget);
493 
494         // Check if we are hovering over the scroll areas
495         mDistanceSinceScroll +=
496             Math.sqrt(Math.pow(mLastTouch[0] - x, 2) + Math.pow(mLastTouch[1] - y, 2));
497         mLastTouch[0] = x;
498         mLastTouch[1] = y;
499         checkScrollState(x, y);
500     }
501 
forceTouchMove()502     public void forceTouchMove() {
503         int[] dummyCoordinates = mCoordinatesTemp;
504         DropTarget dropTarget = findDropTarget(mLastTouch[0], mLastTouch[1], dummyCoordinates);
505         checkTouchMove(dropTarget);
506     }
507 
checkTouchMove(DropTarget dropTarget)508     private void checkTouchMove(DropTarget dropTarget) {
509         if (dropTarget != null) {
510             DropTarget delegate = dropTarget.getDropTargetDelegate(mDragObject);
511             if (delegate != null) {
512                 dropTarget = delegate;
513             }
514 
515             if (mLastDropTarget != dropTarget) {
516                 if (mLastDropTarget != null) {
517                     mLastDropTarget.onDragExit(mDragObject);
518                 }
519                 dropTarget.onDragEnter(mDragObject);
520             }
521             dropTarget.onDragOver(mDragObject);
522         } else {
523             if (mLastDropTarget != null) {
524                 mLastDropTarget.onDragExit(mDragObject);
525             }
526         }
527         mLastDropTarget = dropTarget;
528     }
529 
checkScrollState(int x, int y)530     private void checkScrollState(int x, int y) {
531         final int slop = ViewConfiguration.get(mLauncher).getScaledWindowTouchSlop();
532         final int delay = mDistanceSinceScroll < slop ? RESCROLL_DELAY : SCROLL_DELAY;
533         final DragLayer dragLayer = mLauncher.getDragLayer();
534         final boolean isRtl = (dragLayer.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
535         final int forwardDirection = isRtl ? SCROLL_RIGHT : SCROLL_LEFT;
536         final int backwardsDirection = isRtl ? SCROLL_LEFT : SCROLL_RIGHT;
537 
538         if (x < mScrollZone) {
539             if (mScrollState == SCROLL_OUTSIDE_ZONE) {
540                 mScrollState = SCROLL_WAITING_IN_ZONE;
541                 if (mDragScroller.onEnterScrollArea(x, y, forwardDirection)) {
542                     dragLayer.onEnterScrollArea(forwardDirection);
543                     mScrollRunnable.setDirection(forwardDirection);
544                     mHandler.postDelayed(mScrollRunnable, delay);
545                 }
546             }
547         } else if (x > mScrollView.getWidth() - mScrollZone) {
548             if (mScrollState == SCROLL_OUTSIDE_ZONE) {
549                 mScrollState = SCROLL_WAITING_IN_ZONE;
550                 if (mDragScroller.onEnterScrollArea(x, y, backwardsDirection)) {
551                     dragLayer.onEnterScrollArea(backwardsDirection);
552                     mScrollRunnable.setDirection(backwardsDirection);
553                     mHandler.postDelayed(mScrollRunnable, delay);
554                 }
555             }
556         } else {
557             clearScrollRunnable();
558         }
559     }
560 
561     /**
562      * Call this from a drag source view.
563      */
564     public boolean onTouchEvent(MotionEvent ev) {
565         if (!mDragging) {
566             return false;
567         }
568 
569         // Update the velocity tracker
570         acquireVelocityTrackerAndAddMovement(ev);
571 
572         final int action = ev.getAction();
573         final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
574         final int dragLayerX = dragLayerPos[0];
575         final int dragLayerY = dragLayerPos[1];
576 
577         switch (action) {
578         case MotionEvent.ACTION_DOWN:
579             // Remember where the motion event started
580             mMotionDownX = dragLayerX;
581             mMotionDownY = dragLayerY;
582 
583             if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) {
584                 mScrollState = SCROLL_WAITING_IN_ZONE;
585                 mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
586             } else {
587                 mScrollState = SCROLL_OUTSIDE_ZONE;
588             }
589             break;
590         case MotionEvent.ACTION_MOVE:
591             handleMoveEvent(dragLayerX, dragLayerY);
592             break;
593         case MotionEvent.ACTION_UP:
594             // Ensure that we've processed a move event at the current pointer location.
595             handleMoveEvent(dragLayerX, dragLayerY);
596             mHandler.removeCallbacks(mScrollRunnable);
597 
598             if (mDragging) {
599                 PointF vec = isFlingingToDelete(mDragObject.dragSource);
600                 if (vec != null) {
601                     dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec);
602                 } else {
603                     drop(dragLayerX, dragLayerY);
604                 }
605             }
606             endDrag();
607             break;
608         case MotionEvent.ACTION_CANCEL:
609             mHandler.removeCallbacks(mScrollRunnable);
610             cancelDrag();
611             break;
612         }
613 
614         return true;
615     }
616 
617     /**
618      * Determines whether the user flung the current item to delete it.
619      *
620      * @return the vector at which the item was flung, or null if no fling was detected.
621      */
622     private PointF isFlingingToDelete(DragSource source) {
623         if (mFlingToDeleteDropTarget == null) return null;
624         if (!source.supportsFlingToDelete()) return null;
625 
626         ViewConfiguration config = ViewConfiguration.get(mLauncher);
627         mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity());
628 
629         if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) {
630             // Do a quick dot product test to ensure that we are flinging upwards
631             PointF vel = new PointF(mVelocityTracker.getXVelocity(),
632                     mVelocityTracker.getYVelocity());
633             PointF upVec = new PointF(0f, -1f);
634             float theta = (float) Math.acos(((vel.x * upVec.x) + (vel.y * upVec.y)) /
635                     (vel.length() * upVec.length()));
636             if (theta <= Math.toRadians(MAX_FLING_DEGREES)) {
637                 return vel;
638             }
639         }
640         return null;
641     }
642 
643     private void dropOnFlingToDeleteTarget(float x, float y, PointF vel) {
644         final int[] coordinates = mCoordinatesTemp;
645 
646         mDragObject.x = coordinates[0];
647         mDragObject.y = coordinates[1];
648 
649         // Clean up dragging on the target if it's not the current fling delete target otherwise,
650         // start dragging to it.
651         if (mLastDropTarget != null && mFlingToDeleteDropTarget != mLastDropTarget) {
652             mLastDropTarget.onDragExit(mDragObject);
653         }
654 
655         // Drop onto the fling-to-delete target
656         boolean accepted = false;
657         mFlingToDeleteDropTarget.onDragEnter(mDragObject);
658         // We must set dragComplete to true _only_ after we "enter" the fling-to-delete target for
659         // "drop"
660         mDragObject.dragComplete = true;
661         mFlingToDeleteDropTarget.onDragExit(mDragObject);
662         if (mFlingToDeleteDropTarget.acceptDrop(mDragObject)) {
663             mFlingToDeleteDropTarget.onFlingToDelete(mDragObject, mDragObject.x, mDragObject.y,
664                     vel);
665             accepted = true;
666         }
667         mDragObject.dragSource.onDropCompleted((View) mFlingToDeleteDropTarget, mDragObject, true,
668                 accepted);
669     }
670 
671     private void drop(float x, float y) {
672         final int[] coordinates = mCoordinatesTemp;
673         final DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);
674 
675         mDragObject.x = coordinates[0];
676         mDragObject.y = coordinates[1];
677         boolean accepted = false;
678         if (dropTarget != null) {
679             mDragObject.dragComplete = true;
680             dropTarget.onDragExit(mDragObject);
681             if (dropTarget.acceptDrop(mDragObject)) {
682                 dropTarget.onDrop(mDragObject);
683                 accepted = true;
684             }
685         }
686         mDragObject.dragSource.onDropCompleted((View) dropTarget, mDragObject, false, accepted);
687     }
688 
689     private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) {
690         final Rect r = mRectTemp;
691 
692         final ArrayList<DropTarget> dropTargets = mDropTargets;
693         final int count = dropTargets.size();
694         for (int i=count-1; i>=0; i--) {
695             DropTarget target = dropTargets.get(i);
696             if (!target.isDropEnabled())
697                 continue;
698 
699             target.getHitRect(r);
700 
701             // Convert the hit rect to DragLayer coordinates
702             target.getLocationInDragLayer(dropCoordinates);
703             r.offset(dropCoordinates[0] - target.getLeft(), dropCoordinates[1] - target.getTop());
704 
705             mDragObject.x = x;
706             mDragObject.y = y;
707             if (r.contains(x, y)) {
708                 DropTarget delegate = target.getDropTargetDelegate(mDragObject);
709                 if (delegate != null) {
710                     target = delegate;
711                     target.getLocationInDragLayer(dropCoordinates);
712                 }
713 
714                 // Make dropCoordinates relative to the DropTarget
715                 dropCoordinates[0] = x - dropCoordinates[0];
716                 dropCoordinates[1] = y - dropCoordinates[1];
717 
718                 return target;
719             }
720         }
721         return null;
722     }
723 
724     public void setDragScoller(DragScroller scroller) {
725         mDragScroller = scroller;
726     }
727 
728     public void setWindowToken(IBinder token) {
729         mWindowToken = token;
730     }
731 
732     /**
733      * Sets the drag listner which will be notified when a drag starts or ends.
734      */
735     public void addDragListener(DragListener l) {
736         mListeners.add(l);
737     }
738 
739     /**
740      * Remove a previously installed drag listener.
741      */
742     public void removeDragListener(DragListener l) {
743         mListeners.remove(l);
744     }
745 
746     /**
747      * Add a DropTarget to the list of potential places to receive drop events.
748      */
749     public void addDropTarget(DropTarget target) {
750         mDropTargets.add(target);
751     }
752 
753     /**
754      * Don't send drop events to <em>target</em> any more.
755      */
756     public void removeDropTarget(DropTarget target) {
757         mDropTargets.remove(target);
758     }
759 
760     /**
761      * Sets the current fling-to-delete drop target.
762      */
763     public void setFlingToDeleteDropTarget(DropTarget target) {
764         mFlingToDeleteDropTarget = target;
765     }
766 
767     private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
768         if (mVelocityTracker == null) {
769             mVelocityTracker = VelocityTracker.obtain();
770         }
771         mVelocityTracker.addMovement(ev);
772     }
773 
774     private void releaseVelocityTracker() {
775         if (mVelocityTracker != null) {
776             mVelocityTracker.recycle();
777             mVelocityTracker = null;
778         }
779     }
780 
781     /**
782      * Set which view scrolls for touch events near the edge of the screen.
783      */
784     public void setScrollView(View v) {
785         mScrollView = v;
786     }
787 
788     DragView getDragView() {
789         return mDragObject.dragView;
790     }
791 
792     private class ScrollRunnable implements Runnable {
793         private int mDirection;
794 
795         ScrollRunnable() {
796         }
797 
798         public void run() {
799             if (mDragScroller != null) {
800                 if (mDirection == SCROLL_LEFT) {
801                     mDragScroller.scrollLeft();
802                 } else {
803                     mDragScroller.scrollRight();
804                 }
805                 mScrollState = SCROLL_OUTSIDE_ZONE;
806                 mDistanceSinceScroll = 0;
807                 mDragScroller.onExitScrollArea();
808                 mLauncher.getDragLayer().onExitScrollArea();
809 
810                 if (isDragging()) {
811                     // Check the scroll again so that we can requeue the scroller if necessary
812                     checkScrollState(mLastTouch[0], mLastTouch[1]);
813                 }
814             }
815         }
816 
817         void setDirection(int direction) {
818             mDirection = direction;
819         }
820     }
821 }
822