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