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<ApplicationInfo> apps, 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 (ApplicationInfo info : apps) {
    336                     // Added null checks to prevent NPE we've seen in the wild
    337                     if (dragInfo != null &&
    338                         dragInfo.intent != null &&
    339                         info.intent != null) {
    340                         boolean isSamePackage = dragInfo.getPackageName().equals(
    341                                 info.getPackageName());
    342                         if (isSamePackage) {
    343                             cancelDrag();
    344                             return;
    345                         }
    346                     }
    347                 }
    348             }
    349         }
    350     }
    351 
    352     private void endDrag() {
    353         if (mDragging) {
    354             mDragging = false;
    355             clearScrollRunnable();
    356             boolean isDeferred = false;
    357             if (mDragObject.dragView != null) {
    358                 isDeferred = mDragObject.deferDragViewCleanupPostAnimation;
    359                 if (!isDeferred) {
    360                     mDragObject.dragView.remove();
    361                 }
    362                 mDragObject.dragView = null;
    363             }
    364 
    365             // Only end the drag if we are not deferred
    366             if (!isDeferred) {
    367                 for (DragListener listener : mListeners) {
    368                     listener.onDragEnd();
    369                 }
    370             }
    371         }
    372 
    373         releaseVelocityTracker();
    374     }
    375 
    376     /**
    377      * This only gets called as a result of drag view cleanup being deferred in endDrag();
    378      */
    379     void onDeferredEndDrag(DragView dragView) {
    380         dragView.remove();
    381 
    382         // If we skipped calling onDragEnd() before, do it now
    383         for (DragListener listener : mListeners) {
    384             listener.onDragEnd();
    385         }
    386     }
    387 
    388     void onDeferredEndFling(DropTarget.DragObject d) {
    389         d.dragSource.onFlingToDeleteCompleted();
    390     }
    391 
    392     /**
    393      * Clamps the position to the drag layer bounds.
    394      */
    395     private int[] getClampedDragLayerPos(float x, float y) {
    396         mLauncher.getDragLayer().getLocalVisibleRect(mDragLayerRect);
    397         mTmpPoint[0] = (int) Math.max(mDragLayerRect.left, Math.min(x, mDragLayerRect.right - 1));
    398         mTmpPoint[1] = (int) Math.max(mDragLayerRect.top, Math.min(y, mDragLayerRect.bottom - 1));
    399         return mTmpPoint;
    400     }
    401 
    402     long getLastGestureUpTime() {
    403         if (mDragging) {
    404             return System.currentTimeMillis();
    405         } else {
    406             return mLastTouchUpTime;
    407         }
    408     }
    409 
    410     void resetLastGestureUpTime() {
    411         mLastTouchUpTime = -1;
    412     }
    413 
    414     /**
    415      * Call this from a drag source view.
    416      */
    417     public boolean onInterceptTouchEvent(MotionEvent ev) {
    418         @SuppressWarnings("all") // suppress dead code warning
    419         final boolean debug = false;
    420         if (debug) {
    421             Log.d(Launcher.TAG, "DragController.onInterceptTouchEvent " + ev + " mDragging="
    422                     + mDragging);
    423         }
    424 
    425         // Update the velocity tracker
    426         acquireVelocityTrackerAndAddMovement(ev);
    427 
    428         final int action = ev.getAction();
    429         final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
    430         final int dragLayerX = dragLayerPos[0];
    431         final int dragLayerY = dragLayerPos[1];
    432 
    433         switch (action) {
    434             case MotionEvent.ACTION_MOVE:
    435                 break;
    436             case MotionEvent.ACTION_DOWN:
    437                 // Remember location of down touch
    438                 mMotionDownX = dragLayerX;
    439                 mMotionDownY = dragLayerY;
    440                 mLastDropTarget = null;
    441                 break;
    442             case MotionEvent.ACTION_UP:
    443                 mLastTouchUpTime = System.currentTimeMillis();
    444                 if (mDragging) {
    445                     PointF vec = isFlingingToDelete(mDragObject.dragSource);
    446                     if (vec != null) {
    447                         dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec);
    448                     } else {
    449                         drop(dragLayerX, dragLayerY);
    450                     }
    451                 }
    452                 endDrag();
    453                 break;
    454             case MotionEvent.ACTION_CANCEL:
    455                 cancelDrag();
    456                 break;
    457         }
    458 
    459         return mDragging;
    460     }
    461 
    462     /**
    463      * Sets the view that should handle move events.
    464      */
    465     void setMoveTarget(View view) {
    466         mMoveTarget = view;
    467     }
    468 
    469     public boolean dispatchUnhandledMove(View focused, int direction) {
    470         return mMoveTarget != null && mMoveTarget.dispatchUnhandledMove(focused, direction);
    471     }
    472 
    473     private void clearScrollRunnable() {
    474         mHandler.removeCallbacks(mScrollRunnable);
    475         if (mScrollState == SCROLL_WAITING_IN_ZONE) {
    476             mScrollState = SCROLL_OUTSIDE_ZONE;
    477             mScrollRunnable.setDirection(SCROLL_RIGHT);
    478             mDragScroller.onExitScrollArea();
    479             mLauncher.getDragLayer().onExitScrollArea();
    480         }
    481     }
    482 
    483     private void handleMoveEvent(int x, int y) {
    484         mDragObject.dragView.move(x, y);
    485 
    486         // Drop on someone?
    487         final int[] coordinates = mCoordinatesTemp;
    488         DropTarget dropTarget = findDropTarget(x, y, coordinates);
    489         mDragObject.x = coordinates[0];
    490         mDragObject.y = coordinates[1];
    491         if (dropTarget != null) {
    492             DropTarget delegate = dropTarget.getDropTargetDelegate(mDragObject);
    493             if (delegate != null) {
    494                 dropTarget = delegate;
    495             }
    496 
    497             if (mLastDropTarget != dropTarget) {
    498                 if (mLastDropTarget != null) {
    499                     mLastDropTarget.onDragExit(mDragObject);
    500                 }
    501                 dropTarget.onDragEnter(mDragObject);
    502             }
    503             dropTarget.onDragOver(mDragObject);
    504         } else {
    505             if (mLastDropTarget != null) {
    506                 mLastDropTarget.onDragExit(mDragObject);
    507             }
    508         }
    509         mLastDropTarget = dropTarget;
    510 
    511         // After a scroll, the touch point will still be in the scroll region.
    512         // Rather than scrolling immediately, require a bit of twiddling to scroll again
    513         final int slop = ViewConfiguration.get(mLauncher).getScaledWindowTouchSlop();
    514         mDistanceSinceScroll +=
    515             Math.sqrt(Math.pow(mLastTouch[0] - x, 2) + Math.pow(mLastTouch[1] - y, 2));
    516         mLastTouch[0] = x;
    517         mLastTouch[1] = y;
    518         final int delay = mDistanceSinceScroll < slop ? RESCROLL_DELAY : SCROLL_DELAY;
    519 
    520         if (x < mScrollZone) {
    521             if (mScrollState == SCROLL_OUTSIDE_ZONE) {
    522                 mScrollState = SCROLL_WAITING_IN_ZONE;
    523                 if (mDragScroller.onEnterScrollArea(x, y, SCROLL_LEFT)) {
    524                     mLauncher.getDragLayer().onEnterScrollArea(SCROLL_LEFT);
    525                     mScrollRunnable.setDirection(SCROLL_LEFT);
    526                     mHandler.postDelayed(mScrollRunnable, delay);
    527                 }
    528             }
    529         } else if (x > mScrollView.getWidth() - mScrollZone) {
    530             if (mScrollState == SCROLL_OUTSIDE_ZONE) {
    531                 mScrollState = SCROLL_WAITING_IN_ZONE;
    532                 if (mDragScroller.onEnterScrollArea(x, y, SCROLL_RIGHT)) {
    533                     mLauncher.getDragLayer().onEnterScrollArea(SCROLL_RIGHT);
    534                     mScrollRunnable.setDirection(SCROLL_RIGHT);
    535                     mHandler.postDelayed(mScrollRunnable, delay);
    536                 }
    537             }
    538         } else {
    539             clearScrollRunnable();
    540         }
    541     }
    542 
    543     public void forceMoveEvent() {
    544         if (mDragging) {
    545             handleMoveEvent(mDragObject.x, mDragObject.y);
    546         }
    547     }
    548 
    549     /**
    550      * Call this from a drag source view.
    551      */
    552     public boolean onTouchEvent(MotionEvent ev) {
    553         if (!mDragging) {
    554             return false;
    555         }
    556 
    557         // Update the velocity tracker
    558         acquireVelocityTrackerAndAddMovement(ev);
    559 
    560         final int action = ev.getAction();
    561         final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
    562         final int dragLayerX = dragLayerPos[0];
    563         final int dragLayerY = dragLayerPos[1];
    564 
    565         switch (action) {
    566         case MotionEvent.ACTION_DOWN:
    567             // Remember where the motion event started
    568             mMotionDownX = dragLayerX;
    569             mMotionDownY = dragLayerY;
    570 
    571             if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) {
    572                 mScrollState = SCROLL_WAITING_IN_ZONE;
    573                 mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
    574             } else {
    575                 mScrollState = SCROLL_OUTSIDE_ZONE;
    576             }
    577             break;
    578         case MotionEvent.ACTION_MOVE:
    579             handleMoveEvent(dragLayerX, dragLayerY);
    580             break;
    581         case MotionEvent.ACTION_UP:
    582             // Ensure that we've processed a move event at the current pointer location.
    583             handleMoveEvent(dragLayerX, dragLayerY);
    584             mHandler.removeCallbacks(mScrollRunnable);
    585 
    586             if (mDragging) {
    587                 PointF vec = isFlingingToDelete(mDragObject.dragSource);
    588                 if (vec != null) {
    589                     dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec);
    590                 } else {
    591                     drop(dragLayerX, dragLayerY);
    592                 }
    593             }
    594             endDrag();
    595             break;
    596         case MotionEvent.ACTION_CANCEL:
    597             mHandler.removeCallbacks(mScrollRunnable);
    598             cancelDrag();
    599             break;
    600         }
    601 
    602         return true;
    603     }
    604 
    605     /**
    606      * Determines whether the user flung the current item to delete it.
    607      *
    608      * @return the vector at which the item was flung, or null if no fling was detected.
    609      */
    610     private PointF isFlingingToDelete(DragSource source) {
    611         if (mFlingToDeleteDropTarget == null) return null;
    612         if (!source.supportsFlingToDelete()) return null;
    613 
    614         ViewConfiguration config = ViewConfiguration.get(mLauncher);
    615         mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity());
    616 
    617         if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) {
    618             // Do a quick dot product test to ensure that we are flinging upwards
    619             PointF vel = new PointF(mVelocityTracker.getXVelocity(),
    620                     mVelocityTracker.getYVelocity());
    621             PointF upVec = new PointF(0f, -1f);
    622             float theta = (float) Math.acos(((vel.x * upVec.x) + (vel.y * upVec.y)) /
    623                     (vel.length() * upVec.length()));
    624             if (theta <= Math.toRadians(MAX_FLING_DEGREES)) {
    625                 return vel;
    626             }
    627         }
    628         return null;
    629     }
    630 
    631     private void dropOnFlingToDeleteTarget(float x, float y, PointF vel) {
    632         final int[] coordinates = mCoordinatesTemp;
    633 
    634         mDragObject.x = coordinates[0];
    635         mDragObject.y = coordinates[1];
    636 
    637         // Clean up dragging on the target if it's not the current fling delete target otherwise,
    638         // start dragging to it.
    639         if (mLastDropTarget != null && mFlingToDeleteDropTarget != mLastDropTarget) {
    640             mLastDropTarget.onDragExit(mDragObject);
    641         }
    642 
    643         // Drop onto the fling-to-delete target
    644         boolean accepted = false;
    645         mFlingToDeleteDropTarget.onDragEnter(mDragObject);
    646         // We must set dragComplete to true _only_ after we "enter" the fling-to-delete target for
    647         // "drop"
    648         mDragObject.dragComplete = true;
    649         mFlingToDeleteDropTarget.onDragExit(mDragObject);
    650         if (mFlingToDeleteDropTarget.acceptDrop(mDragObject)) {
    651             mFlingToDeleteDropTarget.onFlingToDelete(mDragObject, mDragObject.x, mDragObject.y,
    652                     vel);
    653             accepted = true;
    654         }
    655         mDragObject.dragSource.onDropCompleted((View) mFlingToDeleteDropTarget, mDragObject, true,
    656                 accepted);
    657     }
    658 
    659     private void drop(float x, float y) {
    660         final int[] coordinates = mCoordinatesTemp;
    661         final DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);
    662 
    663         mDragObject.x = coordinates[0];
    664         mDragObject.y = coordinates[1];
    665         boolean accepted = false;
    666         if (dropTarget != null) {
    667             mDragObject.dragComplete = true;
    668             dropTarget.onDragExit(mDragObject);
    669             if (dropTarget.acceptDrop(mDragObject)) {
    670                 dropTarget.onDrop(mDragObject);
    671                 accepted = true;
    672             }
    673         }
    674         mDragObject.dragSource.onDropCompleted((View) dropTarget, mDragObject, false, accepted);
    675     }
    676 
    677     private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) {
    678         final Rect r = mRectTemp;
    679 
    680         final ArrayList<DropTarget> dropTargets = mDropTargets;
    681         final int count = dropTargets.size();
    682         for (int i=count-1; i>=0; i--) {
    683             DropTarget target = dropTargets.get(i);
    684             if (!target.isDropEnabled())
    685                 continue;
    686 
    687             target.getHitRect(r);
    688 
    689             // Convert the hit rect to DragLayer coordinates
    690             target.getLocationInDragLayer(dropCoordinates);
    691             r.offset(dropCoordinates[0] - target.getLeft(), dropCoordinates[1] - target.getTop());
    692 
    693             mDragObject.x = x;
    694             mDragObject.y = y;
    695             if (r.contains(x, y)) {
    696                 DropTarget delegate = target.getDropTargetDelegate(mDragObject);
    697                 if (delegate != null) {
    698                     target = delegate;
    699                     target.getLocationInDragLayer(dropCoordinates);
    700                 }
    701 
    702                 // Make dropCoordinates relative to the DropTarget
    703                 dropCoordinates[0] = x - dropCoordinates[0];
    704                 dropCoordinates[1] = y - dropCoordinates[1];
    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                     // Force an update so that we can requeue the scroller if necessary
    800                     forceMoveEvent();
    801                 }
    802             }
    803         }
    804 
    805         void setDirection(int direction) {
    806             mDirection = direction;
    807         }
    808     }
    809 }
    810