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 */ 146 void onDragStart(DropTarget.DragObject dragObject, DragOptions options); 147 148 /** 149 * The drag has ended 150 */ 151 void onDragEnd(); 152 } 153 154 /** 155 * Used to create a new DragLayer from XML. 156 */ 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 */ 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 */ 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 287 public boolean isDeferringDrag() { 288 return mIsDragDeferred; 289 } 290 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 */ 309 public boolean dispatchKeyEvent(KeyEvent event) { 310 return mDragDriver != null; 311 } 312 313 public boolean isDragging() { 314 return mDragDriver != null || (mOptions != null && mOptions.isAccessibleDrag); 315 } 316 317 public boolean isExternalDrag() { 318 return (mOptions != null && mOptions.systemDndStartPoint != null); 319 } 320 321 /** 322 * Stop dragging without dropping. 323 */ 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 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 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 */ 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 389 public void onDeferredEndFling(DropTarget.DragObject d) { 390 d.dragSource.onFlingToDeleteCompleted(); 391 } 392 393 /** 394 * Clamps the position to the drag layer bounds. 395 */ 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 403 public long getLastGestureUpTime() { 404 if (mDragDriver != null) { 405 return System.currentTimeMillis(); 406 } else { 407 return mLastTouchUpTime; 408 } 409 } 410 411 public void resetLastGestureUpTime() { 412 mLastTouchUpTime = -1; 413 } 414 415 @Override 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 423 public void onDriverDragExitWindow() { 424 if (mLastDropTarget != null) { 425 mLastDropTarget.onDragExit(mDragObject); 426 mLastDropTarget = null; 427 } 428 } 429 430 @Override 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 452 public void onDriverDragCancel() { 453 cancelDrag(); 454 } 455 456 /** 457 * Call this from a drag source view. 458 */ 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 */ 489 public boolean onDragEvent(DragEvent event) { 490 return mDragDriver != null && mDragDriver.onDragEvent(event); 491 } 492 493 /** 494 * Call this from a drag view. 495 */ 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 */ 505 public void setMoveTarget(View view) { 506 mMoveTarget = view; 507 } 508 509 public boolean dispatchUnhandledMove(View focused, int direction) { 510 return mMoveTarget != null && mMoveTarget.dispatchUnhandledMove(focused, direction); 511 } 512 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 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 545 public float getDistanceDragged() { 546 return mDistanceSinceScroll; 547 } 548 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 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 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