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