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<ApplicationInfo> apps, 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 (ApplicationInfo info : apps) { 336 // Added null checks to prevent NPE we've seen in the wild 337 if (dragInfo != null && 338 dragInfo.intent != null && 339 info.intent != null) { 340 boolean isSamePackage = dragInfo.getPackageName().equals( 341 info.getPackageName()); 342 if (isSamePackage) { 343 cancelDrag(); 344 return; 345 } 346 } 347 } 348 } 349 } 350 } 351 352 private void endDrag() { 353 if (mDragging) { 354 mDragging = false; 355 clearScrollRunnable(); 356 boolean isDeferred = false; 357 if (mDragObject.dragView != null) { 358 isDeferred = mDragObject.deferDragViewCleanupPostAnimation; 359 if (!isDeferred) { 360 mDragObject.dragView.remove(); 361 } 362 mDragObject.dragView = null; 363 } 364 365 // Only end the drag if we are not deferred 366 if (!isDeferred) { 367 for (DragListener listener : mListeners) { 368 listener.onDragEnd(); 369 } 370 } 371 } 372 373 releaseVelocityTracker(); 374 } 375 376 /** 377 * This only gets called as a result of drag view cleanup being deferred in endDrag(); 378 */ 379 void onDeferredEndDrag(DragView dragView) { 380 dragView.remove(); 381 382 // If we skipped calling onDragEnd() before, do it now 383 for (DragListener listener : mListeners) { 384 listener.onDragEnd(); 385 } 386 } 387 388 void onDeferredEndFling(DropTarget.DragObject d) { 389 d.dragSource.onFlingToDeleteCompleted(); 390 } 391 392 /** 393 * Clamps the position to the drag layer bounds. 394 */ 395 private int[] getClampedDragLayerPos(float x, float y) { 396 mLauncher.getDragLayer().getLocalVisibleRect(mDragLayerRect); 397 mTmpPoint[0] = (int) Math.max(mDragLayerRect.left, Math.min(x, mDragLayerRect.right - 1)); 398 mTmpPoint[1] = (int) Math.max(mDragLayerRect.top, Math.min(y, mDragLayerRect.bottom - 1)); 399 return mTmpPoint; 400 } 401 402 long getLastGestureUpTime() { 403 if (mDragging) { 404 return System.currentTimeMillis(); 405 } else { 406 return mLastTouchUpTime; 407 } 408 } 409 410 void resetLastGestureUpTime() { 411 mLastTouchUpTime = -1; 412 } 413 414 /** 415 * Call this from a drag source view. 416 */ 417 public boolean onInterceptTouchEvent(MotionEvent ev) { 418 @SuppressWarnings("all") // suppress dead code warning 419 final boolean debug = false; 420 if (debug) { 421 Log.d(Launcher.TAG, "DragController.onInterceptTouchEvent " + ev + " mDragging=" 422 + mDragging); 423 } 424 425 // Update the velocity tracker 426 acquireVelocityTrackerAndAddMovement(ev); 427 428 final int action = ev.getAction(); 429 final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY()); 430 final int dragLayerX = dragLayerPos[0]; 431 final int dragLayerY = dragLayerPos[1]; 432 433 switch (action) { 434 case MotionEvent.ACTION_MOVE: 435 break; 436 case MotionEvent.ACTION_DOWN: 437 // Remember location of down touch 438 mMotionDownX = dragLayerX; 439 mMotionDownY = dragLayerY; 440 mLastDropTarget = null; 441 break; 442 case MotionEvent.ACTION_UP: 443 mLastTouchUpTime = System.currentTimeMillis(); 444 if (mDragging) { 445 PointF vec = isFlingingToDelete(mDragObject.dragSource); 446 if (vec != null) { 447 dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec); 448 } else { 449 drop(dragLayerX, dragLayerY); 450 } 451 } 452 endDrag(); 453 break; 454 case MotionEvent.ACTION_CANCEL: 455 cancelDrag(); 456 break; 457 } 458 459 return mDragging; 460 } 461 462 /** 463 * Sets the view that should handle move events. 464 */ 465 void setMoveTarget(View view) { 466 mMoveTarget = view; 467 } 468 469 public boolean dispatchUnhandledMove(View focused, int direction) { 470 return mMoveTarget != null && mMoveTarget.dispatchUnhandledMove(focused, direction); 471 } 472 473 private void clearScrollRunnable() { 474 mHandler.removeCallbacks(mScrollRunnable); 475 if (mScrollState == SCROLL_WAITING_IN_ZONE) { 476 mScrollState = SCROLL_OUTSIDE_ZONE; 477 mScrollRunnable.setDirection(SCROLL_RIGHT); 478 mDragScroller.onExitScrollArea(); 479 mLauncher.getDragLayer().onExitScrollArea(); 480 } 481 } 482 483 private void handleMoveEvent(int x, int y) { 484 mDragObject.dragView.move(x, y); 485 486 // Drop on someone? 487 final int[] coordinates = mCoordinatesTemp; 488 DropTarget dropTarget = findDropTarget(x, y, coordinates); 489 mDragObject.x = coordinates[0]; 490 mDragObject.y = coordinates[1]; 491 if (dropTarget != null) { 492 DropTarget delegate = dropTarget.getDropTargetDelegate(mDragObject); 493 if (delegate != null) { 494 dropTarget = delegate; 495 } 496 497 if (mLastDropTarget != dropTarget) { 498 if (mLastDropTarget != null) { 499 mLastDropTarget.onDragExit(mDragObject); 500 } 501 dropTarget.onDragEnter(mDragObject); 502 } 503 dropTarget.onDragOver(mDragObject); 504 } else { 505 if (mLastDropTarget != null) { 506 mLastDropTarget.onDragExit(mDragObject); 507 } 508 } 509 mLastDropTarget = dropTarget; 510 511 // After a scroll, the touch point will still be in the scroll region. 512 // Rather than scrolling immediately, require a bit of twiddling to scroll again 513 final int slop = ViewConfiguration.get(mLauncher).getScaledWindowTouchSlop(); 514 mDistanceSinceScroll += 515 Math.sqrt(Math.pow(mLastTouch[0] - x, 2) + Math.pow(mLastTouch[1] - y, 2)); 516 mLastTouch[0] = x; 517 mLastTouch[1] = y; 518 final int delay = mDistanceSinceScroll < slop ? RESCROLL_DELAY : SCROLL_DELAY; 519 520 if (x < mScrollZone) { 521 if (mScrollState == SCROLL_OUTSIDE_ZONE) { 522 mScrollState = SCROLL_WAITING_IN_ZONE; 523 if (mDragScroller.onEnterScrollArea(x, y, SCROLL_LEFT)) { 524 mLauncher.getDragLayer().onEnterScrollArea(SCROLL_LEFT); 525 mScrollRunnable.setDirection(SCROLL_LEFT); 526 mHandler.postDelayed(mScrollRunnable, delay); 527 } 528 } 529 } else if (x > mScrollView.getWidth() - mScrollZone) { 530 if (mScrollState == SCROLL_OUTSIDE_ZONE) { 531 mScrollState = SCROLL_WAITING_IN_ZONE; 532 if (mDragScroller.onEnterScrollArea(x, y, SCROLL_RIGHT)) { 533 mLauncher.getDragLayer().onEnterScrollArea(SCROLL_RIGHT); 534 mScrollRunnable.setDirection(SCROLL_RIGHT); 535 mHandler.postDelayed(mScrollRunnable, delay); 536 } 537 } 538 } else { 539 clearScrollRunnable(); 540 } 541 } 542 543 public void forceMoveEvent() { 544 if (mDragging) { 545 handleMoveEvent(mDragObject.x, mDragObject.y); 546 } 547 } 548 549 /** 550 * Call this from a drag source view. 551 */ 552 public boolean onTouchEvent(MotionEvent ev) { 553 if (!mDragging) { 554 return false; 555 } 556 557 // Update the velocity tracker 558 acquireVelocityTrackerAndAddMovement(ev); 559 560 final int action = ev.getAction(); 561 final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY()); 562 final int dragLayerX = dragLayerPos[0]; 563 final int dragLayerY = dragLayerPos[1]; 564 565 switch (action) { 566 case MotionEvent.ACTION_DOWN: 567 // Remember where the motion event started 568 mMotionDownX = dragLayerX; 569 mMotionDownY = dragLayerY; 570 571 if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) { 572 mScrollState = SCROLL_WAITING_IN_ZONE; 573 mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY); 574 } else { 575 mScrollState = SCROLL_OUTSIDE_ZONE; 576 } 577 break; 578 case MotionEvent.ACTION_MOVE: 579 handleMoveEvent(dragLayerX, dragLayerY); 580 break; 581 case MotionEvent.ACTION_UP: 582 // Ensure that we've processed a move event at the current pointer location. 583 handleMoveEvent(dragLayerX, dragLayerY); 584 mHandler.removeCallbacks(mScrollRunnable); 585 586 if (mDragging) { 587 PointF vec = isFlingingToDelete(mDragObject.dragSource); 588 if (vec != null) { 589 dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec); 590 } else { 591 drop(dragLayerX, dragLayerY); 592 } 593 } 594 endDrag(); 595 break; 596 case MotionEvent.ACTION_CANCEL: 597 mHandler.removeCallbacks(mScrollRunnable); 598 cancelDrag(); 599 break; 600 } 601 602 return true; 603 } 604 605 /** 606 * Determines whether the user flung the current item to delete it. 607 * 608 * @return the vector at which the item was flung, or null if no fling was detected. 609 */ 610 private PointF isFlingingToDelete(DragSource source) { 611 if (mFlingToDeleteDropTarget == null) return null; 612 if (!source.supportsFlingToDelete()) return null; 613 614 ViewConfiguration config = ViewConfiguration.get(mLauncher); 615 mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity()); 616 617 if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) { 618 // Do a quick dot product test to ensure that we are flinging upwards 619 PointF vel = new PointF(mVelocityTracker.getXVelocity(), 620 mVelocityTracker.getYVelocity()); 621 PointF upVec = new PointF(0f, -1f); 622 float theta = (float) Math.acos(((vel.x * upVec.x) + (vel.y * upVec.y)) / 623 (vel.length() * upVec.length())); 624 if (theta <= Math.toRadians(MAX_FLING_DEGREES)) { 625 return vel; 626 } 627 } 628 return null; 629 } 630 631 private void dropOnFlingToDeleteTarget(float x, float y, PointF vel) { 632 final int[] coordinates = mCoordinatesTemp; 633 634 mDragObject.x = coordinates[0]; 635 mDragObject.y = coordinates[1]; 636 637 // Clean up dragging on the target if it's not the current fling delete target otherwise, 638 // start dragging to it. 639 if (mLastDropTarget != null && mFlingToDeleteDropTarget != mLastDropTarget) { 640 mLastDropTarget.onDragExit(mDragObject); 641 } 642 643 // Drop onto the fling-to-delete target 644 boolean accepted = false; 645 mFlingToDeleteDropTarget.onDragEnter(mDragObject); 646 // We must set dragComplete to true _only_ after we "enter" the fling-to-delete target for 647 // "drop" 648 mDragObject.dragComplete = true; 649 mFlingToDeleteDropTarget.onDragExit(mDragObject); 650 if (mFlingToDeleteDropTarget.acceptDrop(mDragObject)) { 651 mFlingToDeleteDropTarget.onFlingToDelete(mDragObject, mDragObject.x, mDragObject.y, 652 vel); 653 accepted = true; 654 } 655 mDragObject.dragSource.onDropCompleted((View) mFlingToDeleteDropTarget, mDragObject, true, 656 accepted); 657 } 658 659 private void drop(float x, float y) { 660 final int[] coordinates = mCoordinatesTemp; 661 final DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates); 662 663 mDragObject.x = coordinates[0]; 664 mDragObject.y = coordinates[1]; 665 boolean accepted = false; 666 if (dropTarget != null) { 667 mDragObject.dragComplete = true; 668 dropTarget.onDragExit(mDragObject); 669 if (dropTarget.acceptDrop(mDragObject)) { 670 dropTarget.onDrop(mDragObject); 671 accepted = true; 672 } 673 } 674 mDragObject.dragSource.onDropCompleted((View) dropTarget, mDragObject, false, accepted); 675 } 676 677 private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) { 678 final Rect r = mRectTemp; 679 680 final ArrayList<DropTarget> dropTargets = mDropTargets; 681 final int count = dropTargets.size(); 682 for (int i=count-1; i>=0; i--) { 683 DropTarget target = dropTargets.get(i); 684 if (!target.isDropEnabled()) 685 continue; 686 687 target.getHitRect(r); 688 689 // Convert the hit rect to DragLayer coordinates 690 target.getLocationInDragLayer(dropCoordinates); 691 r.offset(dropCoordinates[0] - target.getLeft(), dropCoordinates[1] - target.getTop()); 692 693 mDragObject.x = x; 694 mDragObject.y = y; 695 if (r.contains(x, y)) { 696 DropTarget delegate = target.getDropTargetDelegate(mDragObject); 697 if (delegate != null) { 698 target = delegate; 699 target.getLocationInDragLayer(dropCoordinates); 700 } 701 702 // Make dropCoordinates relative to the DropTarget 703 dropCoordinates[0] = x - dropCoordinates[0]; 704 dropCoordinates[1] = y - dropCoordinates[1]; 705 706 return target; 707 } 708 } 709 return null; 710 } 711 712 public void setDragScoller(DragScroller scroller) { 713 mDragScroller = scroller; 714 } 715 716 public void setWindowToken(IBinder token) { 717 mWindowToken = token; 718 } 719 720 /** 721 * Sets the drag listner which will be notified when a drag starts or ends. 722 */ 723 public void addDragListener(DragListener l) { 724 mListeners.add(l); 725 } 726 727 /** 728 * Remove a previously installed drag listener. 729 */ 730 public void removeDragListener(DragListener l) { 731 mListeners.remove(l); 732 } 733 734 /** 735 * Add a DropTarget to the list of potential places to receive drop events. 736 */ 737 public void addDropTarget(DropTarget target) { 738 mDropTargets.add(target); 739 } 740 741 /** 742 * Don't send drop events to <em>target</em> any more. 743 */ 744 public void removeDropTarget(DropTarget target) { 745 mDropTargets.remove(target); 746 } 747 748 /** 749 * Sets the current fling-to-delete drop target. 750 */ 751 public void setFlingToDeleteDropTarget(DropTarget target) { 752 mFlingToDeleteDropTarget = target; 753 } 754 755 private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) { 756 if (mVelocityTracker == null) { 757 mVelocityTracker = VelocityTracker.obtain(); 758 } 759 mVelocityTracker.addMovement(ev); 760 } 761 762 private void releaseVelocityTracker() { 763 if (mVelocityTracker != null) { 764 mVelocityTracker.recycle(); 765 mVelocityTracker = null; 766 } 767 } 768 769 /** 770 * Set which view scrolls for touch events near the edge of the screen. 771 */ 772 public void setScrollView(View v) { 773 mScrollView = v; 774 } 775 776 DragView getDragView() { 777 return mDragObject.dragView; 778 } 779 780 private class ScrollRunnable implements Runnable { 781 private int mDirection; 782 783 ScrollRunnable() { 784 } 785 786 public void run() { 787 if (mDragScroller != null) { 788 if (mDirection == SCROLL_LEFT) { 789 mDragScroller.scrollLeft(); 790 } else { 791 mDragScroller.scrollRight(); 792 } 793 mScrollState = SCROLL_OUTSIDE_ZONE; 794 mDistanceSinceScroll = 0; 795 mDragScroller.onExitScrollArea(); 796 mLauncher.getDragLayer().onExitScrollArea(); 797 798 if (isDragging()) { 799 // Force an update so that we can requeue the scroller if necessary 800 forceMoveEvent(); 801 } 802 } 803 } 804 805 void setDirection(int direction) { 806 mDirection = direction; 807 } 808 } 809 } 810