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