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