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