DragController.java revision c843ca452a2c1667310246bf85c8eb50656ab7a6
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.launcher2; 18 19import android.content.Context; 20import android.graphics.Bitmap; 21import android.graphics.Point; 22import android.graphics.PointF; 23import android.graphics.Rect; 24import android.os.Handler; 25import android.os.IBinder; 26import android.os.Vibrator; 27import android.util.Log; 28import android.view.KeyEvent; 29import android.view.MotionEvent; 30import android.view.VelocityTracker; 31import android.view.View; 32import android.view.ViewConfiguration; 33import android.view.inputmethod.InputMethodManager; 34 35import com.android.launcher.R; 36 37import java.util.ArrayList; 38 39/** 40 * Class for initiating a drag within a view or across multiple views. 41 */ 42public class DragController { 43 private static final String TAG = "Launcher.DragController"; 44 45 /** Indicates the drag is a move. */ 46 public static int DRAG_ACTION_MOVE = 0; 47 48 /** Indicates the drag is a copy. */ 49 public static int DRAG_ACTION_COPY = 1; 50 51 private static final int SCROLL_DELAY = 500; 52 private static final int RESCROLL_DELAY = 750; 53 private static final int VIBRATE_DURATION = 15; 54 55 private static final boolean PROFILE_DRAWING_DURING_DRAG = false; 56 57 private static final int SCROLL_OUTSIDE_ZONE = 0; 58 private static final int SCROLL_WAITING_IN_ZONE = 1; 59 60 static final int SCROLL_NONE = -1; 61 static final int SCROLL_LEFT = 0; 62 static final int SCROLL_RIGHT = 1; 63 64 private static final float MAX_FLING_DEGREES = 35f; 65 private static final int FLING_TO_DELETE_THRESHOLD_Y_VELOCITY = -1500; 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 mLauncher = launcher; 150 mHandler = new Handler(); 151 mScrollZone = launcher.getResources().getDimensionPixelSize(R.dimen.scroll_zone); 152 mVelocityTracker = VelocityTracker.obtain(); 153 mVibrator = (Vibrator)launcher.getSystemService(Context.VIBRATOR_SERVICE); 154 155 float density = launcher.getResources().getDisplayMetrics().density; 156 mFlingToDeleteThresholdVelocity = (int) (FLING_TO_DELETE_THRESHOLD_Y_VELOCITY * density); 157 } 158 159 public boolean dragging() { 160 return mDragging; 161 } 162 163 /** 164 * Starts a drag. 165 * 166 * @param v The view that is being dragged 167 * @param bmp The bitmap that represents the view being dragged 168 * @param source An object representing where the drag originated 169 * @param dragInfo The data associated with the object that is being dragged 170 * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or 171 * {@link #DRAG_ACTION_COPY} 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, Object dragInfo, int dragAction, 176 Rect dragRegion, float initialDragViewScale) { 177 int[] loc = mCoordinatesTemp; 178 mLauncher.getDragLayer().getLocationInDragLayer(v, loc); 179 int dragLayerX = loc[0] + v.getPaddingLeft() + 180 (int) ((initialDragViewScale * bmp.getWidth() - bmp.getWidth()) / 2); 181 int dragLayerY = loc[1] + v.getPaddingTop() + 182 (int) ((initialDragViewScale * bmp.getHeight() - bmp.getHeight()) / 2); 183 184 startDrag(bmp, dragLayerX, dragLayerY, source, dragInfo, dragAction, null, dragRegion, 185 initialDragViewScale); 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 */ 206 public void startDrag(Bitmap b, int dragLayerX, int dragLayerY, 207 DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion, 208 float initialDragViewScale) { 209 if (PROFILE_DRAWING_DURING_DRAG) { 210 android.os.Debug.startMethodTracing("Launcher"); 211 } 212 213 // Hide soft keyboard, if visible 214 if (mInputMethodManager == null) { 215 mInputMethodManager = (InputMethodManager) 216 mLauncher.getSystemService(Context.INPUT_METHOD_SERVICE); 217 } 218 mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0); 219 220 for (DragListener listener : mListeners) { 221 listener.onDragStart(source, dragInfo, dragAction); 222 } 223 224 final int registrationX = mMotionDownX - dragLayerX; 225 final int registrationY = mMotionDownY - dragLayerY; 226 227 final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left; 228 final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top; 229 230 mDragging = true; 231 232 mDragObject = new DropTarget.DragObject(); 233 234 mDragObject.dragComplete = false; 235 mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft); 236 mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop); 237 mDragObject.dragSource = source; 238 mDragObject.dragInfo = dragInfo; 239 240 mVibrator.vibrate(VIBRATE_DURATION); 241 242 final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX, 243 registrationY, 0, 0, b.getWidth(), b.getHeight(), initialDragViewScale); 244 245 if (dragOffset != null) { 246 dragView.setDragVisualizeOffset(new Point(dragOffset)); 247 } 248 if (dragRegion != null) { 249 dragView.setDragRegion(new Rect(dragRegion)); 250 } 251 252 dragView.show(mMotionDownX, mMotionDownY); 253 handleMoveEvent(mMotionDownX, mMotionDownY); 254 } 255 256 /** 257 * Draw the view into a bitmap. 258 */ 259 Bitmap getViewBitmap(View v) { 260 v.clearFocus(); 261 v.setPressed(false); 262 263 boolean willNotCache = v.willNotCacheDrawing(); 264 v.setWillNotCacheDrawing(false); 265 266 // Reset the drawing cache background color to fully transparent 267 // for the duration of this operation 268 int color = v.getDrawingCacheBackgroundColor(); 269 v.setDrawingCacheBackgroundColor(0); 270 float alpha = v.getAlpha(); 271 v.setAlpha(1.0f); 272 273 if (color != 0) { 274 v.destroyDrawingCache(); 275 } 276 v.buildDrawingCache(); 277 Bitmap cacheBitmap = v.getDrawingCache(); 278 if (cacheBitmap == null) { 279 Log.e(TAG, "failed getViewBitmap(" + v + ")", new RuntimeException()); 280 return null; 281 } 282 283 Bitmap bitmap = Bitmap.createBitmap(cacheBitmap); 284 285 // Restore the view 286 v.destroyDrawingCache(); 287 v.setAlpha(alpha); 288 v.setWillNotCacheDrawing(willNotCache); 289 v.setDrawingCacheBackgroundColor(color); 290 291 return bitmap; 292 } 293 294 /** 295 * Call this from a drag source view like this: 296 * 297 * <pre> 298 * @Override 299 * public boolean dispatchKeyEvent(KeyEvent event) { 300 * return mDragController.dispatchKeyEvent(this, event) 301 * || super.dispatchKeyEvent(event); 302 * </pre> 303 */ 304 public boolean dispatchKeyEvent(KeyEvent event) { 305 return mDragging; 306 } 307 308 public boolean isDragging() { 309 return mDragging; 310 } 311 312 /** 313 * Stop dragging without dropping. 314 */ 315 public void cancelDrag() { 316 if (mDragging) { 317 if (mLastDropTarget != null) { 318 mLastDropTarget.onDragExit(mDragObject); 319 } 320 mDragObject.deferDragViewCleanupPostAnimation = false; 321 mDragObject.cancelled = true; 322 mDragObject.dragComplete = true; 323 mDragObject.dragSource.onDropCompleted(null, mDragObject, false, false); 324 } 325 endDrag(); 326 } 327 public void onAppsRemoved(ArrayList<ApplicationInfo> apps, Context context) { 328 // Cancel the current drag if we are removing an app that we are dragging 329 if (mDragObject != null) { 330 Object rawDragInfo = mDragObject.dragInfo; 331 if (rawDragInfo instanceof ShortcutInfo) { 332 ShortcutInfo dragInfo = (ShortcutInfo) rawDragInfo; 333 for (ApplicationInfo info : apps) { 334 // Added null checks to prevent NPE we've seen in the wild 335 if (dragInfo != null && 336 dragInfo.intent != null && 337 info.intent != null) { 338 boolean isSamePackage = dragInfo.getPackageName().equals( 339 info.getPackageName()); 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