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