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