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