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