DragController.java revision 645764e3e5fa34d9adcddfc722d726b76f048306
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.Workspace; 47import com.android.launcher3.accessibility.DragViewStateAnnouncer; 48import com.android.launcher3.util.Thunk; 49import com.android.launcher3.util.TouchController; 50 51import java.util.ArrayList; 52import java.util.HashSet; 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 DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX, 251 registrationY, 0, 0, b.getWidth(), b.getHeight(), 252 initialDragViewScale); 253 254 mDragObject.dragComplete = false; 255 if (mIsAccessibleDrag) { 256 // For an accessible drag, we assume the view is being dragged from the center. 257 mDragObject.xOffset = b.getWidth() / 2; 258 mDragObject.yOffset = b.getHeight() / 2; 259 mDragObject.accessibleDrag = true; 260 } else { 261 mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft); 262 mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop); 263 mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView); 264 265 mDragDriver = DragDriver.create(this, dragInfo, dragView); 266 } 267 268 mDragObject.dragSource = source; 269 mDragObject.dragInfo = dragInfo; 270 271 if (dragOffset != null) { 272 dragView.setDragVisualizeOffset(new Point(dragOffset)); 273 } 274 if (dragRegion != null) { 275 dragView.setDragRegion(new Rect(dragRegion)); 276 } 277 278 mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 279 dragView.show(mMotionDownX, mMotionDownY); 280 mDistanceSinceScroll = 0; 281 mLastTouch[0] = mMotionDownX; 282 mLastTouch[1] = mMotionDownY; 283 handleMoveEvent(mMotionDownX, mMotionDownY); 284 return dragView; 285 } 286 287 /** 288 * Call this from a drag source view like this: 289 * 290 * <pre> 291 * @Override 292 * public boolean dispatchKeyEvent(KeyEvent event) { 293 * return mDragController.dispatchKeyEvent(this, event) 294 * || super.dispatchKeyEvent(event); 295 * </pre> 296 */ 297 public boolean dispatchKeyEvent(KeyEvent event) { 298 return mDragDriver != null; 299 } 300 301 public boolean isDragging() { 302 return mDragDriver != null || mIsAccessibleDrag; 303 } 304 305 /** 306 * Stop dragging without dropping. 307 */ 308 public void cancelDrag() { 309 if (isDragging()) { 310 if (mLastDropTarget != null) { 311 mLastDropTarget.onDragExit(mDragObject); 312 } 313 mDragObject.deferDragViewCleanupPostAnimation = false; 314 mDragObject.cancelled = true; 315 mDragObject.dragComplete = true; 316 mDragObject.dragSource.onDropCompleted(null, mDragObject, false, false); 317 } 318 endDrag(); 319 } 320 321 public void onAppsRemoved(final HashSet<String> packageNames, HashSet<ComponentName> cns) { 322 // Cancel the current drag if we are removing an app that we are dragging 323 if (mDragObject != null) { 324 Object rawDragInfo = mDragObject.dragInfo; 325 if (rawDragInfo instanceof ShortcutInfo) { 326 ShortcutInfo dragInfo = (ShortcutInfo) rawDragInfo; 327 for (ComponentName componentName : cns) { 328 if (dragInfo.intent != null) { 329 ComponentName cn = dragInfo.intent.getComponent(); 330 boolean isSameComponent = cn != null && (cn.equals(componentName) || 331 packageNames.contains(cn.getPackageName())); 332 if (isSameComponent) { 333 cancelDrag(); 334 return; 335 } 336 } 337 } 338 } 339 } 340 } 341 342 private void endDrag() { 343 if (isDragging()) { 344 mDragDriver = null; 345 mIsAccessibleDrag = false; 346 clearScrollRunnable(); 347 boolean isDeferred = false; 348 if (mDragObject.dragView != null) { 349 isDeferred = mDragObject.deferDragViewCleanupPostAnimation; 350 if (!isDeferred) { 351 mDragObject.dragView.remove(); 352 } 353 mDragObject.dragView = null; 354 } 355 356 // Only end the drag if we are not deferred 357 if (!isDeferred) { 358 for (DragListener listener : new ArrayList<>(mListeners)) { 359 listener.onDragEnd(); 360 } 361 } 362 } 363 364 releaseVelocityTracker(); 365 } 366 367 /** 368 * This only gets called as a result of drag view cleanup being deferred in endDrag(); 369 */ 370 void onDeferredEndDrag(DragView dragView) { 371 dragView.remove(); 372 373 if (mDragObject.deferDragViewCleanupPostAnimation) { 374 // If we skipped calling onDragEnd() before, do it now 375 for (DragListener listener : new ArrayList<>(mListeners)) { 376 listener.onDragEnd(); 377 } 378 } 379 } 380 381 public void onDeferredEndFling(DropTarget.DragObject d) { 382 d.dragSource.onFlingToDeleteCompleted(); 383 } 384 385 /** 386 * Clamps the position to the drag layer bounds. 387 */ 388 private int[] getClampedDragLayerPos(float x, float y) { 389 mLauncher.getDragLayer().getLocalVisibleRect(mDragLayerRect); 390 mTmpPoint[0] = (int) Math.max(mDragLayerRect.left, Math.min(x, mDragLayerRect.right - 1)); 391 mTmpPoint[1] = (int) Math.max(mDragLayerRect.top, Math.min(y, mDragLayerRect.bottom - 1)); 392 return mTmpPoint; 393 } 394 395 public long getLastGestureUpTime() { 396 if (mDragDriver != null) { 397 return System.currentTimeMillis(); 398 } else { 399 return mLastTouchUpTime; 400 } 401 } 402 403 public void resetLastGestureUpTime() { 404 mLastTouchUpTime = -1; 405 } 406 407 @Override 408 public void onDriverDragMove(float x, float y) { 409 final int[] dragLayerPos = getClampedDragLayerPos(x, y); 410 411 handleMoveEvent(dragLayerPos[0], dragLayerPos[1]); 412 } 413 414 @Override 415 public void onDriverDragExitWindow() { 416 if (mLastDropTarget != null) { 417 mLastDropTarget.onDragExit(mDragObject); 418 mLastDropTarget = null; 419 } 420 } 421 422 @Override 423 public void onDriverDragEnd(float x, float y, DropTarget dropTargetOverride) { 424 final int[] dragLayerPos = getClampedDragLayerPos(x, y); 425 final int dragLayerX = dragLayerPos[0]; 426 final int dragLayerY = dragLayerPos[1]; 427 428 DropTarget dropTarget; 429 PointF vec = null; 430 431 if (dropTargetOverride != null) { 432 dropTarget = dropTargetOverride; 433 } else { 434 vec = isFlingingToDelete(mDragObject.dragSource); 435 if (vec != null) { 436 dropTarget = mFlingToDeleteDropTarget; 437 } else { 438 dropTarget = findDropTarget((int) x, (int) y, mCoordinatesTemp); 439 } 440 } 441 442 drop(dropTarget, x, y, vec); 443 444 endDrag(); 445 } 446 447 @Override 448 public void onDriverDragCancel() { 449 cancelDrag(); 450 } 451 452 /** 453 * Call this from a drag source view. 454 */ 455 public boolean onInterceptTouchEvent(MotionEvent ev) { 456 @SuppressWarnings("all") // suppress dead code warning 457 final boolean debug = false; 458 if (debug) { 459 Log.d(Launcher.TAG, "DragController.onInterceptTouchEvent " + ev + " Dragging=" 460 + (mDragDriver != null)); 461 } 462 463 if (mIsAccessibleDrag) { 464 return false; 465 } 466 467 // Update the velocity tracker 468 acquireVelocityTrackerAndAddMovement(ev); 469 470 final int action = ev.getAction(); 471 final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY()); 472 final int dragLayerX = dragLayerPos[0]; 473 final int dragLayerY = dragLayerPos[1]; 474 475 switch (action) { 476 case MotionEvent.ACTION_DOWN: 477 // Remember location of down touch 478 mMotionDownX = dragLayerX; 479 mMotionDownY = dragLayerY; 480 break; 481 case MotionEvent.ACTION_UP: 482 mLastTouchUpTime = System.currentTimeMillis(); 483 break; 484 } 485 486 return mDragDriver != null && mDragDriver.onInterceptTouchEvent(ev); 487 } 488 489 /** 490 * Call this from a drag source view. 491 */ 492 public boolean onDragEvent(DragEvent event) { 493 return mDragDriver != null && mDragDriver.onDragEvent(event); 494 } 495 496 /** 497 * Call this from a drag view. 498 */ 499 public void onDragViewAnimationEnd() { 500 if (mDragDriver != null) { 501 mDragDriver.onDragViewAnimationEnd(); 502 } 503 } 504 505 /** 506 * Sets the view that should handle move events. 507 */ 508 public void setMoveTarget(View view) { 509 mMoveTarget = view; 510 } 511 512 public boolean dispatchUnhandledMove(View focused, int direction) { 513 return mMoveTarget != null && mMoveTarget.dispatchUnhandledMove(focused, direction); 514 } 515 516 private void clearScrollRunnable() { 517 mHandler.removeCallbacks(mScrollRunnable); 518 if (mScrollState == SCROLL_WAITING_IN_ZONE) { 519 mScrollState = SCROLL_OUTSIDE_ZONE; 520 mScrollRunnable.setDirection(SCROLL_RIGHT); 521 mDragScroller.onExitScrollArea(); 522 mLauncher.getDragLayer().onExitScrollArea(); 523 } 524 } 525 526 private void handleMoveEvent(int x, int y) { 527 mDragObject.dragView.move(x, y); 528 529 // Drop on someone? 530 final int[] coordinates = mCoordinatesTemp; 531 DropTarget dropTarget = findDropTarget(x, y, coordinates); 532 mDragObject.x = coordinates[0]; 533 mDragObject.y = coordinates[1]; 534 checkTouchMove(dropTarget); 535 536 // Check if we are hovering over the scroll areas 537 mDistanceSinceScroll += Math.hypot(mLastTouch[0] - x, mLastTouch[1] - y); 538 mLastTouch[0] = x; 539 mLastTouch[1] = y; 540 checkScrollState(x, y); 541 } 542 543 public float getDistanceDragged() { 544 return mDistanceSinceScroll; 545 } 546 547 public void forceTouchMove() { 548 int[] dummyCoordinates = mCoordinatesTemp; 549 DropTarget dropTarget = findDropTarget(mLastTouch[0], mLastTouch[1], dummyCoordinates); 550 mDragObject.x = dummyCoordinates[0]; 551 mDragObject.y = dummyCoordinates[1]; 552 checkTouchMove(dropTarget); 553 } 554 555 private void checkTouchMove(DropTarget dropTarget) { 556 if (dropTarget != null) { 557 if (mLastDropTarget != dropTarget) { 558 if (mLastDropTarget != null) { 559 mLastDropTarget.onDragExit(mDragObject); 560 } 561 dropTarget.onDragEnter(mDragObject); 562 } 563 dropTarget.onDragOver(mDragObject); 564 } else { 565 if (mLastDropTarget != null) { 566 mLastDropTarget.onDragExit(mDragObject); 567 } 568 } 569 mLastDropTarget = dropTarget; 570 } 571 572 @Thunk void checkScrollState(int x, int y) { 573 final int slop = ViewConfiguration.get(mLauncher).getScaledWindowTouchSlop(); 574 final int delay = mDistanceSinceScroll < slop ? RESCROLL_DELAY : SCROLL_DELAY; 575 final DragLayer dragLayer = mLauncher.getDragLayer(); 576 final int forwardDirection = mIsRtl ? SCROLL_RIGHT : SCROLL_LEFT; 577 final int backwardsDirection = mIsRtl ? SCROLL_LEFT : SCROLL_RIGHT; 578 579 if (x < mScrollZone) { 580 if (mScrollState == SCROLL_OUTSIDE_ZONE) { 581 mScrollState = SCROLL_WAITING_IN_ZONE; 582 if (mDragScroller.onEnterScrollArea(x, y, forwardDirection)) { 583 dragLayer.onEnterScrollArea(); 584 mScrollRunnable.setDirection(forwardDirection); 585 mHandler.postDelayed(mScrollRunnable, delay); 586 } 587 } 588 } else if (x > mScrollView.getWidth() - mScrollZone) { 589 if (mScrollState == SCROLL_OUTSIDE_ZONE) { 590 mScrollState = SCROLL_WAITING_IN_ZONE; 591 if (mDragScroller.onEnterScrollArea(x, y, backwardsDirection)) { 592 dragLayer.onEnterScrollArea(); 593 mScrollRunnable.setDirection(backwardsDirection); 594 mHandler.postDelayed(mScrollRunnable, delay); 595 } 596 } 597 } else { 598 clearScrollRunnable(); 599 } 600 } 601 602 /** 603 * Call this from a drag source view. 604 */ 605 public boolean onTouchEvent(MotionEvent ev) { 606 if (mDragDriver == null || mIsAccessibleDrag) { 607 return false; 608 } 609 610 // Update the velocity tracker 611 acquireVelocityTrackerAndAddMovement(ev); 612 613 final int action = ev.getAction(); 614 final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY()); 615 final int dragLayerX = dragLayerPos[0]; 616 final int dragLayerY = dragLayerPos[1]; 617 618 switch (action) { 619 case MotionEvent.ACTION_DOWN: 620 // Remember where the motion event started 621 mMotionDownX = dragLayerX; 622 mMotionDownY = dragLayerY; 623 624 if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) { 625 mScrollState = SCROLL_WAITING_IN_ZONE; 626 mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY); 627 } else { 628 mScrollState = SCROLL_OUTSIDE_ZONE; 629 } 630 break; 631 case MotionEvent.ACTION_UP: 632 case MotionEvent.ACTION_CANCEL: 633 mHandler.removeCallbacks(mScrollRunnable); 634 break; 635 } 636 637 return mDragDriver.onTouchEvent(ev); 638 } 639 640 /** 641 * Since accessible drag and drop won't cause the same sequence of touch events, we manually 642 * inject the appropriate state. 643 */ 644 public void prepareAccessibleDrag(int x, int y) { 645 mMotionDownX = x; 646 mMotionDownY = y; 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(dropTarget, location[0], location[1], null); 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 PointF vel = new PointF(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity()); 680 float theta = MAX_FLING_DEGREES + 1; 681 if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) { 682 // Do a quick dot product test to ensure that we are flinging upwards 683 PointF upVec = new PointF(0f, -1f); 684 theta = getAngleBetweenVectors(vel, upVec); 685 } else if (mLauncher.getDeviceProfile().isVerticalBarLayout() && 686 mVelocityTracker.getXVelocity() < mFlingToDeleteThresholdVelocity) { 687 // Remove icon is on left side instead of top, so check if we are flinging to the left. 688 PointF leftVec = new PointF(-1f, 0f); 689 theta = getAngleBetweenVectors(vel, leftVec); 690 } 691 if (theta <= Math.toRadians(MAX_FLING_DEGREES)) { 692 return vel; 693 } 694 return null; 695 } 696 697 private float getAngleBetweenVectors(PointF vec1, PointF vec2) { 698 return (float) Math.acos(((vec1.x * vec2.x) + (vec1.y * vec2.y)) / 699 (vec1.length() * vec2.length())); 700 } 701 702 void drop(DropTarget dropTarget, float x, float y, PointF flingVel) { 703 final int[] coordinates = mCoordinatesTemp; 704 705 mDragObject.x = coordinates[0]; 706 mDragObject.y = coordinates[1]; 707 708 // Move dragging to the final target. 709 if (dropTarget != mLastDropTarget) { 710 if (mLastDropTarget != null) { 711 mLastDropTarget.onDragExit(mDragObject); 712 } 713 mLastDropTarget = dropTarget; 714 if (dropTarget != null) { 715 dropTarget.onDragEnter(mDragObject); 716 } 717 } 718 719 mDragObject.dragComplete = true; 720 721 // Drop onto the target. 722 boolean accepted = false; 723 if (dropTarget != null) { 724 dropTarget.onDragExit(mDragObject); 725 if (dropTarget.acceptDrop(mDragObject)) { 726 if (flingVel != null) { 727 dropTarget.onFlingToDelete(mDragObject, flingVel); 728 } else { 729 dropTarget.onDrop(mDragObject); 730 } 731 accepted = true; 732 } 733 } 734 final View dropTargetAsView = dropTarget instanceof View ? (View) dropTarget : null; 735 mDragObject.dragSource.onDropCompleted( 736 dropTargetAsView, mDragObject, flingVel != null, 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 private class ScrollRunnable implements Runnable { 830 private int mDirection; 831 832 ScrollRunnable() { 833 } 834 835 public void run() { 836 if (mDragScroller != null) { 837 if (mDirection == SCROLL_LEFT) { 838 mDragScroller.scrollLeft(); 839 } else { 840 mDragScroller.scrollRight(); 841 } 842 mScrollState = SCROLL_OUTSIDE_ZONE; 843 mDistanceSinceScroll = 0; 844 mDragScroller.onExitScrollArea(); 845 mLauncher.getDragLayer().onExitScrollArea(); 846 847 if (isDragging()) { 848 // Check the scroll again so that we can requeue the scroller if necessary 849 checkScrollState(mLastTouch[0], mLastTouch[1]); 850 } 851 } 852 } 853 854 void setDirection(int direction) { 855 mDirection = direction; 856 } 857 } 858} 859