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 */ onDragStart(DragSource source, Object info, int dragAction)135 void onDragStart(DragSource source, Object info, int dragAction); 136 137 /** 138 * The drag has ended 139 */ onDragEnd()140 void onDragEnd(); 141 } 142 143 /** 144 * Used to create a new DragLayer from XML. 145 * 146 * @param context The application's context. 147 */ DragController(Launcher launcher)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 dragging()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 */ startDrag(View v, Bitmap bmp, DragSource source, Object dragInfo, int dragAction, Rect dragRegion, float initialDragViewScale)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 */ startDrag(Bitmap b, int dragLayerX, int dragLayerY, DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion, float initialDragViewScale)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 */ getViewBitmap(View v)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 */ dispatchKeyEvent(KeyEvent event)306 public boolean dispatchKeyEvent(KeyEvent event) { 307 return mDragging; 308 } 309 isDragging()310 public boolean isDragging() { 311 return mDragging; 312 } 313 314 /** 315 * Stop dragging without dropping. 316 */ cancelDrag()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 } onAppsRemoved(ArrayList<String> packageNames, Context context)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 endDrag()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 */ onDeferredEndDrag(DragView dragView)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 onDeferredEndFling(DropTarget.DragObject d)386 void onDeferredEndFling(DropTarget.DragObject d) { 387 d.dragSource.onFlingToDeleteCompleted(); 388 } 389 390 /** 391 * Clamps the position to the drag layer bounds. 392 */ getClampedDragLayerPos(float x, float y)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 getLastGestureUpTime()400 long getLastGestureUpTime() { 401 if (mDragging) { 402 return System.currentTimeMillis(); 403 } else { 404 return mLastTouchUpTime; 405 } 406 } 407 resetLastGestureUpTime()408 void resetLastGestureUpTime() { 409 mLastTouchUpTime = -1; 410 } 411 412 /** 413 * Call this from a drag source view. 414 */ onInterceptTouchEvent(MotionEvent ev)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 */ setMoveTarget(View view)463 void setMoveTarget(View view) { 464 mMoveTarget = view; 465 } 466 dispatchUnhandledMove(View focused, int direction)467 public boolean dispatchUnhandledMove(View focused, int direction) { 468 return mMoveTarget != null && mMoveTarget.dispatchUnhandledMove(focused, direction); 469 } 470 clearScrollRunnable()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 handleMoveEvent(int x, int y)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