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