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 static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
     20 import static com.android.launcher3.LauncherState.NORMAL;
     21 
     22 import android.content.ComponentName;
     23 import android.content.res.Resources;
     24 import android.graphics.Bitmap;
     25 import android.graphics.Point;
     26 import android.graphics.Rect;
     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.View;
     33 
     34 import com.android.launcher3.DragSource;
     35 import com.android.launcher3.DropTarget;
     36 import com.android.launcher3.ItemInfo;
     37 import com.android.launcher3.Launcher;
     38 import com.android.launcher3.R;
     39 import com.android.launcher3.ShortcutInfo;
     40 import com.android.launcher3.accessibility.DragViewStateAnnouncer;
     41 import com.android.launcher3.util.ItemInfoMatcher;
     42 import com.android.launcher3.util.Thunk;
     43 import com.android.launcher3.util.TouchController;
     44 import com.android.launcher3.util.UiThreadHelper;
     45 
     46 import java.util.ArrayList;
     47 
     48 /**
     49  * Class for initiating a drag within a view or across multiple views.
     50  */
     51 public class DragController implements DragDriver.EventListener, TouchController {
     52     private static final boolean PROFILE_DRAWING_DURING_DRAG = false;
     53 
     54     @Thunk Launcher mLauncher;
     55     private FlingToDeleteHelper mFlingToDeleteHelper;
     56 
     57     // temporaries to avoid gc thrash
     58     private Rect mRectTemp = new Rect();
     59     private final int[] mCoordinatesTemp = new int[2];
     60 
     61     /**
     62      * Drag driver for the current drag/drop operation, or null if there is no active DND operation.
     63      * It's null during accessible drag operations.
     64      */
     65     private DragDriver mDragDriver = null;
     66 
     67     /** Options controlling the drag behavior. */
     68     private DragOptions mOptions;
     69 
     70     /** X coordinate of the down event. */
     71     private int mMotionDownX;
     72 
     73     /** Y coordinate of the down event. */
     74     private int mMotionDownY;
     75 
     76     private DropTarget.DragObject mDragObject;
     77 
     78     /** Who can receive drop events */
     79     private ArrayList<DropTarget> mDropTargets = new ArrayList<>();
     80     private ArrayList<DragListener> mListeners = new ArrayList<>();
     81 
     82     /** The window token used as the parent for the DragView. */
     83     private IBinder mWindowToken;
     84 
     85     private View mMoveTarget;
     86 
     87     private DropTarget mLastDropTarget;
     88 
     89     @Thunk int mLastTouch[] = new int[2];
     90     @Thunk long mLastTouchUpTime = -1;
     91     @Thunk int mDistanceSinceScroll = 0;
     92 
     93     private int mTmpPoint[] = new int[2];
     94     private Rect mDragLayerRect = new Rect();
     95 
     96     private boolean mIsInPreDrag;
     97 
     98     /**
     99      * Interface to receive notifications when a drag starts or stops
    100      */
    101     public interface DragListener {
    102         /**
    103          * A drag has begun
    104          *
    105          * @param dragObject The object being dragged
    106          * @param options Options used to start the drag
    107          */
    108         void onDragStart(DropTarget.DragObject dragObject, DragOptions options);
    109 
    110         /**
    111          * The drag has ended
    112          */
    113         void onDragEnd();
    114     }
    115 
    116     /**
    117      * Used to create a new DragLayer from XML.
    118      */
    119     public DragController(Launcher launcher) {
    120         mLauncher = launcher;
    121         mFlingToDeleteHelper = new FlingToDeleteHelper(launcher);
    122     }
    123 
    124     /**
    125      * Starts a drag.
    126      * When the drag is started, the UI automatically goes into spring loaded mode. On a successful
    127      * drop, it is the responsibility of the {@link DropTarget} to exit out of the spring loaded
    128      * mode. If the drop was cancelled for some reason, the UI will automatically exit out of this mode.
    129      *
    130      * @param b The bitmap to display as the drag image.  It will be re-scaled to the
    131      *          enlarged size.
    132      * @param dragLayerX The x position in the DragLayer of the left-top of the bitmap.
    133      * @param dragLayerY The y position in the DragLayer of the left-top of the bitmap.
    134      * @param source An object representing where the drag originated
    135      * @param dragInfo The data associated with the object that is being dragged
    136      * @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
    137      *          Makes dragging feel more precise, e.g. you can clip out a transparent border
    138      */
    139     public DragView startDrag(Bitmap b, int dragLayerX, int dragLayerY,
    140             DragSource source, ItemInfo dragInfo, Point dragOffset, Rect dragRegion,
    141             float initialDragViewScale, float dragViewScaleOnDrop, DragOptions options) {
    142         if (PROFILE_DRAWING_DURING_DRAG) {
    143             android.os.Debug.startMethodTracing("Launcher");
    144         }
    145 
    146         // Hide soft keyboard, if visible
    147         UiThreadHelper.hideKeyboardAsync(mLauncher, mWindowToken);
    148 
    149         mOptions = options;
    150         if (mOptions.systemDndStartPoint != null) {
    151             mMotionDownX = mOptions.systemDndStartPoint.x;
    152             mMotionDownY = mOptions.systemDndStartPoint.y;
    153         }
    154 
    155         final int registrationX = mMotionDownX - dragLayerX;
    156         final int registrationY = mMotionDownY - dragLayerY;
    157 
    158         final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;
    159         final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;
    160 
    161         mLastDropTarget = null;
    162 
    163         mDragObject = new DropTarget.DragObject();
    164 
    165         mIsInPreDrag = mOptions.preDragCondition != null
    166                 && !mOptions.preDragCondition.shouldStartDrag(0);
    167 
    168         final Resources res = mLauncher.getResources();
    169         final float scaleDps = mIsInPreDrag
    170                 ? res.getDimensionPixelSize(R.dimen.pre_drag_view_scale) : 0f;
    171         final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
    172                 registrationY, initialDragViewScale, dragViewScaleOnDrop, scaleDps);
    173         dragView.setItemInfo(dragInfo);
    174         mDragObject.dragComplete = false;
    175         if (mOptions.isAccessibleDrag) {
    176             // For an accessible drag, we assume the view is being dragged from the center.
    177             mDragObject.xOffset = b.getWidth() / 2;
    178             mDragObject.yOffset = b.getHeight() / 2;
    179             mDragObject.accessibleDrag = true;
    180         } else {
    181             mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft);
    182             mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop);
    183             mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView);
    184 
    185             mDragDriver = DragDriver.create(mLauncher, this, mDragObject, mOptions);
    186         }
    187 
    188         mDragObject.dragSource = source;
    189         mDragObject.dragInfo = dragInfo;
    190         mDragObject.originalDragInfo = new ItemInfo();
    191         mDragObject.originalDragInfo.copyFrom(dragInfo);
    192 
    193         if (dragOffset != null) {
    194             dragView.setDragVisualizeOffset(new Point(dragOffset));
    195         }
    196         if (dragRegion != null) {
    197             dragView.setDragRegion(new Rect(dragRegion));
    198         }
    199 
    200         mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
    201         dragView.show(mMotionDownX, mMotionDownY);
    202         mDistanceSinceScroll = 0;
    203 
    204         if (!mIsInPreDrag) {
    205             callOnDragStart();
    206         } else if (mOptions.preDragCondition != null) {
    207             mOptions.preDragCondition.onPreDragStart(mDragObject);
    208         }
    209 
    210         mLastTouch[0] = mMotionDownX;
    211         mLastTouch[1] = mMotionDownY;
    212         handleMoveEvent(mMotionDownX, mMotionDownY);
    213         mLauncher.getUserEventDispatcher().resetActionDurationMillis();
    214         return dragView;
    215     }
    216 
    217     private void callOnDragStart() {
    218         if (mOptions.preDragCondition != null) {
    219             mOptions.preDragCondition.onPreDragEnd(mDragObject, true /* dragStarted*/);
    220         }
    221         mIsInPreDrag = false;
    222         for (DragListener listener : new ArrayList<>(mListeners)) {
    223             listener.onDragStart(mDragObject, mOptions);
    224         }
    225     }
    226 
    227     /**
    228      * Call this from a drag source view like this:
    229      *
    230      * <pre>
    231      *  @Override
    232      *  public boolean dispatchKeyEvent(KeyEvent event) {
    233      *      return mDragController.dispatchKeyEvent(this, event)
    234      *              || super.dispatchKeyEvent(event);
    235      * </pre>
    236      */
    237     public boolean dispatchKeyEvent(KeyEvent event) {
    238         return mDragDriver != null;
    239     }
    240 
    241     public boolean isDragging() {
    242         return mDragDriver != null || (mOptions != null && mOptions.isAccessibleDrag);
    243     }
    244 
    245     /**
    246      * Stop dragging without dropping.
    247      */
    248     public void cancelDrag() {
    249         if (isDragging()) {
    250             if (mLastDropTarget != null) {
    251                 mLastDropTarget.onDragExit(mDragObject);
    252             }
    253             mDragObject.deferDragViewCleanupPostAnimation = false;
    254             mDragObject.cancelled = true;
    255             mDragObject.dragComplete = true;
    256             if (!mIsInPreDrag) {
    257                 dispatchDropComplete(null, false);
    258             }
    259         }
    260         endDrag();
    261     }
    262 
    263     private void dispatchDropComplete(View dropTarget, boolean accepted) {
    264         if (!accepted) {
    265             // If it was not accepted, cleanup the state. If it was accepted, it is the
    266             // responsibility of the drop target to cleanup the state.
    267             mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
    268             mDragObject.deferDragViewCleanupPostAnimation = false;
    269         }
    270 
    271         mDragObject.dragSource.onDropCompleted(dropTarget, mDragObject, accepted);
    272     }
    273 
    274     public void onAppsRemoved(ItemInfoMatcher matcher) {
    275         // Cancel the current drag if we are removing an app that we are dragging
    276         if (mDragObject != null) {
    277             ItemInfo dragInfo = mDragObject.dragInfo;
    278             if (dragInfo instanceof ShortcutInfo) {
    279                 ComponentName cn = dragInfo.getTargetComponent();
    280                 if (cn != null && matcher.matches(dragInfo, cn)) {
    281                     cancelDrag();
    282                 }
    283             }
    284         }
    285     }
    286 
    287     private void endDrag() {
    288         if (isDragging()) {
    289             mDragDriver = null;
    290             boolean isDeferred = false;
    291             if (mDragObject.dragView != null) {
    292                 isDeferred = mDragObject.deferDragViewCleanupPostAnimation;
    293                 if (!isDeferred) {
    294                     mDragObject.dragView.remove();
    295                 } else if (mIsInPreDrag) {
    296                     animateDragViewToOriginalPosition(null, null, -1);
    297                 }
    298                 mDragObject.dragView = null;
    299             }
    300 
    301             // Only end the drag if we are not deferred
    302             if (!isDeferred) {
    303                 callOnDragEnd();
    304             }
    305         }
    306 
    307         mFlingToDeleteHelper.releaseVelocityTracker();
    308     }
    309 
    310     public void animateDragViewToOriginalPosition(final Runnable onComplete,
    311             final View originalIcon, int duration) {
    312         Runnable onCompleteRunnable = new Runnable() {
    313             @Override
    314             public void run() {
    315                 if (originalIcon != null) {
    316                     originalIcon.setVisibility(View.VISIBLE);
    317                 }
    318                 if (onComplete != null) {
    319                     onComplete.run();
    320                 }
    321             }
    322         };
    323         mDragObject.dragView.animateTo(mMotionDownX, mMotionDownY, onCompleteRunnable, duration);
    324     }
    325 
    326     private void callOnDragEnd() {
    327         if (mIsInPreDrag && mOptions.preDragCondition != null) {
    328             mOptions.preDragCondition.onPreDragEnd(mDragObject, false /* dragStarted*/);
    329         }
    330         mIsInPreDrag = false;
    331         mOptions = null;
    332         for (DragListener listener : new ArrayList<>(mListeners)) {
    333             listener.onDragEnd();
    334         }
    335     }
    336 
    337     /**
    338      * This only gets called as a result of drag view cleanup being deferred in endDrag();
    339      */
    340     void onDeferredEndDrag(DragView dragView) {
    341         dragView.remove();
    342 
    343         if (mDragObject.deferDragViewCleanupPostAnimation) {
    344             // If we skipped calling onDragEnd() before, do it now
    345             callOnDragEnd();
    346         }
    347     }
    348 
    349     /**
    350      * Clamps the position to the drag layer bounds.
    351      */
    352     private int[] getClampedDragLayerPos(float x, float y) {
    353         mLauncher.getDragLayer().getLocalVisibleRect(mDragLayerRect);
    354         mTmpPoint[0] = (int) Math.max(mDragLayerRect.left, Math.min(x, mDragLayerRect.right - 1));
    355         mTmpPoint[1] = (int) Math.max(mDragLayerRect.top, Math.min(y, mDragLayerRect.bottom - 1));
    356         return mTmpPoint;
    357     }
    358 
    359     public long getLastGestureUpTime() {
    360         if (mDragDriver != null) {
    361             return System.currentTimeMillis();
    362         } else {
    363             return mLastTouchUpTime;
    364         }
    365     }
    366 
    367     public void resetLastGestureUpTime() {
    368         mLastTouchUpTime = -1;
    369     }
    370 
    371     @Override
    372     public void onDriverDragMove(float x, float y) {
    373         final int[] dragLayerPos = getClampedDragLayerPos(x, y);
    374 
    375         handleMoveEvent(dragLayerPos[0], dragLayerPos[1]);
    376     }
    377 
    378     @Override
    379     public void onDriverDragExitWindow() {
    380         if (mLastDropTarget != null) {
    381             mLastDropTarget.onDragExit(mDragObject);
    382             mLastDropTarget = null;
    383         }
    384     }
    385 
    386     @Override
    387     public void onDriverDragEnd(float x, float y) {
    388         DropTarget dropTarget;
    389         Runnable flingAnimation = mFlingToDeleteHelper.getFlingAnimation(mDragObject);
    390         if (flingAnimation != null) {
    391             dropTarget = mFlingToDeleteHelper.getDropTarget();
    392         } else {
    393             dropTarget = findDropTarget((int) x, (int) y, mCoordinatesTemp);
    394         }
    395 
    396         drop(dropTarget, flingAnimation);
    397 
    398         endDrag();
    399     }
    400 
    401     @Override
    402     public void onDriverDragCancel() {
    403         cancelDrag();
    404     }
    405 
    406     /**
    407      * Call this from a drag source view.
    408      */
    409     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
    410         if (mOptions != null && mOptions.isAccessibleDrag) {
    411             return false;
    412         }
    413 
    414         // Update the velocity tracker
    415         mFlingToDeleteHelper.recordMotionEvent(ev);
    416 
    417         final int action = ev.getAction();
    418         final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
    419         final int dragLayerX = dragLayerPos[0];
    420         final int dragLayerY = dragLayerPos[1];
    421 
    422         switch (action) {
    423             case MotionEvent.ACTION_DOWN:
    424                 // Remember location of down touch
    425                 mMotionDownX = dragLayerX;
    426                 mMotionDownY = dragLayerY;
    427                 break;
    428             case MotionEvent.ACTION_UP:
    429                 mLastTouchUpTime = System.currentTimeMillis();
    430                 break;
    431         }
    432 
    433         return mDragDriver != null && mDragDriver.onInterceptTouchEvent(ev);
    434     }
    435 
    436     /**
    437      * Call this from a drag source view.
    438      */
    439     public boolean onDragEvent(long dragStartTime, DragEvent event) {
    440         mFlingToDeleteHelper.recordDragEvent(dragStartTime, event);
    441         return mDragDriver != null && mDragDriver.onDragEvent(event);
    442     }
    443 
    444     /**
    445      * Call this from a drag view.
    446      */
    447     public void onDragViewAnimationEnd() {
    448         if (mDragDriver != null) {
    449             mDragDriver.onDragViewAnimationEnd();
    450         }
    451     }
    452 
    453     /**
    454      * Sets the view that should handle move events.
    455      */
    456     public void setMoveTarget(View view) {
    457         mMoveTarget = view;
    458     }
    459 
    460     public boolean dispatchUnhandledMove(View focused, int direction) {
    461         return mMoveTarget != null && mMoveTarget.dispatchUnhandledMove(focused, direction);
    462     }
    463 
    464     private void handleMoveEvent(int x, int y) {
    465         mDragObject.dragView.move(x, y);
    466 
    467         // Drop on someone?
    468         final int[] coordinates = mCoordinatesTemp;
    469         DropTarget dropTarget = findDropTarget(x, y, coordinates);
    470         mDragObject.x = coordinates[0];
    471         mDragObject.y = coordinates[1];
    472         checkTouchMove(dropTarget);
    473 
    474         // Check if we are hovering over the scroll areas
    475         mDistanceSinceScroll += Math.hypot(mLastTouch[0] - x, mLastTouch[1] - y);
    476         mLastTouch[0] = x;
    477         mLastTouch[1] = y;
    478 
    479         if (mIsInPreDrag && mOptions.preDragCondition != null
    480                 && mOptions.preDragCondition.shouldStartDrag(mDistanceSinceScroll)) {
    481             callOnDragStart();
    482         }
    483     }
    484 
    485     public float getDistanceDragged() {
    486         return mDistanceSinceScroll;
    487     }
    488 
    489     public void forceTouchMove() {
    490         int[] dummyCoordinates = mCoordinatesTemp;
    491         DropTarget dropTarget = findDropTarget(mLastTouch[0], mLastTouch[1], dummyCoordinates);
    492         mDragObject.x = dummyCoordinates[0];
    493         mDragObject.y = dummyCoordinates[1];
    494         checkTouchMove(dropTarget);
    495     }
    496 
    497     private void checkTouchMove(DropTarget dropTarget) {
    498         if (dropTarget != null) {
    499             if (mLastDropTarget != dropTarget) {
    500                 if (mLastDropTarget != null) {
    501                     mLastDropTarget.onDragExit(mDragObject);
    502                 }
    503                 dropTarget.onDragEnter(mDragObject);
    504             }
    505             dropTarget.onDragOver(mDragObject);
    506         } else {
    507             if (mLastDropTarget != null) {
    508                 mLastDropTarget.onDragExit(mDragObject);
    509             }
    510         }
    511         mLastDropTarget = dropTarget;
    512     }
    513 
    514     /**
    515      * Call this from a drag source view.
    516      */
    517     public boolean onControllerTouchEvent(MotionEvent ev) {
    518         if (mDragDriver == null || mOptions == null || mOptions.isAccessibleDrag) {
    519             return false;
    520         }
    521 
    522         // Update the velocity tracker
    523         mFlingToDeleteHelper.recordMotionEvent(ev);
    524 
    525         final int action = ev.getAction();
    526         final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
    527         final int dragLayerX = dragLayerPos[0];
    528         final int dragLayerY = dragLayerPos[1];
    529 
    530         switch (action) {
    531             case MotionEvent.ACTION_DOWN:
    532                 // Remember where the motion event started
    533                 mMotionDownX = dragLayerX;
    534                 mMotionDownY = dragLayerY;
    535                 break;
    536         }
    537 
    538         return mDragDriver.onTouchEvent(ev);
    539     }
    540 
    541     /**
    542      * Since accessible drag and drop won't cause the same sequence of touch events, we manually
    543      * inject the appropriate state.
    544      */
    545     public void prepareAccessibleDrag(int x, int y) {
    546         mMotionDownX = x;
    547         mMotionDownY = y;
    548     }
    549 
    550     /**
    551      * As above, since accessible drag and drop won't cause the same sequence of touch events,
    552      * we manually ensure appropriate drag and drop events get emulated for accessible drag.
    553      */
    554     public void completeAccessibleDrag(int[] location) {
    555         final int[] coordinates = mCoordinatesTemp;
    556 
    557         // We make sure that we prime the target for drop.
    558         DropTarget dropTarget = findDropTarget(location[0], location[1], coordinates);
    559         mDragObject.x = coordinates[0];
    560         mDragObject.y = coordinates[1];
    561         checkTouchMove(dropTarget);
    562 
    563         dropTarget.prepareAccessibilityDrop();
    564         // Perform the drop
    565         drop(dropTarget, null);
    566         endDrag();
    567     }
    568 
    569     private void drop(DropTarget dropTarget, Runnable flingAnimation) {
    570         final int[] coordinates = mCoordinatesTemp;
    571         mDragObject.x = coordinates[0];
    572         mDragObject.y = coordinates[1];
    573 
    574         // Move dragging to the final target.
    575         if (dropTarget != mLastDropTarget) {
    576             if (mLastDropTarget != null) {
    577                 mLastDropTarget.onDragExit(mDragObject);
    578             }
    579             mLastDropTarget = dropTarget;
    580             if (dropTarget != null) {
    581                 dropTarget.onDragEnter(mDragObject);
    582             }
    583         }
    584 
    585         mDragObject.dragComplete = true;
    586         if (mIsInPreDrag) {
    587             if (dropTarget != null) {
    588                 dropTarget.onDragExit(mDragObject);
    589             }
    590             return;
    591         }
    592 
    593         // Drop onto the target.
    594         boolean accepted = false;
    595         if (dropTarget != null) {
    596             dropTarget.onDragExit(mDragObject);
    597             if (dropTarget.acceptDrop(mDragObject)) {
    598                 if (flingAnimation != null) {
    599                     flingAnimation.run();
    600                 } else {
    601                     dropTarget.onDrop(mDragObject, mOptions);
    602                 }
    603                 accepted = true;
    604             }
    605         }
    606         final View dropTargetAsView = dropTarget instanceof View ? (View) dropTarget : null;
    607         mLauncher.getUserEventDispatcher().logDragNDrop(mDragObject, dropTargetAsView);
    608         dispatchDropComplete(dropTargetAsView, accepted);
    609     }
    610 
    611     private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) {
    612         mDragObject.x = x;
    613         mDragObject.y = y;
    614 
    615         final Rect r = mRectTemp;
    616         final ArrayList<DropTarget> dropTargets = mDropTargets;
    617         final int count = dropTargets.size();
    618         for (int i = count - 1; i >= 0; i--) {
    619             DropTarget target = dropTargets.get(i);
    620             if (!target.isDropEnabled())
    621                 continue;
    622 
    623             target.getHitRectRelativeToDragLayer(r);
    624             if (r.contains(x, y)) {
    625                 dropCoordinates[0] = x;
    626                 dropCoordinates[1] = y;
    627                 mLauncher.getDragLayer().mapCoordInSelfToDescendant((View) target, dropCoordinates);
    628                 return target;
    629             }
    630         }
    631         // Pass all unhandled drag to workspace. Workspace finds the correct
    632         // cell layout to drop to in the existing drag/drop logic.
    633         dropCoordinates[0] = x;
    634         dropCoordinates[1] = y;
    635         mLauncher.getDragLayer().mapCoordInSelfToDescendant(mLauncher.getWorkspace(),
    636                 dropCoordinates);
    637         return mLauncher.getWorkspace();
    638     }
    639 
    640     public void setWindowToken(IBinder token) {
    641         mWindowToken = token;
    642     }
    643 
    644     /**
    645      * Sets the drag listener which will be notified when a drag starts or ends.
    646      */
    647     public void addDragListener(DragListener l) {
    648         mListeners.add(l);
    649     }
    650 
    651     /**
    652      * Remove a previously installed drag listener.
    653      */
    654     public void removeDragListener(DragListener l) {
    655         mListeners.remove(l);
    656     }
    657 
    658     /**
    659      * Add a DropTarget to the list of potential places to receive drop events.
    660      */
    661     public void addDropTarget(DropTarget target) {
    662         mDropTargets.add(target);
    663     }
    664 
    665     /**
    666      * Don't send drop events to <em>target</em> any more.
    667      */
    668     public void removeDropTarget(DropTarget target) {
    669         mDropTargets.remove(target);
    670     }
    671 
    672 }
    673