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