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