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