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