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