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