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