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