Refactor shortcuts drag and drop.
- Instead of creating our own drag view within the container, and
handling logic to determine when to start a real drag, we start
the drag immediately and just defer onDragStart().
- To determine when the deferred drag should start, we add a
DeferDragCondition to DragOptions. The default DeferDragCondition
never defers a drag, but is overridden for apps with shortcuts
to defer until the icon is dragged a given distance.
- Because the drag is handled in DragController, including checking
when to start the deferred drag, DeepShortcutsContainer no longer
needs to handle touch events and ShortcutsContainerListener has
been removed.
This change has several immediate benefits:
- The code is much cleaner, because it allows touch handling to be
done by the DragController through the normal drag flow, without
recreating logic in ShortcutsContainerListener/DeepShortcutContainer.
- The janky second haptic feedback has been removed (now it vibrates
when you long press, like everywhere else, but not again when the
shortcuts close after dragging a distance).
- Drops are animated, instead of just popping the icon back into place.
Bug: 30769920
Bug: 30465972
Bug: 31533078
Change-Id: I679b412b72fbf6c3895d76963311eb5010c8e8db
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index b6474e6..2b64d42 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -3109,7 +3109,17 @@
longClickCellInfo.cellX, longClickCellInfo.cellY));
if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) {
// User long pressed on an item
- mWorkspace.startDrag(longClickCellInfo, new DragOptions());
+ DragOptions dragOptions = new DragOptions();
+ if (itemUnderLongClick instanceof BubbleTextView) {
+ BubbleTextView icon = (BubbleTextView) itemUnderLongClick;
+ if (icon.hasDeepShortcuts()) {
+ DeepShortcutsContainer dsc = DeepShortcutsContainer.showForIcon(icon);
+ if (dsc != null) {
+ dragOptions.deferDragCondition = dsc.createDeferDragCondition(null);
+ }
+ }
+ }
+ mWorkspace.startDrag(longClickCellInfo, dragOptions);
}
}
}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 0e25b1e..ea5401e 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -74,8 +74,6 @@
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.graphics.DragPreviewProvider;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
-import com.android.launcher3.shortcuts.ShortcutsContainerListener;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.ItemInfoMatcher;
@@ -1175,10 +1173,6 @@
if (!(child instanceof Folder)) {
child.setHapticFeedbackEnabled(false);
child.setOnLongClickListener(mLongClickListener);
- if (child instanceof BubbleTextView && DeepShortcutManager.supportsShortcuts(info)) {
- // TODO: only add this listener if the item has shortcuts associated with it.
- child.setOnTouchListener(new ShortcutsContainerListener((BubbleTextView) child));
- }
}
if (child instanceof DropTarget) {
mDragController.addDropTarget((DropTarget) child);
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 290accb..5892787 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -36,6 +36,7 @@
import com.android.launcher3.AppInfo;
import com.android.launcher3.BaseContainerView;
+import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeleteDropTarget;
import com.android.launcher3.DeviceProfile;
@@ -53,6 +54,7 @@
import com.android.launcher3.folder.Folder;
import com.android.launcher3.graphics.TintedDrawableSpan;
import com.android.launcher3.keyboard.FocusedItemDecorator;
+import com.android.launcher3.shortcuts.DeepShortcutsContainer;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.ComponentKey;
@@ -542,13 +544,32 @@
if (!mLauncher.isAppsViewVisible() ||
mLauncher.getWorkspace().isSwitchingState()) return false;
- // Return if global dragging is not enabled
+ // Return if global dragging is not enabled or we are already dragging
if (!mLauncher.isDraggingEnabled()) return false;
+ if (mLauncher.getDragController().isDragging()) return false;
// Start the drag
- mLauncher.getWorkspace().beginDragShared(v, this, new DragOptions());
- // Enter spring loaded mode
- mLauncher.enterSpringLoadedDragMode();
+ DragOptions dragOptions = new DragOptions();
+ if (v instanceof BubbleTextView) {
+ final BubbleTextView icon = (BubbleTextView) v;
+ if (icon.hasDeepShortcuts()) {
+ DeepShortcutsContainer dsc = DeepShortcutsContainer.showForIcon(icon);
+ if (dsc != null) {
+ dragOptions.deferDragCondition = dsc.createDeferDragCondition(new Runnable() {
+ @Override
+ public void run() {
+ icon.setVisibility(VISIBLE);
+ }
+ });
+ }
+ }
+ }
+ mLauncher.getWorkspace().beginDragShared(v, this, dragOptions);
+ if (FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) {
+ // Enter spring loaded mode (the new workspace does this in
+ // onDragStart(), so we don't want to do it here)
+ mLauncher.enterSpringLoadedDragMode();
+ }
return false;
}
@@ -598,7 +619,7 @@
// target layout we were dropping on.
if (!success) {
boolean showOutOfSpaceMessage = false;
- if (target instanceof Workspace) {
+ if (target instanceof Workspace && !mLauncher.getDragController().isDeferringDrag()) {
int currentScreen = mLauncher.getCurrentWorkspaceScreen();
Workspace workspace = (Workspace) target;
CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 39ab58b..7b6aef1 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -42,8 +42,6 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
-import com.android.launcher3.shortcuts.ShortcutsContainerListener;
import java.util.HashMap;
import java.util.List;
@@ -503,10 +501,6 @@
AppInfo info = mApps.getAdapterItems().get(position).appInfo;
BubbleTextView icon = (BubbleTextView) holder.mContent;
icon.applyFromApplicationInfo(info);
- if (DeepShortcutManager.supportsShortcuts(info)) {
- // TODO: only add this listener if the item has shortcuts associated with it.
- icon.setOnTouchListener(new ShortcutsContainerListener(icon));
- }
icon.setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
break;
}
@@ -514,10 +508,6 @@
AppInfo info = mApps.getAdapterItems().get(position).appInfo;
BubbleTextView icon = (BubbleTextView) holder.mContent;
icon.applyFromApplicationInfo(info);
- if (DeepShortcutManager.supportsShortcuts(info)) {
- // TODO: only add this listener if the item has shortcuts associated with it.
- icon.setOnTouchListener(new ShortcutsContainerListener(icon));
- }
icon.setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
break;
}
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index a93ee90..6eb7dcc 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -131,6 +131,8 @@
protected final int mFlingToDeleteThresholdVelocity;
private VelocityTracker mVelocityTracker;
+ private boolean mIsDragDeferred;
+
/**
* Interface to receive notifications when a drag starts or stops
*/
@@ -228,9 +230,14 @@
mDragObject = new DropTarget.DragObject();
+ mIsDragDeferred = !mOptions.deferDragCondition.shouldStartDeferredDrag(0);
+
final Resources res = mLauncher.getResources();
- final float scaleDps = FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND ?
- res.getDimensionPixelSize(R.dimen.dragViewScale) : 0f;
+ final float scaleDps = FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND
+ ? res.getDimensionPixelSize(R.dimen.dragViewScale)
+ : mIsDragDeferred
+ ? res.getDimensionPixelSize(R.dimen.deferred_drag_view_scale)
+ : 0f;
final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
registrationY, initialDragViewScale, scaleDps);
@@ -264,8 +271,10 @@
dragView.show(mMotionDownX, mMotionDownY);
mDistanceSinceScroll = 0;
- for (DragListener listener : new ArrayList<>(mListeners)) {
- listener.onDragStart(mDragObject, mOptions);
+ if (!mIsDragDeferred) {
+ startDeferredDrag();
+ } else {
+ mOptions.deferDragCondition.onDeferredDragStart();
}
mLastTouch[0] = mMotionDownX;
@@ -275,8 +284,16 @@
return dragView;
}
- public Point getMotionDown() {
- return new Point(mMotionDownX, mMotionDownY);
+ public boolean isDeferringDrag() {
+ return mIsDragDeferred;
+ }
+
+ public void startDeferredDrag() {
+ for (DragListener listener : new ArrayList<>(mListeners)) {
+ listener.onDragStart(mDragObject, mOptions);
+ }
+ mOptions.deferDragCondition.onDragStart();
+ mIsDragDeferred = false;
}
/**
@@ -518,6 +535,11 @@
mLastTouch[0] = x;
mLastTouch[1] = y;
checkScrollState(x, y);
+
+ if (mIsDragDeferred && mOptions.deferDragCondition.shouldStartDeferredDrag(
+ Math.hypot(x - mMotionDownX, y - mMotionDownY))) {
+ startDeferredDrag();
+ }
}
public float getDistanceDragged() {
@@ -715,6 +737,9 @@
mDragObject.dragSource.onDropCompleted(
dropTargetAsView, mDragObject, flingVel != null, accepted);
mLauncher.getUserEventDispatcher().logDragNDrop(mDragObject, dropTargetAsView);
+ if (mIsDragDeferred) {
+ mOptions.deferDragCondition.onDropBeforeDeferredDrag();
+ }
}
private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) {
diff --git a/src/com/android/launcher3/dragndrop/DragOptions.java b/src/com/android/launcher3/dragndrop/DragOptions.java
index 3d52a48..dbf46f3 100644
--- a/src/com/android/launcher3/dragndrop/DragOptions.java
+++ b/src/com/android/launcher3/dragndrop/DragOptions.java
@@ -28,4 +28,44 @@
/** Specifies the start location for the system DnD, null when using internal DnD */
public Point systemDndStartPoint = null;
+
+ /** Determines when a deferred drag should start. By default, drags aren't deferred at all. */
+ public DeferDragCondition deferDragCondition = new DeferDragCondition();
+
+ /**
+ * Specifies a condition that must be met before DragListener#onDragStart() is called.
+ * By default, there is no condition and onDragStart() is called immediately following
+ * DragController#startDrag().
+ *
+ * This condition can be overridden, and callbacks are provided for the following cases:
+ * - The drag starts, but onDragStart() is deferred (onDeferredDragStart()).
+ * - The drag ends before the condition is met (onDropBeforeDeferredDrag()).
+ * - The condition is met (onDragStart()).
+ */
+ public static class DeferDragCondition {
+ public boolean shouldStartDeferredDrag(double distanceDragged) {
+ return true;
+ }
+
+ /**
+ * The drag has started, but onDragStart() is deferred.
+ * This happens when shouldStartDeferredDrag() returns true.
+ */
+ public void onDeferredDragStart() {
+ // Do nothing.
+ }
+
+ /**
+ * User dropped before the deferred condition was met,
+ * i.e. before shouldStartDeferredDrag() returned true.
+ */
+ public void onDropBeforeDeferredDrag() {
+ // Do nothing
+ }
+
+ /** onDragStart() has been called, now we are in a normal drag. */
+ public void onDragStart() {
+ // Do nothing
+ }
+ }
}
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index b64d12c..a666b56 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -51,6 +51,7 @@
import com.android.launcher3.Alarm;
import com.android.launcher3.AppInfo;
+import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DragSource;
@@ -77,6 +78,7 @@
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.pageindicators.PageIndicatorDots;
+import com.android.launcher3.shortcuts.DeepShortcutsContainer;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.CircleRevealOutlineProvider;
@@ -279,7 +281,17 @@
public boolean onLongClick(View v) {
// Return if global dragging is not enabled
if (!mLauncher.isDraggingEnabled()) return true;
- return startDrag(v, new DragOptions());
+ DragOptions dragOptions = new DragOptions();
+ if (v instanceof BubbleTextView) {
+ BubbleTextView icon = (BubbleTextView) v;
+ if (icon.hasDeepShortcuts()) {
+ DeepShortcutsContainer dsc = DeepShortcutsContainer.showForIcon(icon);
+ if (dsc != null) {
+ dragOptions.deferDragCondition = dsc.createDeferDragCondition(null);
+ }
+ }
+ }
+ return startDrag(v, dragOptions);
}
public boolean startDrag(View v, DragOptions options) {
@@ -1297,7 +1309,9 @@
mIsExternalDrag = false;
} else {
currentDragView = mCurrentDragView;
- mContent.addViewForRank(currentDragView, si, mEmptyCellRank);
+ if (!mDragController.isDeferringDrag()) {
+ mContent.addViewForRank(currentDragView, si, mEmptyCellRank);
+ }
}
if (d.dragView.hasDrawn()) {
@@ -1318,9 +1332,11 @@
mItemsInvalidated = true;
rearrangeChildren();
- // Temporarily suppress the listener, as we did all the work already here.
- try (SuppressInfoChanges s = new SuppressInfoChanges()) {
- mInfo.add(si, false);
+ if (!mDragController.isDeferringDrag()) {
+ // Temporarily suppress the listener, as we did all the work already here.
+ try (SuppressInfoChanges s = new SuppressInfoChanges()) {
+ mInfo.add(si, false);
+ }
}
// Clear the drag info, as it is no longer being dragged.
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 1171d48..c6b0671 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -44,10 +44,8 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace.ItemOperator;
import com.android.launcher3.dragndrop.DragController;
-import com.android.launcher3.pageindicators.PageIndicator;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
-import com.android.launcher3.shortcuts.ShortcutsContainerListener;
+import com.android.launcher3.pageindicators.PageIndicator;
import com.android.launcher3.util.Thunk;
import java.util.ArrayList;
@@ -236,10 +234,6 @@
textView.applyFromShortcutInfo(item, mIconCache);
textView.setOnClickListener(mFolder);
textView.setOnLongClickListener(mFolder);
- if (DeepShortcutManager.supportsShortcuts(item)) {
- // TODO: only add this listener if the item has shortcuts associated with it.
- textView.setOnTouchListener(new ShortcutsContainerListener(textView));
- }
textView.setOnFocusChangeListener(mFocusIndicatorHelper);
textView.setOnKeyListener(mKeyListener);
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
index 5ef1288..2702d4e 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
@@ -34,16 +34,13 @@
import android.os.Looper;
import android.util.AttributeSet;
import android.view.Gravity;
-import android.view.HapticFeedbackConstants;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.DecelerateInterpolator;
import android.widget.LinearLayout;
-import com.android.launcher3.AppInfo;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
@@ -59,7 +56,6 @@
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.ShortcutMenuAccessibilityDelegate;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.dragndrop.DragController;
@@ -85,18 +81,12 @@
private final Launcher mLauncher;
private final DeepShortcutManager mDeepShortcutsManager;
- private final int mDragDeadzone;
private final int mStartDragThreshold;
private final ShortcutMenuAccessibilityDelegate mAccessibilityDelegate;
+ private final boolean mIsRtl;
private BubbleTextView mDeferredDragIcon;
- private int mActivePointerId;
- private int[] mTouchDown = null;
- private DragView mDragView;
- private float mLastX, mLastY;
- private float mDistanceDragged = 0;
private final Rect mTempRect = new Rect();
- private final int[] mTempXY = new int[2];
private Point mIconLastTouchPos = new Point();
private boolean mIsLeftAligned;
private boolean mIsAboveIcon;
@@ -106,16 +96,11 @@
private boolean mDeferContainerRemoval;
private boolean mIsOpen;
- private boolean mSrcIconDragStarted;
- private boolean mIsRtl;
- private int mArrowHorizontalOffset;
-
public DeepShortcutsContainer(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mLauncher = Launcher.getLauncher(context);
mDeepShortcutsManager = LauncherAppState.getInstance().getShortcutManager();
- mDragDeadzone = ViewConfiguration.get(context).getScaledTouchSlop();
mStartDragThreshold = getResources().getDimensionPixelSize(
R.dimen.deep_shortcuts_start_drag_threshold);
mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(mLauncher);
@@ -134,7 +119,7 @@
final Resources resources = getResources();
final int arrowWidth = resources.getDimensionPixelSize(R.dimen.deep_shortcuts_arrow_width);
final int arrowHeight = resources.getDimensionPixelSize(R.dimen.deep_shortcuts_arrow_height);
- mArrowHorizontalOffset = resources.getDimensionPixelSize(
+ final int arrowHorizontalOffset = resources.getDimensionPixelSize(
R.dimen.deep_shortcuts_arrow_horizontal_offset);
final int arrowVerticalOffset = resources.getDimensionPixelSize(
R.dimen.deep_shortcuts_arrow_vertical_offset);
@@ -159,7 +144,7 @@
orientAboutIcon(originalIcon, arrowHeight + arrowVerticalOffset);
// Add the arrow.
- mArrow = addArrowView(mArrowHorizontalOffset, arrowVerticalOffset, arrowWidth, arrowHeight);
+ mArrow = addArrowView(arrowHorizontalOffset, arrowVerticalOffset, arrowWidth, arrowHeight);
mArrow.setPivotX(arrowWidth / 2);
mArrow.setPivotY(mIsAboveIcon ? 0 : arrowHeight);
@@ -347,7 +332,6 @@
mIsAboveIcon = y > dragLayer.getTop() + insets.top;
if (!mIsAboveIcon) {
y = mTempRect.top + icon.getPaddingTop() + iconHeight;
- icon.setTextVisibility(false);
}
// Insets are added later, so subtract them now.
@@ -393,7 +377,6 @@
private void deferDrag(BubbleTextView originalIcon) {
mDeferredDragIcon = originalIcon;
- showDragView(originalIcon);
mLauncher.getDragController().addDragListener(this);
}
@@ -401,103 +384,39 @@
return mDeferredDragIcon;
}
- private void showDragView(BubbleTextView originalIcon) {
- // TODO: implement support for Drawable DragViews so we don't have to create a bitmap here.
- Bitmap b = Utilities.createIconBitmap(originalIcon.getIcon(), mLauncher);
- float scale = mLauncher.getDragLayer().getLocationInDragLayer(originalIcon, mTempXY);
- int dragLayerX = Math.round(mTempXY[0] - (b.getWidth() - scale * originalIcon.getWidth()) / 2);
- int dragLayerY = Math.round(mTempXY[1] - (b.getHeight() - scale * b.getHeight()) / 2
- - Workspace.DRAG_BITMAP_PADDING / 2) + originalIcon.getPaddingTop();
- int motionDownX = mLauncher.getDragController().getMotionDown().x;
- int motionDownY = mLauncher.getDragController().getMotionDown().y;
- final int registrationX = motionDownX - dragLayerX;
- final int registrationY = motionDownY - dragLayerY;
-
- float scaleDps = getResources().getDimensionPixelSize(R.dimen.deep_shortcuts_drag_view_scale);
- mDragView = new DragView(mLauncher, b, registrationX, registrationY, 1f, scaleDps);
- mLastX = mLastY = mDistanceDragged = 0;
- mDragView.show(motionDownX, motionDownY);
- }
-
- public boolean onForwardedEvent(MotionEvent ev, int activePointerId, int[] touchDown) {
- mActivePointerId = activePointerId;
- mTouchDown = touchDown;
- return dispatchTouchEvent(ev);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- if (mDeferredDragIcon == null) {
- return false;
- }
-
- final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
- if (activePointerIndex < 0) {
- return false;
- }
- final float x = ev.getX(activePointerIndex);
- final float y = ev.getY(activePointerIndex);
-
-
- int action = ev.getAction();
- // The event was in this container's coordinate system before this,
- // but will be in DragLayer's coordinate system from now on.
- Utilities.translateEventCoordinates(this, mLauncher.getDragLayer(), ev);
- final int dragLayerX = (int) ev.getX();
- final int dragLayerY = (int) ev.getY();
- if (action == MotionEvent.ACTION_MOVE) {
- if (mLastX != 0 || mLastY != 0) {
- mDistanceDragged += Math.hypot(mLastX - x, mLastY - y);
- }
- mLastX = x;
- mLastY = y;
-
- if (shouldStartDeferredDrag((int) x, (int) y)) {
- mSrcIconDragStarted = true;
- cleanupDeferredDrag(true);
- mDeferredDragIcon.getParent().requestDisallowInterceptTouchEvent(false);
- mDeferredDragIcon.getOnLongClickListener().onLongClick(mDeferredDragIcon);
- mLauncher.getDragController().onTouchEvent(ev);
- return true;
- } else if (mDistanceDragged > mDragDeadzone) {
- // After dragging further than a small deadzone,
- // have the drag view follow the user's finger.
- mDragView.setVisibility(VISIBLE);
- mDragView.move(dragLayerX, dragLayerY);
- mDeferredDragIcon.setVisibility(INVISIBLE);
- }
- } else if (action == MotionEvent.ACTION_UP) {
- cleanupDeferredDrag(true);
- mLauncher.getUserEventDispatcher().logDeepShortcutsOpen(mDeferredDragIcon);
- } else if (action == MotionEvent.ACTION_CANCEL) {
- // Do not change the source icon visibility if we are already dragging the source icon.
- cleanupDeferredDrag(!mSrcIconDragStarted);
- }
- return true;
- }
-
/**
- * Determines whether the deferred drag should be started based on touch coordinates
- * relative to the original icon and the shortcuts container.
+ * Determines when the deferred drag should be started.
*
* Current behavior:
* - Start the drag if the touch passes a certain distance from the original touch down.
- *
- * @param x the x touch coordinate relative to this container
- * @param y the y touch coordinate relative to this container
*/
- private boolean shouldStartDeferredDrag(int x, int y) {
- double distFromTouchDown = Math.hypot(x - mTouchDown[0], y - mTouchDown[1]);
- return distFromTouchDown > mStartDragThreshold;
- }
+ public DragOptions.DeferDragCondition createDeferDragCondition(final Runnable onDragStart) {
+ return new DragOptions.DeferDragCondition() {
+ @Override
+ public boolean shouldStartDeferredDrag(double distanceDragged) {
+ return distanceDragged > mStartDragThreshold;
+ }
- private void cleanupDeferredDrag(boolean updateSrcVisibility) {
- if (mDragView != null) {
- mDragView.remove();
- }
- if (updateSrcVisibility) {
- mDeferredDragIcon.setVisibility(VISIBLE);
- }
+ @Override
+ public void onDeferredDragStart() {
+ mDeferredDragIcon.setVisibility(INVISIBLE);
+ }
+
+ @Override
+ public void onDropBeforeDeferredDrag() {
+ mLauncher.getUserEventDispatcher().logDeepShortcutsOpen(mDeferredDragIcon);
+ if (!mIsAboveIcon) {
+ mDeferredDragIcon.setTextVisibility(false);
+ }
+ }
+
+ @Override
+ public void onDragStart() {
+ if (onDragStart != null) {
+ onDragStart.run();
+ }
+ }
+ };
}
@Override
@@ -581,9 +500,7 @@
@Override
public void onDragEnd() {
- if (mIsOpen) {
- animateClose();
- } else {
+ if (!mIsOpen) {
if (mOpenCloseAnimator != null) {
// Close animation is running.
mDeferContainerRemoval = false;
@@ -594,6 +511,7 @@
}
}
}
+ mDeferredDragIcon.setVisibility(VISIBLE);
}
@Override
@@ -701,8 +619,6 @@
}
mIsOpen = false;
mDeferContainerRemoval = false;
- // Make the original icon visible in All Apps, but not in Workspace or Folders.
- cleanupDeferredDrag(mDeferredDragIcon.getTag() instanceof AppInfo);
boolean isInHotseat = ((ItemInfo) mDeferredDragIcon.getTag()).container
== LauncherSettings.Favorites.CONTAINER_HOTSEAT;
mDeferredDragIcon.setTextVisibility(!isInHotseat);
@@ -734,8 +650,6 @@
container.setVisibility(View.INVISIBLE);
launcher.getDragLayer().addView(container);
container.populateAndShow(icon, ids);
- icon.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
- HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
return container;
}
return null;
diff --git a/src/com/android/launcher3/shortcuts/ShortcutsContainerListener.java b/src/com/android/launcher3/shortcuts/ShortcutsContainerListener.java
deleted file mode 100644
index 31f0969..0000000
--- a/src/com/android/launcher3/shortcuts/ShortcutsContainerListener.java
+++ /dev/null
@@ -1,252 +0,0 @@
-package com.android.launcher3.shortcuts;
-
-import android.os.SystemClock;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.CheckLongPressHelper;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.dragndrop.DragLayer;
-
-/**
- * A {@link android.view.View.OnTouchListener} that creates a {@link DeepShortcutsContainer} and
- * forwards touch events to it. This listener should be put on any icon that supports shortcuts.
- */
-public class ShortcutsContainerListener implements View.OnTouchListener,
- View.OnAttachStateChangeListener {
-
- /** Scaled touch slop, used for detecting movement outside bounds. */
- private final float mScaledTouchSlop;
-
- /** Timeout before accepting a long-press to start forwarding. */
- private final int mLongPressTimeout;
-
- /** Source view from which events are forwarded. */
- private final BubbleTextView mSrcIcon;
-
- /** Runnable used to trigger forwarding on long-press. */
- private Runnable mTriggerLongPress;
-
- /** Whether this listener is currently forwarding touch events. */
- private boolean mForwarding;
-
- /** The id of the first pointer down in the current event stream. */
- private int mActivePointerId;
-
- private Launcher mLauncher;
- private DragLayer mDragLayer;
- /** The coordinates of the touch down, relative to the shortcuts container. */
- private final int[] mTouchDown;
- private boolean mHasMappedTouchDownToContainerCoord;
-
- /** If true, the gesture is not handled. The value is reset when next gesture starts. */
- private boolean mIgnoreCurrentGesture;
- private DeepShortcutsContainer mShortcutsContainer;
-
- public ShortcutsContainerListener(BubbleTextView icon) {
- mSrcIcon = icon;
- mScaledTouchSlop = ViewConfiguration.get(icon.getContext()).getScaledTouchSlop();
-
- mLongPressTimeout = CheckLongPressHelper.DEFAULT_LONG_PRESS_TIMEOUT;
-
- icon.addOnAttachStateChangeListener(this);
-
- mLauncher = Launcher.getLauncher(mSrcIcon.getContext());
- mDragLayer = mLauncher.getDragLayer();
- mTouchDown = new int[2];
- }
-
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- if (event.getAction() == MotionEvent.ACTION_DOWN) {
- // There are no shortcuts associated with this item,
- // so return to normal touch handling.
- mIgnoreCurrentGesture = !mSrcIcon.hasDeepShortcuts();
-
- mTouchDown[0] = (int) event.getX();
- mTouchDown[1] = (int) event.getY();
- mDragLayer.getDescendantCoordRelativeToSelf(mSrcIcon, mTouchDown);
- mHasMappedTouchDownToContainerCoord = false;
- }
-
- if (mIgnoreCurrentGesture) {
- return false;
- }
-
- final boolean wasForwarding = mForwarding;
- final boolean forwarding;
- if (wasForwarding) {
- forwarding = onTouchForwarded(event) || !onForwardingStopped();
- } else {
- forwarding = onTouchObserved(event) && onForwardingStarted();
-
- if (forwarding) {
- // Make sure we cancel any ongoing source event stream.
- final long now = SystemClock.uptimeMillis();
- final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL,
- 0.0f, 0.0f, 0);
- mSrcIcon.onTouchEvent(e);
- e.recycle();
- }
- }
-
- mForwarding = forwarding;
- return forwarding || wasForwarding;
- }
-
- @Override
- public void onViewAttachedToWindow(View v) {
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {
- mForwarding = false;
- mActivePointerId = MotionEvent.INVALID_POINTER_ID;
- }
-
- /**
- * Called when forwarding would like to start.
- * <p>
- * This is when we populate the shortcuts container and add it to the DragLayer.
- *
- * @return true to start forwarding, false otherwise
- */
- protected boolean onForwardingStarted() {
- mShortcutsContainer = DeepShortcutsContainer.showForIcon(mSrcIcon);
- return mShortcutsContainer != null;
- }
-
- /**
- * Called when forwarding would like to stop.
- *
- * @return true to stop forwarding, false otherwise
- */
- protected boolean onForwardingStopped() {
- mShortcutsContainer = null;
- return true;
- }
-
- /**
- * Observes motion events and determines when to start forwarding.
- *
- * @param srcEvent motion event in source view coordinates
- * @return true to start forwarding motion events, false otherwise
- */
- private boolean onTouchObserved(MotionEvent srcEvent) {
- final View src = mSrcIcon;
- if (!src.isEnabled()) {
- return false;
- }
-
- final int actionMasked = srcEvent.getActionMasked();
- switch (actionMasked) {
- case MotionEvent.ACTION_DOWN:
- mActivePointerId = srcEvent.getPointerId(0);
-
- if (mTriggerLongPress == null) {
- mTriggerLongPress = new TriggerLongPress();
- }
- src.postDelayed(mTriggerLongPress, mLongPressTimeout);
- break;
- case MotionEvent.ACTION_MOVE:
- final int activePointerIndex = srcEvent.findPointerIndex(mActivePointerId);
- if (activePointerIndex >= 0) {
- final float x = srcEvent.getX(activePointerIndex);
- final float y = srcEvent.getY(activePointerIndex);
-
- // Has the pointer moved outside of the view?
- if (!Utilities.pointInView(src, x, y, mScaledTouchSlop)) {
- clearCallbacks();
-
- return false;
- }
- }
- break;
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- clearCallbacks();
- break;
- }
-
- return false;
- }
-
- private void clearCallbacks() {
- if (mTriggerLongPress != null) {
- mSrcIcon.removeCallbacks(mTriggerLongPress);
- }
- }
-
- private void onLongPress() {
- clearCallbacks();
-
- final BubbleTextView src = mSrcIcon;
- if (!src.isEnabled() || !src.hasDeepShortcuts()) {
- // Ignore long-press if the view is disabled or doesn't have shortcuts.
- return;
- }
-
- if (!onForwardingStarted()) {
- return;
- }
-
- // Don't let the parent intercept our events.
- src.getParent().requestDisallowInterceptTouchEvent(true);
-
- // Make sure we cancel any ongoing source event stream.
- final long now = SystemClock.uptimeMillis();
- final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
- src.onTouchEvent(e);
- e.recycle();
-
- mForwarding = true;
- }
-
- /**
- * Handles forwarded motion events and determines when to stop
- * forwarding.
- *
- * @param srcEvent motion event in source view coordinates
- * @return true to continue forwarding motion events, false to cancel
- */
- private boolean onTouchForwarded(MotionEvent srcEvent) {
- final View src = mSrcIcon;
- final DeepShortcutsContainer dst = mShortcutsContainer;
- if (dst == null) {
- return false;
- }
- // Always cancel forwarding when the touch stream ends.
- final int action = srcEvent.getActionMasked();
- final boolean keepForwarding = action != MotionEvent.ACTION_UP
- && action != MotionEvent.ACTION_CANCEL;
- if (!dst.isLaidOut()) {
- return keepForwarding;
- }
-
- // Convert event to destination-local coordinates.
- final MotionEvent dstEvent = MotionEvent.obtainNoHistory(srcEvent);
- Utilities.translateEventCoordinates(src, dst, dstEvent);
-
- // Convert touch down event to destination-local coordinates.
- if (!mHasMappedTouchDownToContainerCoord) {
- mDragLayer.mapCoordInSelfToDescendent(dst, mTouchDown);
- mHasMappedTouchDownToContainerCoord = true;
- }
-
- // Forward converted event to destination view, then recycle it.
- final boolean handled = dst.onForwardedEvent(dstEvent, mActivePointerId, mTouchDown);
- dstEvent.recycle();
-
- return handled && keepForwarding;
- }
-
- private class TriggerLongPress implements Runnable {
- @Override
- public void run() {
- onLongPress();
- }
- }
-}