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