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