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 android.content.ComponentName; 20 import android.content.Context; 21 import android.content.res.Resources; 22 import android.graphics.Bitmap; 23 import android.graphics.Point; 24 import android.graphics.PointF; 25 import android.graphics.Rect; 26 import android.os.Handler; 27 import android.os.IBinder; 28 import android.view.DragEvent; 29 import android.view.HapticFeedbackConstants; 30 import android.view.KeyEvent; 31 import android.view.MotionEvent; 32 import android.view.VelocityTracker; 33 import android.view.View; 34 import android.view.ViewConfiguration; 35 import android.view.inputmethod.InputMethodManager; 36 37 import com.android.launcher3.DragSource; 38 import com.android.launcher3.DropTarget; 39 import com.android.launcher3.ItemInfo; 40 import com.android.launcher3.Launcher; 41 import com.android.launcher3.PagedView; 42 import com.android.launcher3.R; 43 import com.android.launcher3.ShortcutInfo; 44 import com.android.launcher3.Utilities; 45 import com.android.launcher3.accessibility.DragViewStateAnnouncer; 46 import com.android.launcher3.config.FeatureFlags; 47 import com.android.launcher3.util.ItemInfoMatcher; 48 import com.android.launcher3.util.Thunk; 49 import com.android.launcher3.util.TouchController; 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 String TAG = "Launcher.DragController"; 58 59 public static final int SCROLL_DELAY = 500; 60 public static final int RESCROLL_DELAY = PagedView.PAGE_SNAP_ANIMATION_DURATION + 150; 61 62 private static final boolean PROFILE_DRAWING_DURING_DRAG = false; 63 64 private static final int SCROLL_OUTSIDE_ZONE = 0; 65 private static final int SCROLL_WAITING_IN_ZONE = 1; 66 67 public static final int SCROLL_NONE = -1; 68 public static final int SCROLL_LEFT = 0; 69 public static final int SCROLL_RIGHT = 1; 70 71 private static final float MAX_FLING_DEGREES = 35f; 72 73 @Thunk Launcher mLauncher; 74 private Handler mHandler; 75 76 // temporaries to avoid gc thrash 77 private Rect mRectTemp = new Rect(); 78 private final int[] mCoordinatesTemp = new int[2]; 79 private final boolean mIsRtl; 80 81 /** 82 * Drag driver for the current drag/drop operation, or null if there is no active DND operation. 83 * It's null during accessible drag operations. 84 */ 85 private DragDriver mDragDriver = null; 86 87 /** Options controlling the drag behavior. */ 88 private DragOptions mOptions; 89 90 /** X coordinate of the down event. */ 91 private int mMotionDownX; 92 93 /** Y coordinate of the down event. */ 94 private int mMotionDownY; 95 96 /** the area at the edge of the screen that makes the workspace go left 97 * or right while you're dragging. 98 */ 99 private final int mScrollZone; 100 101 private DropTarget.DragObject mDragObject; 102 103 /** Who can receive drop events */ 104 private ArrayList<DropTarget> mDropTargets = new ArrayList<DropTarget>(); 105 private ArrayList<DragListener> mListeners = new ArrayList<DragListener>(); 106 private DropTarget mFlingToDeleteDropTarget; 107 108 /** The window token used as the parent for the DragView. */ 109 private IBinder mWindowToken; 110 111 /** The view that will be scrolled when dragging to the left and right edges of the screen. */ 112 private View mScrollView; 113 114 private View mMoveTarget; 115 116 @Thunk DragScroller mDragScroller; 117 @Thunk int mScrollState = SCROLL_OUTSIDE_ZONE; 118 private ScrollRunnable mScrollRunnable = new ScrollRunnable(); 119 120 private DropTarget mLastDropTarget; 121 122 private InputMethodManager mInputMethodManager; 123 124 @Thunk int mLastTouch[] = new int[2]; 125 @Thunk long mLastTouchUpTime = -1; 126 @Thunk int mDistanceSinceScroll = 0; 127 128 private int mTmpPoint[] = new int[2]; 129 private Rect mDragLayerRect = new Rect(); 130 131 protected final int mFlingToDeleteThresholdVelocity; 132 private VelocityTracker mVelocityTracker; 133 134 private boolean mIsDragDeferred; 135 136 /** 137 * Interface to receive notifications when a drag starts or stops 138 */ 139 public interface DragListener { 140 /** 141 * A drag has begun 142 * 143 * @param dragObject The object being dragged 144 * @param options Options used to start the drag 145 */ onDragStart(DropTarget.DragObject dragObject, DragOptions options)146 void onDragStart(DropTarget.DragObject dragObject, DragOptions options); 147 148 /** 149 * The drag has ended 150 */ onDragEnd()151 void onDragEnd(); 152 } 153 154 /** 155 * Used to create a new DragLayer from XML. 156 */ DragController(Launcher launcher)157 public DragController(Launcher launcher) { 158 Resources r = launcher.getResources(); 159 mLauncher = launcher; 160 mHandler = new Handler(); 161 mScrollZone = r.getDimensionPixelSize(R.dimen.scroll_zone); 162 mVelocityTracker = VelocityTracker.obtain(); 163 164 mFlingToDeleteThresholdVelocity = 165 r.getDimensionPixelSize(R.dimen.drag_flingToDeleteMinVelocity); 166 mIsRtl = Utilities.isRtl(r); 167 } 168 169 /** 170 * Starts a drag. 171 * 172 * @param v The view that is being dragged 173 * @param bmp The bitmap that represents the view being dragged 174 * @param source An object representing where the drag originated 175 * @param dragInfo The data associated with the object that is being dragged 176 * @param viewImageBounds the position of the image inside the view 177 */ startDrag(View v, Bitmap bmp, DragSource source, ItemInfo dragInfo, Rect viewImageBounds, float initialDragViewScale, DragOptions options)178 public void startDrag(View v, Bitmap bmp, DragSource source, ItemInfo dragInfo, 179 Rect viewImageBounds, float initialDragViewScale, DragOptions options) { 180 int[] loc = mCoordinatesTemp; 181 mLauncher.getDragLayer().getLocationInDragLayer(v, loc); 182 int dragLayerX = loc[0] + viewImageBounds.left 183 + (int) ((initialDragViewScale * bmp.getWidth() - bmp.getWidth()) / 2); 184 int dragLayerY = loc[1] + viewImageBounds.top 185 + (int) ((initialDragViewScale * bmp.getHeight() - bmp.getHeight()) / 2); 186 187 startDrag(bmp, dragLayerX, dragLayerY, source, dragInfo, null, 188 null, initialDragViewScale, options); 189 } 190 191 /** 192 * Starts a drag. 193 * 194 * @param b The bitmap to display as the drag image. It will be re-scaled to the 195 * enlarged size. 196 * @param dragLayerX The x position in the DragLayer of the left-top of the bitmap. 197 * @param dragLayerY The y position in the DragLayer of the left-top of the bitmap. 198 * @param source An object representing where the drag originated 199 * @param dragInfo The data associated with the object that is being dragged 200 * @param dragRegion Coordinates within the bitmap b for the position of item being dragged. 201 * Makes dragging feel more precise, e.g. you can clip out a transparent border 202 */ startDrag(Bitmap b, int dragLayerX, int dragLayerY, DragSource source, ItemInfo dragInfo, Point dragOffset, Rect dragRegion, float initialDragViewScale, DragOptions options)203 public DragView startDrag(Bitmap b, int dragLayerX, int dragLayerY, 204 DragSource source, ItemInfo dragInfo, Point dragOffset, Rect dragRegion, 205 float initialDragViewScale, DragOptions options) { 206 if (PROFILE_DRAWING_DURING_DRAG) { 207 android.os.Debug.startMethodTracing("Launcher"); 208 } 209 210 // Hide soft keyboard, if visible 211 if (mInputMethodManager == null) { 212 mInputMethodManager = (InputMethodManager) 213 mLauncher.getSystemService(Context.INPUT_METHOD_SERVICE); 214 } 215 mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0); 216 217 mOptions = options; 218 if (mOptions.systemDndStartPoint != null) { 219 mMotionDownX = mOptions.systemDndStartPoint.x; 220 mMotionDownY = mOptions.systemDndStartPoint.y; 221 } 222 223 final int registrationX = mMotionDownX - dragLayerX; 224 final int registrationY = mMotionDownY - dragLayerY; 225 226 final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left; 227 final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top; 228 229 mLastDropTarget = null; 230 231 mDragObject = new DropTarget.DragObject(); 232 233 mIsDragDeferred = !mOptions.deferDragCondition.shouldStartDeferredDrag(0); 234 235 final Resources res = mLauncher.getResources(); 236 final float scaleDps = FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND 237 ? res.getDimensionPixelSize(R.dimen.dragViewScale) 238 : mIsDragDeferred 239 ? res.getDimensionPixelSize(R.dimen.deferred_drag_view_scale) 240 : 0f; 241 final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX, 242 registrationY, initialDragViewScale, scaleDps); 243 244 mDragObject.dragComplete = false; 245 if (mOptions.isAccessibleDrag) { 246 // For an accessible drag, we assume the view is being dragged from the center. 247 mDragObject.xOffset = b.getWidth() / 2; 248 mDragObject.yOffset = b.getHeight() / 2; 249 mDragObject.accessibleDrag = true; 250 } else { 251 mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft); 252 mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop); 253 mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView); 254 255 mDragDriver = DragDriver.create(mLauncher, this, mDragObject, mOptions); 256 } 257 258 mDragObject.dragSource = source; 259 mDragObject.dragInfo = dragInfo; 260 mDragObject.originalDragInfo = new ItemInfo(); 261 mDragObject.originalDragInfo.copyFrom(dragInfo); 262 263 if (dragOffset != null) { 264 dragView.setDragVisualizeOffset(new Point(dragOffset)); 265 } 266 if (dragRegion != null) { 267 dragView.setDragRegion(new Rect(dragRegion)); 268 } 269 270 mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 271 dragView.show(mMotionDownX, mMotionDownY); 272 mDistanceSinceScroll = 0; 273 274 if (!mIsDragDeferred) { 275 startDeferredDrag(); 276 } else { 277 mOptions.deferDragCondition.onDeferredDragStart(); 278 } 279 280 mLastTouch[0] = mMotionDownX; 281 mLastTouch[1] = mMotionDownY; 282 handleMoveEvent(mMotionDownX, mMotionDownY); 283 mLauncher.getUserEventDispatcher().resetActionDurationMillis(); 284 return dragView; 285 } 286 isDeferringDrag()287 public boolean isDeferringDrag() { 288 return mIsDragDeferred; 289 } 290 startDeferredDrag()291 public void startDeferredDrag() { 292 for (DragListener listener : new ArrayList<>(mListeners)) { 293 listener.onDragStart(mDragObject, mOptions); 294 } 295 mOptions.deferDragCondition.onDragStart(); 296 mIsDragDeferred = false; 297 } 298 299 /** 300 * Call this from a drag source view like this: 301 * 302 * <pre> 303 * @Override 304 * public boolean dispatchKeyEvent(KeyEvent event) { 305 * return mDragController.dispatchKeyEvent(this, event) 306 * || super.dispatchKeyEvent(event); 307 * </pre> 308 */ dispatchKeyEvent(KeyEvent event)309 public boolean dispatchKeyEvent(KeyEvent event) { 310 return mDragDriver != null; 311 } 312 isDragging()313 public boolean isDragging() { 314 return mDragDriver != null || (mOptions != null && mOptions.isAccessibleDrag); 315 } 316 isExternalDrag()317 public boolean isExternalDrag() { 318 return (mOptions != null && mOptions.systemDndStartPoint != null); 319 } 320 321 /** 322 * Stop dragging without dropping. 323 */ cancelDrag()324 public void cancelDrag() { 325 if (isDragging()) { 326 if (mLastDropTarget != null) { 327 mLastDropTarget.onDragExit(mDragObject); 328 } 329 mDragObject.deferDragViewCleanupPostAnimation = false; 330 mDragObject.cancelled = true; 331 mDragObject.dragComplete = true; 332 mDragObject.dragSource.onDropCompleted(null, mDragObject, false, false); 333 } 334 endDrag(); 335 } 336 onAppsRemoved(ItemInfoMatcher matcher)337 public void onAppsRemoved(ItemInfoMatcher matcher) { 338 // Cancel the current drag if we are removing an app that we are dragging 339 if (mDragObject != null) { 340 ItemInfo dragInfo = mDragObject.dragInfo; 341 if (dragInfo instanceof ShortcutInfo) { 342 ComponentName cn = dragInfo.getTargetComponent(); 343 if (cn != null && matcher.matches(dragInfo, cn)) { 344 cancelDrag(); 345 } 346 } 347 } 348 } 349 endDrag()350 private void endDrag() { 351 if (isDragging()) { 352 mDragDriver = null; 353 mOptions = null; 354 clearScrollRunnable(); 355 boolean isDeferred = false; 356 if (mDragObject.dragView != null) { 357 isDeferred = mDragObject.deferDragViewCleanupPostAnimation; 358 if (!isDeferred) { 359 mDragObject.dragView.remove(); 360 } 361 mDragObject.dragView = null; 362 } 363 364 // Only end the drag if we are not deferred 365 if (!isDeferred) { 366 for (DragListener listener : new ArrayList<>(mListeners)) { 367 listener.onDragEnd(); 368 } 369 } 370 } 371 372 releaseVelocityTracker(); 373 } 374 375 /** 376 * This only gets called as a result of drag view cleanup being deferred in endDrag(); 377 */ onDeferredEndDrag(DragView dragView)378 void onDeferredEndDrag(DragView dragView) { 379 dragView.remove(); 380 381 if (mDragObject.deferDragViewCleanupPostAnimation) { 382 // If we skipped calling onDragEnd() before, do it now 383 for (DragListener listener : new ArrayList<>(mListeners)) { 384 listener.onDragEnd(); 385 } 386 } 387 } 388 onDeferredEndFling(DropTarget.DragObject d)389 public void onDeferredEndFling(DropTarget.DragObject d) { 390 d.dragSource.onFlingToDeleteCompleted(); 391 } 392 393 /** 394 * Clamps the position to the drag layer bounds. 395 */ getClampedDragLayerPos(float x, float y)396 private int[] getClampedDragLayerPos(float x, float y) { 397 mLauncher.getDragLayer().getLocalVisibleRect(mDragLayerRect); 398 mTmpPoint[0] = (int) Math.max(mDragLayerRect.left, Math.min(x, mDragLayerRect.right - 1)); 399 mTmpPoint[1] = (int) Math.max(mDragLayerRect.top, Math.min(y, mDragLayerRect.bottom - 1)); 400 return mTmpPoint; 401 } 402 getLastGestureUpTime()403 public long getLastGestureUpTime() { 404 if (mDragDriver != null) { 405 return System.currentTimeMillis(); 406 } else { 407 return mLastTouchUpTime; 408 } 409 } 410 resetLastGestureUpTime()411 public void resetLastGestureUpTime() { 412 mLastTouchUpTime = -1; 413 } 414 415 @Override onDriverDragMove(float x, float y)416 public void onDriverDragMove(float x, float y) { 417 final int[] dragLayerPos = getClampedDragLayerPos(x, y); 418 419 handleMoveEvent(dragLayerPos[0], dragLayerPos[1]); 420 } 421 422 @Override onDriverDragExitWindow()423 public void onDriverDragExitWindow() { 424 if (mLastDropTarget != null) { 425 mLastDropTarget.onDragExit(mDragObject); 426 mLastDropTarget = null; 427 } 428 } 429 430 @Override onDriverDragEnd(float x, float y, DropTarget dropTargetOverride)431 public void onDriverDragEnd(float x, float y, DropTarget dropTargetOverride) { 432 DropTarget dropTarget; 433 PointF vec = null; 434 435 if (dropTargetOverride != null) { 436 dropTarget = dropTargetOverride; 437 } else { 438 vec = isFlingingToDelete(mDragObject.dragSource); 439 if (vec != null) { 440 dropTarget = mFlingToDeleteDropTarget; 441 } else { 442 dropTarget = findDropTarget((int) x, (int) y, mCoordinatesTemp); 443 } 444 } 445 446 drop(dropTarget, x, y, vec); 447 448 endDrag(); 449 } 450 451 @Override onDriverDragCancel()452 public void onDriverDragCancel() { 453 cancelDrag(); 454 } 455 456 /** 457 * Call this from a drag source view. 458 */ onInterceptTouchEvent(MotionEvent ev)459 public boolean onInterceptTouchEvent(MotionEvent ev) { 460 if (mOptions != null && mOptions.isAccessibleDrag) { 461 return false; 462 } 463 464 // Update the velocity tracker 465 acquireVelocityTrackerAndAddMovement(ev); 466 467 final int action = ev.getAction(); 468 final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY()); 469 final int dragLayerX = dragLayerPos[0]; 470 final int dragLayerY = dragLayerPos[1]; 471 472 switch (action) { 473 case MotionEvent.ACTION_DOWN: 474 // Remember location of down touch 475 mMotionDownX = dragLayerX; 476 mMotionDownY = dragLayerY; 477 break; 478 case MotionEvent.ACTION_UP: 479 mLastTouchUpTime = System.currentTimeMillis(); 480 break; 481 } 482 483 return mDragDriver != null && mDragDriver.onInterceptTouchEvent(ev); 484 } 485 486 /** 487 * Call this from a drag source view. 488 */ onDragEvent(DragEvent event)489 public boolean onDragEvent(DragEvent event) { 490 return mDragDriver != null && mDragDriver.onDragEvent(event); 491 } 492 493 /** 494 * Call this from a drag view. 495 */ onDragViewAnimationEnd()496 public void onDragViewAnimationEnd() { 497 if (mDragDriver != null) { 498 mDragDriver.onDragViewAnimationEnd(); 499 } 500 } 501 502 /** 503 * Sets the view that should handle move events. 504 */ setMoveTarget(View view)505 public void setMoveTarget(View view) { 506 mMoveTarget = view; 507 } 508 dispatchUnhandledMove(View focused, int direction)509 public boolean dispatchUnhandledMove(View focused, int direction) { 510 return mMoveTarget != null && mMoveTarget.dispatchUnhandledMove(focused, direction); 511 } 512 clearScrollRunnable()513 private void clearScrollRunnable() { 514 mHandler.removeCallbacks(mScrollRunnable); 515 if (mScrollState == SCROLL_WAITING_IN_ZONE) { 516 mScrollState = SCROLL_OUTSIDE_ZONE; 517 mScrollRunnable.setDirection(SCROLL_RIGHT); 518 mDragScroller.onExitScrollArea(); 519 mLauncher.getDragLayer().onExitScrollArea(); 520 } 521 } 522 handleMoveEvent(int x, int y)523 private void handleMoveEvent(int x, int y) { 524 mDragObject.dragView.move(x, y); 525 526 // Drop on someone? 527 final int[] coordinates = mCoordinatesTemp; 528 DropTarget dropTarget = findDropTarget(x, y, coordinates); 529 mDragObject.x = coordinates[0]; 530 mDragObject.y = coordinates[1]; 531 checkTouchMove(dropTarget); 532 533 // Check if we are hovering over the scroll areas 534 mDistanceSinceScroll += Math.hypot(mLastTouch[0] - x, mLastTouch[1] - y); 535 mLastTouch[0] = x; 536 mLastTouch[1] = y; 537 checkScrollState(x, y); 538 539 if (mIsDragDeferred && mOptions.deferDragCondition.shouldStartDeferredDrag( 540 Math.hypot(x - mMotionDownX, y - mMotionDownY))) { 541 startDeferredDrag(); 542 } 543 } 544 getDistanceDragged()545 public float getDistanceDragged() { 546 return mDistanceSinceScroll; 547 } 548 forceTouchMove()549 public void forceTouchMove() { 550 int[] dummyCoordinates = mCoordinatesTemp; 551 DropTarget dropTarget = findDropTarget(mLastTouch[0], mLastTouch[1], dummyCoordinates); 552 mDragObject.x = dummyCoordinates[0]; 553 mDragObject.y = dummyCoordinates[1]; 554 checkTouchMove(dropTarget); 555 } 556 checkTouchMove(DropTarget dropTarget)557 private void checkTouchMove(DropTarget dropTarget) { 558 if (dropTarget != null) { 559 if (mLastDropTarget != dropTarget) { 560 if (mLastDropTarget != null) { 561 mLastDropTarget.onDragExit(mDragObject); 562 } 563 dropTarget.onDragEnter(mDragObject); 564 } 565 dropTarget.onDragOver(mDragObject); 566 } else { 567 if (mLastDropTarget != null) { 568 mLastDropTarget.onDragExit(mDragObject); 569 } 570 } 571 mLastDropTarget = dropTarget; 572 } 573 checkScrollState(int x, int y)574 @Thunk void checkScrollState(int x, int y) { 575 final int slop = ViewConfiguration.get(mLauncher).getScaledWindowTouchSlop(); 576 final int delay = mDistanceSinceScroll < slop ? RESCROLL_DELAY : SCROLL_DELAY; 577 final DragLayer dragLayer = mLauncher.getDragLayer(); 578 final int forwardDirection = mIsRtl ? SCROLL_RIGHT : SCROLL_LEFT; 579 final int backwardsDirection = mIsRtl ? SCROLL_LEFT : SCROLL_RIGHT; 580 581 if (x < mScrollZone) { 582 if (mScrollState == SCROLL_OUTSIDE_ZONE) { 583 mScrollState = SCROLL_WAITING_IN_ZONE; 584 if (mDragScroller.onEnterScrollArea(x, y, forwardDirection)) { 585 dragLayer.onEnterScrollArea(); 586 mScrollRunnable.setDirection(forwardDirection); 587 mHandler.postDelayed(mScrollRunnable, delay); 588 } 589 } 590 } else if (x > mScrollView.getWidth() - mScrollZone) { 591 if (mScrollState == SCROLL_OUTSIDE_ZONE) { 592 mScrollState = SCROLL_WAITING_IN_ZONE; 593 if (mDragScroller.onEnterScrollArea(x, y, backwardsDirection)) { 594 dragLayer.onEnterScrollArea(); 595 mScrollRunnable.setDirection(backwardsDirection); 596 mHandler.postDelayed(mScrollRunnable, delay); 597 } 598 } 599 } else { 600 clearScrollRunnable(); 601 } 602 } 603 604 /** 605 * Call this from a drag source view. 606 */ 607 public boolean onTouchEvent(MotionEvent ev) { 608 if (mDragDriver == null || mOptions == null || mOptions.isAccessibleDrag) { 609 return false; 610 } 611 612 // Update the velocity tracker 613 acquireVelocityTrackerAndAddMovement(ev); 614 615 final int action = ev.getAction(); 616 final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY()); 617 final int dragLayerX = dragLayerPos[0]; 618 final int dragLayerY = dragLayerPos[1]; 619 620 switch (action) { 621 case MotionEvent.ACTION_DOWN: 622 // Remember where the motion event started 623 mMotionDownX = dragLayerX; 624 mMotionDownY = dragLayerY; 625 626 if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) { 627 mScrollState = SCROLL_WAITING_IN_ZONE; 628 mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY); 629 } else { 630 mScrollState = SCROLL_OUTSIDE_ZONE; 631 } 632 break; 633 case MotionEvent.ACTION_UP: 634 case MotionEvent.ACTION_CANCEL: 635 mHandler.removeCallbacks(mScrollRunnable); 636 break; 637 } 638 639 return mDragDriver.onTouchEvent(ev); 640 } 641 642 /** 643 * Since accessible drag and drop won't cause the same sequence of touch events, we manually 644 * inject the appropriate state. 645 */ 646 public void prepareAccessibleDrag(int x, int y) { 647 mMotionDownX = x; 648 mMotionDownY = y; 649 } 650 651 /** 652 * As above, since accessible drag and drop won't cause the same sequence of touch events, 653 * we manually ensure appropriate drag and drop events get emulated for accessible drag. 654 */ 655 public void completeAccessibleDrag(int[] location) { 656 final int[] coordinates = mCoordinatesTemp; 657 658 // We make sure that we prime the target for drop. 659 DropTarget dropTarget = findDropTarget(location[0], location[1], coordinates); 660 mDragObject.x = coordinates[0]; 661 mDragObject.y = coordinates[1]; 662 checkTouchMove(dropTarget); 663 664 dropTarget.prepareAccessibilityDrop(); 665 // Perform the drop 666 drop(dropTarget, location[0], location[1], null); 667 endDrag(); 668 } 669 670 /** 671 * Determines whether the user flung the current item to delete it. 672 * 673 * @return the vector at which the item was flung, or null if no fling was detected. 674 */ 675 private PointF isFlingingToDelete(DragSource source) { 676 if (mFlingToDeleteDropTarget == null) return null; 677 if (!source.supportsFlingToDelete()) return null; 678 679 ViewConfiguration config = ViewConfiguration.get(mLauncher); 680 mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity()); 681 PointF vel = new PointF(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity()); 682 float theta = MAX_FLING_DEGREES + 1; 683 if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) { 684 // Do a quick dot product test to ensure that we are flinging upwards 685 PointF upVec = new PointF(0f, -1f); 686 theta = getAngleBetweenVectors(vel, upVec); 687 } else if (mLauncher.getDeviceProfile().isVerticalBarLayout() && 688 mVelocityTracker.getXVelocity() < mFlingToDeleteThresholdVelocity) { 689 // Remove icon is on left side instead of top, so check if we are flinging to the left. 690 PointF leftVec = new PointF(-1f, 0f); 691 theta = getAngleBetweenVectors(vel, leftVec); 692 } 693 if (theta <= Math.toRadians(MAX_FLING_DEGREES)) { 694 return vel; 695 } 696 return null; 697 } 698 699 private float getAngleBetweenVectors(PointF vec1, PointF vec2) { 700 return (float) Math.acos(((vec1.x * vec2.x) + (vec1.y * vec2.y)) / 701 (vec1.length() * vec2.length())); 702 } 703 704 void drop(DropTarget dropTarget, float x, float y, PointF flingVel) { 705 final int[] coordinates = mCoordinatesTemp; 706 707 mDragObject.x = coordinates[0]; 708 mDragObject.y = coordinates[1]; 709 710 // Move dragging to the final target. 711 if (dropTarget != mLastDropTarget) { 712 if (mLastDropTarget != null) { 713 mLastDropTarget.onDragExit(mDragObject); 714 } 715 mLastDropTarget = dropTarget; 716 if (dropTarget != null) { 717 dropTarget.onDragEnter(mDragObject); 718 } 719 } 720 721 mDragObject.dragComplete = true; 722 723 // Drop onto the target. 724 boolean accepted = false; 725 if (dropTarget != null) { 726 dropTarget.onDragExit(mDragObject); 727 if (dropTarget.acceptDrop(mDragObject)) { 728 if (flingVel != null) { 729 dropTarget.onFlingToDelete(mDragObject, flingVel); 730 } else { 731 dropTarget.onDrop(mDragObject); 732 } 733 accepted = true; 734 } 735 } 736 final View dropTargetAsView = dropTarget instanceof View ? (View) dropTarget : null; 737 mDragObject.dragSource.onDropCompleted( 738 dropTargetAsView, mDragObject, flingVel != null, accepted); 739 mLauncher.getUserEventDispatcher().logDragNDrop(mDragObject, dropTargetAsView); 740 if (mIsDragDeferred) { 741 mOptions.deferDragCondition.onDropBeforeDeferredDrag(); 742 } 743 } 744 745 private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) { 746 final Rect r = mRectTemp; 747 748 final ArrayList<DropTarget> dropTargets = mDropTargets; 749 final int count = dropTargets.size(); 750 for (int i=count-1; i>=0; i--) { 751 DropTarget target = dropTargets.get(i); 752 if (!target.isDropEnabled()) 753 continue; 754 755 target.getHitRectRelativeToDragLayer(r); 756 757 mDragObject.x = x; 758 mDragObject.y = y; 759 if (r.contains(x, y)) { 760 761 dropCoordinates[0] = x; 762 dropCoordinates[1] = y; 763 mLauncher.getDragLayer().mapCoordInSelfToDescendent((View) target, dropCoordinates); 764 765 return target; 766 } 767 } 768 return null; 769 } 770 771 public void setDragScoller(DragScroller scroller) { 772 mDragScroller = scroller; 773 } 774 775 public void setWindowToken(IBinder token) { 776 mWindowToken = token; 777 } 778 779 /** 780 * Sets the drag listner which will be notified when a drag starts or ends. 781 */ 782 public void addDragListener(DragListener l) { 783 mListeners.add(l); 784 } 785 786 /** 787 * Remove a previously installed drag listener. 788 */ 789 public void removeDragListener(DragListener l) { 790 mListeners.remove(l); 791 } 792 793 /** 794 * Add a DropTarget to the list of potential places to receive drop events. 795 */ 796 public void addDropTarget(DropTarget target) { 797 mDropTargets.add(target); 798 } 799 800 /** 801 * Don't send drop events to <em>target</em> any more. 802 */ 803 public void removeDropTarget(DropTarget target) { 804 mDropTargets.remove(target); 805 } 806 807 /** 808 * Sets the current fling-to-delete drop target. 809 */ 810 public void setFlingToDeleteDropTarget(DropTarget target) { 811 mFlingToDeleteDropTarget = target; 812 } 813 814 private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) { 815 if (mVelocityTracker == null) { 816 mVelocityTracker = VelocityTracker.obtain(); 817 } 818 mVelocityTracker.addMovement(ev); 819 } 820 821 private void releaseVelocityTracker() { 822 if (mVelocityTracker != null) { 823 mVelocityTracker.recycle(); 824 mVelocityTracker = null; 825 } 826 } 827 828 /** 829 * Set which view scrolls for touch events near the edge of the screen. 830 */ 831 public void setScrollView(View v) { 832 mScrollView = v; 833 } 834 835 private class ScrollRunnable implements Runnable { 836 private int mDirection; 837 838 ScrollRunnable() { 839 } 840 841 public void run() { 842 if (mDragScroller != null) { 843 if (mDirection == SCROLL_LEFT) { 844 mDragScroller.scrollLeft(); 845 } else { 846 mDragScroller.scrollRight(); 847 } 848 mScrollState = SCROLL_OUTSIDE_ZONE; 849 mDistanceSinceScroll = 0; 850 mDragScroller.onExitScrollArea(); 851 mLauncher.getDragLayer().onExitScrollArea(); 852 853 if (isDragging()) { 854 // Check the scroll again so that we can requeue the scroller if necessary 855 checkScrollState(mLastTouch[0], mLastTouch[1]); 856 } 857 } 858 } 859 860 void setDirection(int direction) { 861 mDirection = direction; 862 } 863 } 864 } 865