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