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