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