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