• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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          */
onDragStart(DropTarget.DragObject dragObject, DragOptions options)108         void onDragStart(DropTarget.DragObject dragObject, DragOptions options);
109 
110         /**
111          * The drag has ended
112          */
onDragEnd()113         void onDragEnd();
114     }
115 
116     /**
117      * Used to create a new DragLayer from XML.
118      */
DragController(Launcher launcher)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      */
startDrag(Bitmap b, int dragLayerX, int dragLayerY, DragSource source, ItemInfo dragInfo, Point dragOffset, Rect dragRegion, float initialDragViewScale, float dragViewScaleOnDrop, DragOptions options)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 
callOnDragStart()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      */
dispatchKeyEvent(KeyEvent event)237     public boolean dispatchKeyEvent(KeyEvent event) {
238         return mDragDriver != null;
239     }
240 
isDragging()241     public boolean isDragging() {
242         return mDragDriver != null || (mOptions != null && mOptions.isAccessibleDrag);
243     }
244 
245     /**
246      * Stop dragging without dropping.
247      */
cancelDrag()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 
dispatchDropComplete(View dropTarget, boolean accepted)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 
onAppsRemoved(ItemInfoMatcher matcher)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 
endDrag()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 
animateDragViewToOriginalPosition(final Runnable onComplete, final View originalIcon, int duration)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 
callOnDragEnd()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      */
onDeferredEndDrag(DragView dragView)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      */
getClampedDragLayerPos(float x, float y)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 
getLastGestureUpTime()359     public long getLastGestureUpTime() {
360         if (mDragDriver != null) {
361             return System.currentTimeMillis();
362         } else {
363             return mLastTouchUpTime;
364         }
365     }
366 
resetLastGestureUpTime()367     public void resetLastGestureUpTime() {
368         mLastTouchUpTime = -1;
369     }
370 
371     @Override
onDriverDragMove(float x, float y)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
onDriverDragExitWindow()379     public void onDriverDragExitWindow() {
380         if (mLastDropTarget != null) {
381             mLastDropTarget.onDragExit(mDragObject);
382             mLastDropTarget = null;
383         }
384     }
385 
386     @Override
onDriverDragEnd(float x, float y)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
onDriverDragCancel()402     public void onDriverDragCancel() {
403         cancelDrag();
404     }
405 
406     /**
407      * Call this from a drag source view.
408      */
onControllerInterceptTouchEvent(MotionEvent ev)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      */
onDragEvent(long dragStartTime, DragEvent event)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      */
onDragViewAnimationEnd()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      */
setMoveTarget(View view)456     public void setMoveTarget(View view) {
457         mMoveTarget = view;
458     }
459 
dispatchUnhandledMove(View focused, int direction)460     public boolean dispatchUnhandledMove(View focused, int direction) {
461         return mMoveTarget != null && mMoveTarget.dispatchUnhandledMove(focused, direction);
462     }
463 
handleMoveEvent(int x, int y)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 
getDistanceDragged()485     public float getDistanceDragged() {
486         return mDistanceSinceScroll;
487     }
488 
forceTouchMove()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 
checkTouchMove(DropTarget dropTarget)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      */
onControllerTouchEvent(MotionEvent ev)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      */
prepareAccessibleDrag(int x, int y)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      */
completeAccessibleDrag(int[] location)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 
drop(DropTarget dropTarget, Runnable flingAnimation)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 
findDropTarget(int x, int y, int[] dropCoordinates)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 
setWindowToken(IBinder token)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      */
addDragListener(DragListener l)647     public void addDragListener(DragListener l) {
648         mListeners.add(l);
649     }
650 
651     /**
652      * Remove a previously installed drag listener.
653      */
removeDragListener(DragListener l)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      */
addDropTarget(DropTarget target)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      */
removeDropTarget(DropTarget target)668     public void removeDropTarget(DropTarget target) {
669         mDropTargets.remove(target);
670     }
671 
672 }
673