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