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