Home | History | Annotate | Download | only in launcher2
      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 
     17 package com.android.launcher2;
     18 
     19 import android.content.Context;
     20 import android.content.res.Resources;
     21 import android.graphics.Bitmap;
     22 import android.graphics.Point;
     23 import android.graphics.PointF;
     24 import android.graphics.Rect;
     25 import android.os.Handler;
     26 import android.os.IBinder;
     27 import android.os.Vibrator;
     28 import android.util.Log;
     29 import android.view.KeyEvent;
     30 import android.view.MotionEvent;
     31 import android.view.VelocityTracker;
     32 import android.view.View;
     33 import android.view.ViewConfiguration;
     34 import android.view.inputmethod.InputMethodManager;
     35 
     36 import com.android.launcher.R;
     37 
     38 import java.util.ArrayList;
     39 
     40 /**
     41  * Class for initiating a drag within a view or across multiple views.
     42  */
     43 public class DragController {
     44     private static final String TAG = "Launcher.DragController";
     45 
     46     /** Indicates the drag is a move.  */
     47     public static int DRAG_ACTION_MOVE = 0;
     48 
     49     /** Indicates the drag is a copy.  */
     50     public static int DRAG_ACTION_COPY = 1;
     51 
     52     private static final int SCROLL_DELAY = 500;
     53     private static final int RESCROLL_DELAY = 750;
     54     private static final int VIBRATE_DURATION = 15;
     55 
     56     private static final boolean PROFILE_DRAWING_DURING_DRAG = false;
     57 
     58     private static final int SCROLL_OUTSIDE_ZONE = 0;
     59     private static final int SCROLL_WAITING_IN_ZONE = 1;
     60 
     61     static final int SCROLL_NONE = -1;
     62     static final int SCROLL_LEFT = 0;
     63     static final int SCROLL_RIGHT = 1;
     64 
     65     private static final float MAX_FLING_DEGREES = 35f;
     66 
     67     private Launcher mLauncher;
     68     private Handler mHandler;
     69     private final Vibrator mVibrator;
     70 
     71     // temporaries to avoid gc thrash
     72     private Rect mRectTemp = new Rect();
     73     private final int[] mCoordinatesTemp = new int[2];
     74 
     75     /** Whether or not we're dragging. */
     76     private boolean mDragging;
     77 
     78     /** X coordinate of the down event. */
     79     private int mMotionDownX;
     80 
     81     /** Y coordinate of the down event. */
     82     private int mMotionDownY;
     83 
     84     /** the area at the edge of the screen that makes the workspace go left
     85      *   or right while you're dragging.
     86      */
     87     private int mScrollZone;
     88 
     89     private DropTarget.DragObject mDragObject;
     90 
     91     /** Who can receive drop events */
     92     private ArrayList<DropTarget> mDropTargets = new ArrayList<DropTarget>();
     93     private ArrayList<DragListener> mListeners = new ArrayList<DragListener>();
     94     private DropTarget mFlingToDeleteDropTarget;
     95 
     96     /** The window token used as the parent for the DragView. */
     97     private IBinder mWindowToken;
     98 
     99     /** The view that will be scrolled when dragging to the left and right edges of the screen. */
    100     private View mScrollView;
    101 
    102     private View mMoveTarget;
    103 
    104     private DragScroller mDragScroller;
    105     private int mScrollState = SCROLL_OUTSIDE_ZONE;
    106     private ScrollRunnable mScrollRunnable = new ScrollRunnable();
    107 
    108     private DropTarget mLastDropTarget;
    109 
    110     private InputMethodManager mInputMethodManager;
    111 
    112     private int mLastTouch[] = new int[2];
    113     private long mLastTouchUpTime = -1;
    114     private int mDistanceSinceScroll = 0;
    115 
    116     private int mTmpPoint[] = new int[2];
    117     private Rect mDragLayerRect = new Rect();
    118 
    119     protected int mFlingToDeleteThresholdVelocity;
    120     private VelocityTracker mVelocityTracker;
    121 
    122     /**
    123      * Interface to receive notifications when a drag starts or stops
    124      */
    125     interface DragListener {
    126 
    127         /**
    128          * A drag has begun
    129          *
    130          * @param source An object representing where the drag originated
    131          * @param info The data associated with the object that is being dragged
    132          * @param dragAction The drag action: either {@link DragController#DRAG_ACTION_MOVE}
    133          *        or {@link DragController#DRAG_ACTION_COPY}
    134          */
    135         void onDragStart(DragSource source, Object info, int dragAction);
    136 
    137         /**
    138          * The drag has ended
    139          */
    140         void onDragEnd();
    141     }
    142 
    143     /**
    144      * Used to create a new DragLayer from XML.
    145      *
    146      * @param context The application's context.
    147      */
    148     public DragController(Launcher launcher) {
    149         Resources r = launcher.getResources();
    150         mLauncher = launcher;
    151         mHandler = new Handler();
    152         mScrollZone = r.getDimensionPixelSize(R.dimen.scroll_zone);
    153         mVelocityTracker = VelocityTracker.obtain();
    154         mVibrator = (Vibrator) launcher.getSystemService(Context.VIBRATOR_SERVICE);
    155 
    156         float density = r.getDisplayMetrics().density;
    157         mFlingToDeleteThresholdVelocity =
    158                 (int) (r.getInteger(R.integer.config_flingToDeleteMinVelocity) * density);
    159     }
    160 
    161     public boolean dragging() {
    162         return mDragging;
    163     }
    164 
    165     /**
    166      * Starts a drag.
    167      *
    168      * @param v The view that is being dragged
    169      * @param bmp The bitmap that represents the view being dragged
    170      * @param source An object representing where the drag originated
    171      * @param dragInfo The data associated with the object that is being dragged
    172      * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or
    173      *        {@link #DRAG_ACTION_COPY}
    174      * @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
    175      *          Makes dragging feel more precise, e.g. you can clip out a transparent border
    176      */
    177     public void startDrag(View v, Bitmap bmp, DragSource source, Object dragInfo, int dragAction,
    178             Rect dragRegion, float initialDragViewScale) {
    179         int[] loc = mCoordinatesTemp;
    180         mLauncher.getDragLayer().getLocationInDragLayer(v, loc);
    181         int dragLayerX = loc[0] + v.getPaddingLeft() +
    182                 (int) ((initialDragViewScale * bmp.getWidth() - bmp.getWidth()) / 2);
    183         int dragLayerY = loc[1] + v.getPaddingTop() +
    184                 (int) ((initialDragViewScale * bmp.getHeight() - bmp.getHeight()) / 2);
    185 
    186         startDrag(bmp, dragLayerX, dragLayerY, source, dragInfo, dragAction, null, dragRegion,
    187                 initialDragViewScale);
    188 
    189         if (dragAction == DRAG_ACTION_MOVE) {
    190             v.setVisibility(View.GONE);
    191         }
    192     }
    193 
    194     /**
    195      * Starts a drag.
    196      *
    197      * @param b The bitmap to display as the drag image.  It will be re-scaled to the
    198      *          enlarged size.
    199      * @param dragLayerX The x position in the DragLayer of the left-top of the bitmap.
    200      * @param dragLayerY The y position in the DragLayer of the left-top of the bitmap.
    201      * @param source An object representing where the drag originated
    202      * @param dragInfo The data associated with the object that is being dragged
    203      * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or
    204      *        {@link #DRAG_ACTION_COPY}
    205      * @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
    206      *          Makes dragging feel more precise, e.g. you can clip out a transparent border
    207      */
    208     public void startDrag(Bitmap b, int dragLayerX, int dragLayerY,
    209             DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion,
    210             float initialDragViewScale) {
    211         if (PROFILE_DRAWING_DURING_DRAG) {
    212             android.os.Debug.startMethodTracing("Launcher");
    213         }
    214 
    215         // Hide soft keyboard, if visible
    216         if (mInputMethodManager == null) {
    217             mInputMethodManager = (InputMethodManager)
    218                     mLauncher.getSystemService(Context.INPUT_METHOD_SERVICE);
    219         }
    220         mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0);
    221 
    222         for (DragListener listener : mListeners) {
    223             listener.onDragStart(source, dragInfo, dragAction);
    224         }
    225 
    226         final int registrationX = mMotionDownX - dragLayerX;
    227         final int registrationY = mMotionDownY - dragLayerY;
    228 
    229         final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;
    230         final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;
    231 
    232         mDragging = true;
    233 
    234         mDragObject = new DropTarget.DragObject();
    235 
    236         mDragObject.dragComplete = false;
    237         mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft);
    238         mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop);
    239         mDragObject.dragSource = source;
    240         mDragObject.dragInfo = dragInfo;
    241 
    242         mVibrator.vibrate(VIBRATE_DURATION);
    243 
    244         final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
    245                 registrationY, 0, 0, b.getWidth(), b.getHeight(), initialDragViewScale);
    246 
    247         if (dragOffset != null) {
    248             dragView.setDragVisualizeOffset(new Point(dragOffset));
    249         }
    250         if (dragRegion != null) {
    251             dragView.setDragRegion(new Rect(dragRegion));
    252         }
    253 
    254         dragView.show(mMotionDownX, mMotionDownY);
    255         handleMoveEvent(mMotionDownX, mMotionDownY);
    256     }
    257 
    258     /**
    259      * Draw the view into a bitmap.
    260      */
    261     Bitmap getViewBitmap(View v) {
    262         v.clearFocus();
    263         v.setPressed(false);
    264 
    265         boolean willNotCache = v.willNotCacheDrawing();
    266         v.setWillNotCacheDrawing(false);
    267 
    268         // Reset the drawing cache background color to fully transparent
    269         // for the duration of this operation
    270         int color = v.getDrawingCacheBackgroundColor();
    271         v.setDrawingCacheBackgroundColor(0);
    272         float alpha = v.getAlpha();
    273         v.setAlpha(1.0f);
    274 
    275         if (color != 0) {
    276             v.destroyDrawingCache();
    277         }
    278         v.buildDrawingCache();
    279         Bitmap cacheBitmap = v.getDrawingCache();
    280         if (cacheBitmap == null) {
    281             Log.e(TAG, "failed getViewBitmap(" + v + ")", new RuntimeException());
    282             return null;
    283         }
    284 
    285         Bitmap bitmap = Bitmap.createBitmap(cacheBitmap);
    286 
    287         // Restore the view
    288         v.destroyDrawingCache();
    289         v.setAlpha(alpha);
    290         v.setWillNotCacheDrawing(willNotCache);
    291         v.setDrawingCacheBackgroundColor(color);
    292 
    293         return bitmap;
    294     }
    295 
    296     /**
    297      * Call this from a drag source view like this:
    298      *
    299      * <pre>
    300      *  @Override
    301      *  public boolean dispatchKeyEvent(KeyEvent event) {
    302      *      return mDragController.dispatchKeyEvent(this, event)
    303      *              || super.dispatchKeyEvent(event);
    304      * </pre>
    305      */
    306     public boolean dispatchKeyEvent(KeyEvent event) {
    307         return mDragging;
    308     }
    309 
    310     public boolean isDragging() {
    311         return mDragging;
    312     }
    313 
    314     /**
    315      * Stop dragging without dropping.
    316      */
    317     public void cancelDrag() {
    318         if (mDragging) {
    319             if (mLastDropTarget != null) {
    320                 mLastDropTarget.onDragExit(mDragObject);
    321             }
    322             mDragObject.deferDragViewCleanupPostAnimation = false;
    323             mDragObject.cancelled = true;
    324             mDragObject.dragComplete = true;
    325             mDragObject.dragSource.onDropCompleted(null, mDragObject, false, false);
    326         }
    327         endDrag();
    328     }
    329     public void onAppsRemoved(ArrayList<String> packageNames, Context context) {
    330         // Cancel the current drag if we are removing an app that we are dragging
    331         if (mDragObject != null) {
    332             Object rawDragInfo = mDragObject.dragInfo;
    333             if (rawDragInfo instanceof ShortcutInfo) {
    334                 ShortcutInfo dragInfo = (ShortcutInfo) rawDragInfo;
    335                 for (String pn : packageNames) {
    336                     // Added null checks to prevent NPE we've seen in the wild
    337                     if (dragInfo != null &&
    338                         dragInfo.intent != null) {
    339                         boolean isSamePackage = dragInfo.getPackageName().equals(pn);
    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