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