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