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