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