| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.launcher3.dragndrop; |
| |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.res.Resources; |
| import android.graphics.Bitmap; |
| import android.graphics.Point; |
| import android.graphics.PointF; |
| import android.graphics.Rect; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.util.Log; |
| import android.view.DragEvent; |
| import android.view.HapticFeedbackConstants; |
| import android.view.KeyEvent; |
| import android.view.MotionEvent; |
| import android.view.VelocityTracker; |
| import android.view.View; |
| import android.view.ViewConfiguration; |
| import android.view.inputmethod.InputMethodManager; |
| |
| import com.android.launcher3.DragSource; |
| import com.android.launcher3.DropTarget; |
| import com.android.launcher3.ItemInfo; |
| import com.android.launcher3.Launcher; |
| import com.android.launcher3.PagedView; |
| import com.android.launcher3.R; |
| import com.android.launcher3.ShortcutInfo; |
| import com.android.launcher3.Utilities; |
| import com.android.launcher3.Workspace; |
| import com.android.launcher3.accessibility.DragViewStateAnnouncer; |
| import com.android.launcher3.util.Thunk; |
| |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| |
| /** |
| * Class for initiating a drag within a view or across multiple views. |
| */ |
| public class DragController implements DragDriver.EventListener { |
| private static final String TAG = "Launcher.DragController"; |
| |
| /** Indicates the drag is a move. */ |
| public static int DRAG_ACTION_MOVE = 0; |
| |
| /** Indicates the drag is a copy. */ |
| public static int DRAG_ACTION_COPY = 1; |
| |
| public static final int SCROLL_DELAY = 500; |
| public static final int RESCROLL_DELAY = PagedView.PAGE_SNAP_ANIMATION_DURATION + 150; |
| |
| private static final boolean PROFILE_DRAWING_DURING_DRAG = false; |
| |
| private static final int SCROLL_OUTSIDE_ZONE = 0; |
| private static final int SCROLL_WAITING_IN_ZONE = 1; |
| |
| public static final int SCROLL_NONE = -1; |
| public static final int SCROLL_LEFT = 0; |
| public static final int SCROLL_RIGHT = 1; |
| |
| private static final float MAX_FLING_DEGREES = 35f; |
| |
| @Thunk Launcher mLauncher; |
| private Handler mHandler; |
| |
| // temporaries to avoid gc thrash |
| private Rect mRectTemp = new Rect(); |
| private final int[] mCoordinatesTemp = new int[2]; |
| private final boolean mIsRtl; |
| |
| /** |
| * Drag driver for the current drag/drop operation, or null if there is no active DND operation. |
| * It's null during accessible drag operations. |
| */ |
| private DragDriver mDragDriver = null; |
| |
| /** Whether or not an accessible drag operation is in progress. */ |
| private boolean mIsAccessibleDrag; |
| |
| /** X coordinate of the down event. */ |
| private int mMotionDownX; |
| |
| /** Y coordinate of the down event. */ |
| private int mMotionDownY; |
| |
| /** the area at the edge of the screen that makes the workspace go left |
| * or right while you're dragging. |
| */ |
| private final int mScrollZone; |
| |
| private DropTarget.DragObject mDragObject; |
| |
| /** Who can receive drop events */ |
| private ArrayList<DropTarget> mDropTargets = new ArrayList<DropTarget>(); |
| private ArrayList<DragListener> mListeners = new ArrayList<DragListener>(); |
| private DropTarget mFlingToDeleteDropTarget; |
| |
| /** The window token used as the parent for the DragView. */ |
| private IBinder mWindowToken; |
| |
| /** The view that will be scrolled when dragging to the left and right edges of the screen. */ |
| private View mScrollView; |
| |
| private View mMoveTarget; |
| |
| @Thunk DragScroller mDragScroller; |
| @Thunk int mScrollState = SCROLL_OUTSIDE_ZONE; |
| private ScrollRunnable mScrollRunnable = new ScrollRunnable(); |
| |
| private DropTarget mLastDropTarget; |
| |
| private InputMethodManager mInputMethodManager; |
| |
| @Thunk int mLastTouch[] = new int[2]; |
| @Thunk long mLastTouchUpTime = -1; |
| @Thunk int mDistanceSinceScroll = 0; |
| |
| private int mTmpPoint[] = new int[2]; |
| private Rect mDragLayerRect = new Rect(); |
| |
| protected final int mFlingToDeleteThresholdVelocity; |
| private VelocityTracker mVelocityTracker; |
| |
| /** |
| * Interface to receive notifications when a drag starts or stops |
| */ |
| public interface DragListener { |
| /** |
| * A drag has begun |
| * |
| * @param source An object representing where the drag originated |
| * @param info The data associated with the object that is being dragged |
| * @param dragAction The drag action: either {@link DragController#DRAG_ACTION_MOVE} |
| * or {@link DragController#DRAG_ACTION_COPY} |
| */ |
| void onDragStart(DragSource source, ItemInfo info, int dragAction); |
| |
| /** |
| * The drag has ended |
| */ |
| void onDragEnd(); |
| } |
| |
| /** |
| * Used to create a new DragLayer from XML. |
| * |
| * @param context The application's context. |
| */ |
| public DragController(Launcher launcher) { |
| Resources r = launcher.getResources(); |
| mLauncher = launcher; |
| mHandler = new Handler(); |
| mScrollZone = r.getDimensionPixelSize(R.dimen.scroll_zone); |
| mVelocityTracker = VelocityTracker.obtain(); |
| |
| mFlingToDeleteThresholdVelocity = |
| r.getDimensionPixelSize(R.dimen.drag_flingToDeleteMinVelocity); |
| mIsRtl = Utilities.isRtl(r); |
| } |
| |
| /** |
| * Starts a drag. |
| * |
| * @param v The view that is being dragged |
| * @param bmp The bitmap that represents the view being dragged |
| * @param source An object representing where the drag originated |
| * @param dragInfo The data associated with the object that is being dragged |
| * @param viewImageBounds the position of the image inside the view |
| * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or |
| * {@link #DRAG_ACTION_COPY} |
| */ |
| public void startDrag(View v, Bitmap bmp, DragSource source, ItemInfo dragInfo, |
| Rect viewImageBounds, int dragAction, float initialDragViewScale) { |
| int[] loc = mCoordinatesTemp; |
| mLauncher.getDragLayer().getLocationInDragLayer(v, loc); |
| int dragLayerX = loc[0] + viewImageBounds.left |
| + (int) ((initialDragViewScale * bmp.getWidth() - bmp.getWidth()) / 2); |
| int dragLayerY = loc[1] + viewImageBounds.top |
| + (int) ((initialDragViewScale * bmp.getHeight() - bmp.getHeight()) / 2); |
| |
| startDrag(bmp, dragLayerX, dragLayerY, source, dragInfo, dragAction, null, |
| null, initialDragViewScale, false); |
| |
| if (dragAction == DRAG_ACTION_MOVE) { |
| v.setVisibility(View.GONE); |
| } |
| } |
| |
| /** |
| * Starts a drag. |
| * |
| * @param b The bitmap to display as the drag image. It will be re-scaled to the |
| * enlarged size. |
| * @param dragLayerX The x position in the DragLayer of the left-top of the bitmap. |
| * @param dragLayerY The y position in the DragLayer of the left-top of the bitmap. |
| * @param source An object representing where the drag originated |
| * @param dragInfo The data associated with the object that is being dragged |
| * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or |
| * {@link #DRAG_ACTION_COPY} |
| * @param dragRegion Coordinates within the bitmap b for the position of item being dragged. |
| * Makes dragging feel more precise, e.g. you can clip out a transparent border |
| * @param accessible whether this drag should occur in accessibility mode |
| */ |
| public DragView startDrag(Bitmap b, int dragLayerX, int dragLayerY, |
| DragSource source, ItemInfo dragInfo, int dragAction, Point dragOffset, Rect dragRegion, |
| float initialDragViewScale, boolean accessible) { |
| if (PROFILE_DRAWING_DURING_DRAG) { |
| android.os.Debug.startMethodTracing("Launcher"); |
| } |
| |
| // Hide soft keyboard, if visible |
| if (mInputMethodManager == null) { |
| mInputMethodManager = (InputMethodManager) |
| mLauncher.getSystemService(Context.INPUT_METHOD_SERVICE); |
| } |
| mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0); |
| |
| for (DragListener listener : mListeners) { |
| listener.onDragStart(source, dragInfo, dragAction); |
| } |
| |
| final int registrationX = mMotionDownX - dragLayerX; |
| final int registrationY = mMotionDownY - dragLayerY; |
| |
| final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left; |
| final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top; |
| |
| mIsAccessibleDrag = accessible; |
| mLastDropTarget = null; |
| |
| mDragObject = new DropTarget.DragObject(); |
| |
| final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX, |
| registrationY, 0, 0, b.getWidth(), b.getHeight(), |
| initialDragViewScale); |
| |
| mDragObject.dragComplete = false; |
| if (mIsAccessibleDrag) { |
| // For an accessible drag, we assume the view is being dragged from the center. |
| mDragObject.xOffset = b.getWidth() / 2; |
| mDragObject.yOffset = b.getHeight() / 2; |
| mDragObject.accessibleDrag = true; |
| } else { |
| mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft); |
| mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop); |
| mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView); |
| |
| mDragDriver = DragDriver.create(this, dragInfo, dragView); |
| } |
| |
| mDragObject.dragSource = source; |
| mDragObject.dragInfo = dragInfo; |
| |
| if (dragOffset != null) { |
| dragView.setDragVisualizeOffset(new Point(dragOffset)); |
| } |
| if (dragRegion != null) { |
| dragView.setDragRegion(new Rect(dragRegion)); |
| } |
| |
| mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); |
| dragView.show(mMotionDownX, mMotionDownY); |
| mDistanceSinceScroll = 0; |
| mLastTouch[0] = mMotionDownX; |
| mLastTouch[1] = mMotionDownY; |
| handleMoveEvent(mMotionDownX, mMotionDownY); |
| return dragView; |
| } |
| |
| /** |
| * Call this from a drag source view like this: |
| * |
| * <pre> |
| * @Override |
| * public boolean dispatchKeyEvent(KeyEvent event) { |
| * return mDragController.dispatchKeyEvent(this, event) |
| * || super.dispatchKeyEvent(event); |
| * </pre> |
| */ |
| public boolean dispatchKeyEvent(KeyEvent event) { |
| return mDragDriver != null; |
| } |
| |
| public boolean isDragging() { |
| return mDragDriver != null || mIsAccessibleDrag; |
| } |
| |
| /** |
| * Stop dragging without dropping. |
| */ |
| public void cancelDrag() { |
| if (isDragging()) { |
| if (mLastDropTarget != null) { |
| mLastDropTarget.onDragExit(mDragObject); |
| } |
| mDragObject.deferDragViewCleanupPostAnimation = false; |
| mDragObject.cancelled = true; |
| mDragObject.dragComplete = true; |
| mDragObject.dragSource.onDropCompleted(null, mDragObject, false, false); |
| } |
| endDrag(); |
| } |
| |
| public void onAppsRemoved(final HashSet<String> packageNames, HashSet<ComponentName> cns) { |
| // Cancel the current drag if we are removing an app that we are dragging |
| if (mDragObject != null) { |
| Object rawDragInfo = mDragObject.dragInfo; |
| if (rawDragInfo instanceof ShortcutInfo) { |
| ShortcutInfo dragInfo = (ShortcutInfo) rawDragInfo; |
| for (ComponentName componentName : cns) { |
| if (dragInfo.intent != null) { |
| ComponentName cn = dragInfo.intent.getComponent(); |
| boolean isSameComponent = cn != null && (cn.equals(componentName) || |
| packageNames.contains(cn.getPackageName())); |
| if (isSameComponent) { |
| cancelDrag(); |
| return; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private void endDrag() { |
| if (isDragging()) { |
| mDragDriver = null; |
| mIsAccessibleDrag = false; |
| clearScrollRunnable(); |
| boolean isDeferred = false; |
| if (mDragObject.dragView != null) { |
| isDeferred = mDragObject.deferDragViewCleanupPostAnimation; |
| if (!isDeferred) { |
| mDragObject.dragView.remove(); |
| } |
| mDragObject.dragView = null; |
| } |
| |
| // Only end the drag if we are not deferred |
| if (!isDeferred) { |
| for (DragListener listener : new ArrayList<>(mListeners)) { |
| listener.onDragEnd(); |
| } |
| } |
| } |
| |
| releaseVelocityTracker(); |
| } |
| |
| /** |
| * This only gets called as a result of drag view cleanup being deferred in endDrag(); |
| */ |
| void onDeferredEndDrag(DragView dragView) { |
| dragView.remove(); |
| |
| if (mDragObject.deferDragViewCleanupPostAnimation) { |
| // If we skipped calling onDragEnd() before, do it now |
| for (DragListener listener : new ArrayList<>(mListeners)) { |
| listener.onDragEnd(); |
| } |
| } |
| } |
| |
| public void onDeferredEndFling(DropTarget.DragObject d) { |
| d.dragSource.onFlingToDeleteCompleted(); |
| } |
| |
| /** |
| * Clamps the position to the drag layer bounds. |
| */ |
| private int[] getClampedDragLayerPos(float x, float y) { |
| mLauncher.getDragLayer().getLocalVisibleRect(mDragLayerRect); |
| mTmpPoint[0] = (int) Math.max(mDragLayerRect.left, Math.min(x, mDragLayerRect.right - 1)); |
| mTmpPoint[1] = (int) Math.max(mDragLayerRect.top, Math.min(y, mDragLayerRect.bottom - 1)); |
| return mTmpPoint; |
| } |
| |
| public long getLastGestureUpTime() { |
| if (mDragDriver != null) { |
| return System.currentTimeMillis(); |
| } else { |
| return mLastTouchUpTime; |
| } |
| } |
| |
| public void resetLastGestureUpTime() { |
| mLastTouchUpTime = -1; |
| } |
| |
| @Override |
| public void onDriverDragMove(float x, float y) { |
| final int[] dragLayerPos = getClampedDragLayerPos(x, y); |
| |
| handleMoveEvent(dragLayerPos[0], dragLayerPos[1]); |
| } |
| |
| @Override |
| public void onDriverDragExitWindow() { |
| if (mLastDropTarget != null) { |
| mLastDropTarget.onDragExit(mDragObject); |
| mLastDropTarget = null; |
| } |
| } |
| |
| @Override |
| public void onDriverDragEnd(float x, float y, DropTarget dropTargetOverride) { |
| final int[] dragLayerPos = getClampedDragLayerPos(x, y); |
| final int dragLayerX = dragLayerPos[0]; |
| final int dragLayerY = dragLayerPos[1]; |
| |
| DropTarget dropTarget; |
| PointF vec = null; |
| |
| if (dropTargetOverride != null) { |
| dropTarget = dropTargetOverride; |
| } else { |
| vec = isFlingingToDelete(mDragObject.dragSource); |
| if (vec != null) { |
| dropTarget = mFlingToDeleteDropTarget; |
| } else { |
| dropTarget = findDropTarget((int) x, (int) y, mCoordinatesTemp); |
| } |
| } |
| |
| drop(dropTarget, x, y, vec); |
| |
| endDrag(); |
| } |
| |
| @Override |
| public void onDriverDragCancel() { |
| cancelDrag(); |
| } |
| |
| /** |
| * Call this from a drag source view. |
| */ |
| public boolean onInterceptTouchEvent(MotionEvent ev) { |
| @SuppressWarnings("all") // suppress dead code warning |
| final boolean debug = false; |
| if (debug) { |
| Log.d(Launcher.TAG, "DragController.onInterceptTouchEvent " + ev + " Dragging=" |
| + (mDragDriver != null)); |
| } |
| |
| if (mIsAccessibleDrag) { |
| return false; |
| } |
| |
| // Update the velocity tracker |
| acquireVelocityTrackerAndAddMovement(ev); |
| |
| final int action = ev.getAction(); |
| final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY()); |
| final int dragLayerX = dragLayerPos[0]; |
| final int dragLayerY = dragLayerPos[1]; |
| |
| switch (action) { |
| case MotionEvent.ACTION_DOWN: |
| // Remember location of down touch |
| mMotionDownX = dragLayerX; |
| mMotionDownY = dragLayerY; |
| break; |
| case MotionEvent.ACTION_UP: |
| mLastTouchUpTime = System.currentTimeMillis(); |
| break; |
| } |
| |
| return mDragDriver != null && mDragDriver.onInterceptTouchEvent(ev); |
| } |
| |
| /** |
| * Call this from a drag source view. |
| */ |
| public boolean onDragEvent(DragEvent event) { |
| return mDragDriver != null && mDragDriver.onDragEvent(event); |
| } |
| |
| /** |
| * Call this from a drag view. |
| */ |
| public void onDragViewAnimationEnd() { |
| if (mDragDriver != null) { |
| mDragDriver.onDragViewAnimationEnd(); |
| } |
| } |
| |
| /** |
| * Sets the view that should handle move events. |
| */ |
| public void setMoveTarget(View view) { |
| mMoveTarget = view; |
| } |
| |
| public boolean dispatchUnhandledMove(View focused, int direction) { |
| return mMoveTarget != null && mMoveTarget.dispatchUnhandledMove(focused, direction); |
| } |
| |
| private void clearScrollRunnable() { |
| mHandler.removeCallbacks(mScrollRunnable); |
| if (mScrollState == SCROLL_WAITING_IN_ZONE) { |
| mScrollState = SCROLL_OUTSIDE_ZONE; |
| mScrollRunnable.setDirection(SCROLL_RIGHT); |
| mDragScroller.onExitScrollArea(); |
| mLauncher.getDragLayer().onExitScrollArea(); |
| } |
| } |
| |
| private void handleMoveEvent(int x, int y) { |
| mDragObject.dragView.move(x, y); |
| |
| // Drop on someone? |
| final int[] coordinates = mCoordinatesTemp; |
| DropTarget dropTarget = findDropTarget(x, y, coordinates); |
| mDragObject.x = coordinates[0]; |
| mDragObject.y = coordinates[1]; |
| checkTouchMove(dropTarget); |
| |
| // Check if we are hovering over the scroll areas |
| mDistanceSinceScroll += Math.hypot(mLastTouch[0] - x, mLastTouch[1] - y); |
| mLastTouch[0] = x; |
| mLastTouch[1] = y; |
| checkScrollState(x, y); |
| } |
| |
| public float getDistanceDragged() { |
| return mDistanceSinceScroll; |
| } |
| |
| public void forceTouchMove() { |
| int[] dummyCoordinates = mCoordinatesTemp; |
| DropTarget dropTarget = findDropTarget(mLastTouch[0], mLastTouch[1], dummyCoordinates); |
| mDragObject.x = dummyCoordinates[0]; |
| mDragObject.y = dummyCoordinates[1]; |
| checkTouchMove(dropTarget); |
| } |
| |
| private void checkTouchMove(DropTarget dropTarget) { |
| if (dropTarget != null) { |
| if (mLastDropTarget != dropTarget) { |
| if (mLastDropTarget != null) { |
| mLastDropTarget.onDragExit(mDragObject); |
| } |
| dropTarget.onDragEnter(mDragObject); |
| } |
| dropTarget.onDragOver(mDragObject); |
| } else { |
| if (mLastDropTarget != null) { |
| mLastDropTarget.onDragExit(mDragObject); |
| } |
| } |
| mLastDropTarget = dropTarget; |
| } |
| |
| @Thunk void checkScrollState(int x, int y) { |
| final int slop = ViewConfiguration.get(mLauncher).getScaledWindowTouchSlop(); |
| final int delay = mDistanceSinceScroll < slop ? RESCROLL_DELAY : SCROLL_DELAY; |
| final DragLayer dragLayer = mLauncher.getDragLayer(); |
| final int forwardDirection = mIsRtl ? SCROLL_RIGHT : SCROLL_LEFT; |
| final int backwardsDirection = mIsRtl ? SCROLL_LEFT : SCROLL_RIGHT; |
| |
| if (x < mScrollZone) { |
| if (mScrollState == SCROLL_OUTSIDE_ZONE) { |
| mScrollState = SCROLL_WAITING_IN_ZONE; |
| if (mDragScroller.onEnterScrollArea(x, y, forwardDirection)) { |
| dragLayer.onEnterScrollArea(); |
| mScrollRunnable.setDirection(forwardDirection); |
| mHandler.postDelayed(mScrollRunnable, delay); |
| } |
| } |
| } else if (x > mScrollView.getWidth() - mScrollZone) { |
| if (mScrollState == SCROLL_OUTSIDE_ZONE) { |
| mScrollState = SCROLL_WAITING_IN_ZONE; |
| if (mDragScroller.onEnterScrollArea(x, y, backwardsDirection)) { |
| dragLayer.onEnterScrollArea(); |
| mScrollRunnable.setDirection(backwardsDirection); |
| mHandler.postDelayed(mScrollRunnable, delay); |
| } |
| } |
| } else { |
| clearScrollRunnable(); |
| } |
| } |
| |
| /** |
| * Call this from a drag source view. |
| */ |
| public boolean onTouchEvent(MotionEvent ev) { |
| if (mDragDriver == null || mIsAccessibleDrag) { |
| return false; |
| } |
| |
| // Update the velocity tracker |
| acquireVelocityTrackerAndAddMovement(ev); |
| |
| final int action = ev.getAction(); |
| final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY()); |
| final int dragLayerX = dragLayerPos[0]; |
| final int dragLayerY = dragLayerPos[1]; |
| |
| switch (action) { |
| case MotionEvent.ACTION_DOWN: |
| // Remember where the motion event started |
| mMotionDownX = dragLayerX; |
| mMotionDownY = dragLayerY; |
| |
| if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) { |
| mScrollState = SCROLL_WAITING_IN_ZONE; |
| mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY); |
| } else { |
| mScrollState = SCROLL_OUTSIDE_ZONE; |
| } |
| break; |
| case MotionEvent.ACTION_UP: |
| case MotionEvent.ACTION_CANCEL: |
| mHandler.removeCallbacks(mScrollRunnable); |
| break; |
| } |
| |
| return mDragDriver.onTouchEvent(ev); |
| } |
| |
| /** |
| * Since accessible drag and drop won't cause the same sequence of touch events, we manually |
| * inject the appropriate state. |
| */ |
| public void prepareAccessibleDrag(int x, int y) { |
| mMotionDownX = x; |
| mMotionDownY = y; |
| } |
| |
| /** |
| * As above, since accessible drag and drop won't cause the same sequence of touch events, |
| * we manually ensure appropriate drag and drop events get emulated for accessible drag. |
| */ |
| public void completeAccessibleDrag(int[] location) { |
| final int[] coordinates = mCoordinatesTemp; |
| |
| // We make sure that we prime the target for drop. |
| DropTarget dropTarget = findDropTarget(location[0], location[1], coordinates); |
| mDragObject.x = coordinates[0]; |
| mDragObject.y = coordinates[1]; |
| checkTouchMove(dropTarget); |
| |
| dropTarget.prepareAccessibilityDrop(); |
| // Perform the drop |
| drop(dropTarget, location[0], location[1], null); |
| endDrag(); |
| } |
| |
| /** |
| * Determines whether the user flung the current item to delete it. |
| * |
| * @return the vector at which the item was flung, or null if no fling was detected. |
| */ |
| private PointF isFlingingToDelete(DragSource source) { |
| if (mFlingToDeleteDropTarget == null) return null; |
| if (!source.supportsFlingToDelete()) return null; |
| |
| ViewConfiguration config = ViewConfiguration.get(mLauncher); |
| mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity()); |
| PointF vel = new PointF(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity()); |
| float theta = MAX_FLING_DEGREES + 1; |
| if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) { |
| // Do a quick dot product test to ensure that we are flinging upwards |
| PointF upVec = new PointF(0f, -1f); |
| theta = getAngleBetweenVectors(vel, upVec); |
| } else if (mLauncher.getDeviceProfile().isVerticalBarLayout() && |
| mVelocityTracker.getXVelocity() < mFlingToDeleteThresholdVelocity) { |
| // Remove icon is on left side instead of top, so check if we are flinging to the left. |
| PointF leftVec = new PointF(-1f, 0f); |
| theta = getAngleBetweenVectors(vel, leftVec); |
| } |
| if (theta <= Math.toRadians(MAX_FLING_DEGREES)) { |
| return vel; |
| } |
| return null; |
| } |
| |
| private float getAngleBetweenVectors(PointF vec1, PointF vec2) { |
| return (float) Math.acos(((vec1.x * vec2.x) + (vec1.y * vec2.y)) / |
| (vec1.length() * vec2.length())); |
| } |
| |
| void drop(DropTarget dropTarget, float x, float y, PointF flingVel) { |
| final int[] coordinates = mCoordinatesTemp; |
| |
| mDragObject.x = coordinates[0]; |
| mDragObject.y = coordinates[1]; |
| |
| // Move dragging to the final target. |
| if (dropTarget != mLastDropTarget) { |
| if (mLastDropTarget != null) { |
| mLastDropTarget.onDragExit(mDragObject); |
| } |
| mLastDropTarget = dropTarget; |
| if (dropTarget != null) { |
| dropTarget.onDragEnter(mDragObject); |
| } |
| } |
| |
| mDragObject.dragComplete = true; |
| |
| // Drop onto the target. |
| boolean accepted = false; |
| if (dropTarget != null) { |
| dropTarget.onDragExit(mDragObject); |
| if (dropTarget.acceptDrop(mDragObject)) { |
| if (flingVel != null) { |
| dropTarget.onFlingToDelete(mDragObject, flingVel); |
| } else { |
| dropTarget.onDrop(mDragObject); |
| } |
| accepted = true; |
| } |
| } |
| final View dropTargetAsView = dropTarget instanceof View ? (View) dropTarget : null; |
| mDragObject.dragSource.onDropCompleted( |
| dropTargetAsView, mDragObject, flingVel != null, accepted); |
| } |
| |
| private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) { |
| final Rect r = mRectTemp; |
| |
| final ArrayList<DropTarget> dropTargets = mDropTargets; |
| final int count = dropTargets.size(); |
| for (int i=count-1; i>=0; i--) { |
| DropTarget target = dropTargets.get(i); |
| if (!target.isDropEnabled()) |
| continue; |
| |
| target.getHitRectRelativeToDragLayer(r); |
| |
| mDragObject.x = x; |
| mDragObject.y = y; |
| if (r.contains(x, y)) { |
| |
| dropCoordinates[0] = x; |
| dropCoordinates[1] = y; |
| mLauncher.getDragLayer().mapCoordInSelfToDescendent((View) target, dropCoordinates); |
| |
| return target; |
| } |
| } |
| return null; |
| } |
| |
| public void setDragScoller(DragScroller scroller) { |
| mDragScroller = scroller; |
| } |
| |
| public void setWindowToken(IBinder token) { |
| mWindowToken = token; |
| } |
| |
| /** |
| * Sets the drag listner which will be notified when a drag starts or ends. |
| */ |
| public void addDragListener(DragListener l) { |
| mListeners.add(l); |
| } |
| |
| /** |
| * Remove a previously installed drag listener. |
| */ |
| public void removeDragListener(DragListener l) { |
| mListeners.remove(l); |
| } |
| |
| /** |
| * Add a DropTarget to the list of potential places to receive drop events. |
| */ |
| public void addDropTarget(DropTarget target) { |
| mDropTargets.add(target); |
| } |
| |
| /** |
| * Don't send drop events to <em>target</em> any more. |
| */ |
| public void removeDropTarget(DropTarget target) { |
| mDropTargets.remove(target); |
| } |
| |
| /** |
| * Sets the current fling-to-delete drop target. |
| */ |
| public void setFlingToDeleteDropTarget(DropTarget target) { |
| mFlingToDeleteDropTarget = target; |
| } |
| |
| private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) { |
| if (mVelocityTracker == null) { |
| mVelocityTracker = VelocityTracker.obtain(); |
| } |
| mVelocityTracker.addMovement(ev); |
| } |
| |
| private void releaseVelocityTracker() { |
| if (mVelocityTracker != null) { |
| mVelocityTracker.recycle(); |
| mVelocityTracker = null; |
| } |
| } |
| |
| /** |
| * Set which view scrolls for touch events near the edge of the screen. |
| */ |
| public void setScrollView(View v) { |
| mScrollView = v; |
| } |
| |
| private class ScrollRunnable implements Runnable { |
| private int mDirection; |
| |
| ScrollRunnable() { |
| } |
| |
| public void run() { |
| if (mDragScroller != null) { |
| if (mDirection == SCROLL_LEFT) { |
| mDragScroller.scrollLeft(); |
| } else { |
| mDragScroller.scrollRight(); |
| } |
| mScrollState = SCROLL_OUTSIDE_ZONE; |
| mDistanceSinceScroll = 0; |
| mDragScroller.onExitScrollArea(); |
| mLauncher.getDragLayer().onExitScrollArea(); |
| |
| if (isDragging()) { |
| // Check the scroll again so that we can requeue the scroller if necessary |
| checkScrollState(mLastTouch[0], mLastTouch[1]); |
| } |
| } |
| } |
| |
| void setDirection(int direction) { |
| mDirection = direction; |
| } |
| } |
| } |