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