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