summaryrefslogtreecommitdiffstats
path: root/src/com/android/launcher3/touch
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/launcher3/touch')
-rw-r--r--src/com/android/launcher3/touch/AbstractStateChangeTouchController.java23
-rw-r--r--src/com/android/launcher3/touch/BaseSwipeDetector.java268
-rw-r--r--src/com/android/launcher3/touch/BothAxesSwipeDetector.java99
-rw-r--r--src/com/android/launcher3/touch/ItemLongClickListener.java16
-rw-r--r--src/com/android/launcher3/touch/SingleAxisSwipeDetector.java190
-rw-r--r--src/com/android/launcher3/touch/SwipeDetector.java407
6 files changed, 584 insertions, 419 deletions
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index c5ba5bab6..60f6ee9c5 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -53,7 +53,7 @@ import com.android.launcher3.util.TouchController;
* TouchController for handling state changes
*/
public abstract class AbstractStateChangeTouchController
- implements TouchController, SwipeDetector.Listener {
+ implements TouchController, SingleAxisSwipeDetector.Listener {
// Progress after which the transition is assumed to be a success in case user does not fling
public static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
@@ -65,8 +65,8 @@ public abstract class AbstractStateChangeTouchController
protected final long ATOMIC_DURATION = getAtomicDuration();
protected final Launcher mLauncher;
- protected final SwipeDetector mDetector;
- protected final SwipeDetector.Direction mSwipeDirection;
+ protected final SingleAxisSwipeDetector mDetector;
+ protected final SingleAxisSwipeDetector.Direction mSwipeDirection;
private boolean mNoIntercept;
private boolean mIsLogContainerSet;
@@ -101,9 +101,9 @@ public abstract class AbstractStateChangeTouchController
private float mAtomicComponentsStartProgress;
- public AbstractStateChangeTouchController(Launcher l, SwipeDetector.Direction dir) {
+ public AbstractStateChangeTouchController(Launcher l, SingleAxisSwipeDetector.Direction dir) {
mLauncher = l;
- mDetector = new SwipeDetector(l, this, dir);
+ mDetector = new SingleAxisSwipeDetector(l, this, dir);
mSwipeDirection = dir;
}
@@ -127,7 +127,7 @@ public abstract class AbstractStateChangeTouchController
boolean ignoreSlopWhenSettling = false;
if (mCurrentAnimation != null) {
- directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
+ directionsToDetectScroll = SingleAxisSwipeDetector.DIRECTION_BOTH;
ignoreSlopWhenSettling = true;
} else {
directionsToDetectScroll = getSwipeDirection();
@@ -152,10 +152,10 @@ public abstract class AbstractStateChangeTouchController
LauncherState fromState = mLauncher.getStateManager().getState();
int swipeDirection = 0;
if (getTargetState(fromState, true /* isDragTowardPositive */) != fromState) {
- swipeDirection |= SwipeDetector.DIRECTION_POSITIVE;
+ swipeDirection |= SingleAxisSwipeDetector.DIRECTION_POSITIVE;
}
if (getTargetState(fromState, false /* isDragTowardPositive */) != fromState) {
- swipeDirection |= SwipeDetector.DIRECTION_NEGATIVE;
+ swipeDirection |= SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
}
return swipeDirection;
}
@@ -369,7 +369,8 @@ public abstract class AbstractStateChangeTouchController
}
@Override
- public void onDragEnd(float velocity, boolean fling) {
+ public void onDragEnd(float velocity) {
+ boolean fling = mDetector.isFling(velocity);
final int logAction = fling ? Touch.FLING : Touch.SWIPE;
boolean blockedFling = fling && mFlingBlockCheck.isBlocked();
@@ -406,7 +407,7 @@ public abstract class AbstractStateChangeTouchController
} else {
startProgress = Utilities.boundToRange(progress
+ velocity * getSingleFrameMs(mLauncher) * mProgressMultiplier, 0f, 1f);
- duration = SwipeDetector.calculateDuration(velocity,
+ duration = BaseSwipeDetector.calculateDuration(velocity,
endProgress - Math.max(progress, 0)) * durationMultiplier;
}
} else {
@@ -424,7 +425,7 @@ public abstract class AbstractStateChangeTouchController
} else {
startProgress = Utilities.boundToRange(progress
+ velocity * getSingleFrameMs(mLauncher) * mProgressMultiplier, 0f, 1f);
- duration = SwipeDetector.calculateDuration(velocity,
+ duration = BaseSwipeDetector.calculateDuration(velocity,
Math.min(progress, 1) - endProgress) * durationMultiplier;
}
}
diff --git a/src/com/android/launcher3/touch/BaseSwipeDetector.java b/src/com/android/launcher3/touch/BaseSwipeDetector.java
new file mode 100644
index 000000000..12ca5ee7b
--- /dev/null
+++ b/src/com/android/launcher3/touch/BaseSwipeDetector.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2017 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.touch;
+
+import static android.view.MotionEvent.INVALID_POINTER_ID;
+
+import android.graphics.PointF;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Scroll/drag/swipe gesture detector.
+ *
+ * Definition of swipe is different from android system in that this detector handles
+ * 'swipe to dismiss', 'swiping up/down a container' but also keeps scrolling state before
+ * swipe action happens.
+ *
+ * @see SingleAxisSwipeDetector
+ * @see BothAxesSwipeDetector
+ */
+public abstract class BaseSwipeDetector {
+
+ private static final boolean DBG = false;
+ private static final String TAG = "BaseSwipeDetector";
+ private static final float ANIMATION_DURATION = 1200;
+ /** The minimum release velocity in pixels per millisecond that triggers fling.*/
+ private static final float RELEASE_VELOCITY_PX_MS = 1.0f;
+ private static final PointF sTempPoint = new PointF();
+
+ private final PointF mDownPos = new PointF();
+ private final PointF mLastPos = new PointF();
+ protected final boolean mIsRtl;
+ protected final float mTouchSlop;
+ protected final float mMaxVelocity;
+
+ private int mActivePointerId = INVALID_POINTER_ID;
+ private VelocityTracker mVelocityTracker;
+ private PointF mLastDisplacement = new PointF();
+ private PointF mDisplacement = new PointF();
+ protected PointF mSubtractDisplacement = new PointF();
+ private ScrollState mState = ScrollState.IDLE;
+
+ protected boolean mIgnoreSlopWhenSettling;
+
+ private enum ScrollState {
+ IDLE,
+ DRAGGING, // onDragStart, onDrag
+ SETTLING // onDragEnd
+ }
+
+ protected BaseSwipeDetector(@NonNull ViewConfiguration config, boolean isRtl) {
+ mTouchSlop = config.getScaledTouchSlop();
+ mMaxVelocity = config.getScaledMaximumFlingVelocity();
+ mIsRtl = isRtl;
+ }
+
+ public static long calculateDuration(float velocity, float progressNeeded) {
+ // TODO: make these values constants after tuning.
+ float velocityDivisor = Math.max(2f, Math.abs(0.5f * velocity));
+ float travelDistance = Math.max(0.2f, progressNeeded);
+ long duration = (long) Math.max(100, ANIMATION_DURATION / velocityDivisor * travelDistance);
+ if (DBG) {
+ Log.d(TAG, String.format(
+ "calculateDuration=%d, v=%f, d=%f", duration, velocity, progressNeeded));
+ }
+ return duration;
+ }
+
+ public int getDownX() {
+ return (int) mDownPos.x;
+ }
+
+ public int getDownY() {
+ return (int) mDownPos.y;
+ }
+ /**
+ * There's no touch and there's no animation.
+ */
+ public boolean isIdleState() {
+ return mState == ScrollState.IDLE;
+ }
+
+ public boolean isSettlingState() {
+ return mState == ScrollState.SETTLING;
+ }
+
+ public boolean isDraggingState() {
+ return mState == ScrollState.DRAGGING;
+ }
+
+ public boolean isDraggingOrSettling() {
+ return mState == ScrollState.DRAGGING || mState == ScrollState.SETTLING;
+ }
+
+ public void finishedScrolling() {
+ setState(ScrollState.IDLE);
+ }
+
+ public boolean isFling(float velocity) {
+ return Math.abs(velocity) > RELEASE_VELOCITY_PX_MS;
+ }
+
+ public boolean onTouchEvent(MotionEvent ev) {
+ int actionMasked = ev.getActionMasked();
+ if (actionMasked == MotionEvent.ACTION_DOWN && mVelocityTracker != null) {
+ mVelocityTracker.clear();
+ }
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(ev);
+
+ switch (actionMasked) {
+ case MotionEvent.ACTION_DOWN:
+ mActivePointerId = ev.getPointerId(0);
+ mDownPos.set(ev.getX(), ev.getY());
+ mLastPos.set(mDownPos);
+ mLastDisplacement.set(0, 0);
+ mDisplacement.set(0, 0);
+
+ if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
+ setState(ScrollState.DRAGGING);
+ }
+ break;
+ //case MotionEvent.ACTION_POINTER_DOWN:
+ case MotionEvent.ACTION_POINTER_UP:
+ int ptrIdx = ev.getActionIndex();
+ int ptrId = ev.getPointerId(ptrIdx);
+ if (ptrId == mActivePointerId) {
+ final int newPointerIdx = ptrIdx == 0 ? 1 : 0;
+ mDownPos.set(
+ ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x),
+ ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y));
+ mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx));
+ mActivePointerId = ev.getPointerId(newPointerIdx);
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ int pointerIndex = ev.findPointerIndex(mActivePointerId);
+ if (pointerIndex == INVALID_POINTER_ID) {
+ break;
+ }
+ mDisplacement.set(ev.getX(pointerIndex) - mDownPos.x,
+ ev.getY(pointerIndex) - mDownPos.y);
+ if (mIsRtl) {
+ mDisplacement.x = -mDisplacement.x;
+ }
+
+ // handle state and listener calls.
+ if (mState != ScrollState.DRAGGING && shouldScrollStart(mDisplacement)) {
+ setState(ScrollState.DRAGGING);
+ }
+ if (mState == ScrollState.DRAGGING) {
+ reportDragging(ev);
+ }
+ mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ // These are synthetic events and there is no need to update internal values.
+ if (mState == ScrollState.DRAGGING) {
+ setState(ScrollState.SETTLING);
+ }
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ break;
+ default:
+ break;
+ }
+ return true;
+ }
+
+ //------------------- ScrollState transition diagram -----------------------------------
+ //
+ // IDLE -> (mDisplacement > mTouchSlop) -> DRAGGING
+ // DRAGGING -> (MotionEvent#ACTION_UP, MotionEvent#ACTION_CANCEL) -> SETTLING
+ // SETTLING -> (MotionEvent#ACTION_DOWN) -> DRAGGING
+ // SETTLING -> (View settled) -> IDLE
+
+ private void setState(ScrollState newState) {
+ if (DBG) {
+ Log.d(TAG, "setState:" + mState + "->" + newState);
+ }
+ // onDragStart and onDragEnd is reported ONLY on state transition
+ if (newState == ScrollState.DRAGGING) {
+ initializeDragging();
+ if (mState == ScrollState.IDLE) {
+ reportDragStart(false /* recatch */);
+ } else if (mState == ScrollState.SETTLING) {
+ reportDragStart(true /* recatch */);
+ }
+ }
+ if (newState == ScrollState.SETTLING) {
+ reportDragEnd();
+ }
+
+ mState = newState;
+ }
+
+ private void initializeDragging() {
+ if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
+ mSubtractDisplacement.set(0, 0);
+ } else {
+ mSubtractDisplacement.x = mDisplacement.x > 0 ? mTouchSlop : -mTouchSlop;
+ mSubtractDisplacement.y = mDisplacement.y > 0 ? mTouchSlop : -mTouchSlop;
+ }
+ }
+
+ protected abstract boolean shouldScrollStart(PointF displacement);
+
+ private void reportDragStart(boolean recatch) {
+ reportDragStartInternal(recatch);
+ if (DBG) {
+ Log.d(TAG, "onDragStart recatch:" + recatch);
+ }
+ }
+
+ protected abstract void reportDragStartInternal(boolean recatch);
+
+ private void reportDragging(MotionEvent event) {
+ if (mDisplacement != mLastDisplacement) {
+ if (DBG) {
+ Log.d(TAG, String.format("onDrag disp=%s", mDisplacement));
+ }
+
+ mLastDisplacement.set(mDisplacement);
+ sTempPoint.set(mDisplacement.x - mSubtractDisplacement.x,
+ mDisplacement.y - mSubtractDisplacement.y);
+ reportDraggingInternal(sTempPoint, event);
+ }
+ }
+
+ protected abstract void reportDraggingInternal(PointF displacement, MotionEvent event);
+
+ private void reportDragEnd() {
+ mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
+ PointF velocity = new PointF(mVelocityTracker.getXVelocity() / 1000,
+ mVelocityTracker.getYVelocity() / 1000);
+ if (mIsRtl) {
+ velocity.x = -velocity.x;
+ }
+ if (DBG) {
+ Log.d(TAG, String.format("onScrollEnd disp=%.1s, velocity=%.1s",
+ mDisplacement, velocity));
+ }
+
+ reportDragEndInternal(velocity);
+ }
+
+ protected abstract void reportDragEndInternal(PointF velocity);
+}
diff --git a/src/com/android/launcher3/touch/BothAxesSwipeDetector.java b/src/com/android/launcher3/touch/BothAxesSwipeDetector.java
new file mode 100644
index 000000000..944391e9b
--- /dev/null
+++ b/src/com/android/launcher3/touch/BothAxesSwipeDetector.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2019 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.touch;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.launcher3.Utilities;
+
+/**
+ * Two dimensional scroll/drag/swipe gesture detector that reports x and y displacement/velocity.
+ */
+public class BothAxesSwipeDetector extends BaseSwipeDetector {
+
+ public static final int DIRECTION_UP = 1 << 0;
+ // Note that this will track left instead of right in RTL.
+ public static final int DIRECTION_RIGHT = 1 << 1;
+ public static final int DIRECTION_DOWN = 1 << 2;
+ // Note that this will track right instead of left in RTL.
+ public static final int DIRECTION_LEFT = 1 << 3;
+
+ /* Client of this gesture detector can register a callback. */
+ private final Listener mListener;
+
+ private int mScrollDirections;
+
+ public BothAxesSwipeDetector(@NonNull Context context, @NonNull Listener l) {
+ this(ViewConfiguration.get(context), l, Utilities.isRtl(context.getResources()));
+ }
+
+ @VisibleForTesting
+ protected BothAxesSwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l,
+ boolean isRtl) {
+ super(config, isRtl);
+ mListener = l;
+ }
+
+ public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) {
+ mScrollDirections = scrollDirectionFlags;
+ mIgnoreSlopWhenSettling = ignoreSlop;
+ }
+
+ @Override
+ protected boolean shouldScrollStart(PointF displacement) {
+ // Check if the client is interested in scroll in current direction.
+ boolean canScrollUp = (mScrollDirections & DIRECTION_UP) > 0
+ && displacement.y <= -mTouchSlop;
+ boolean canScrollRight = (mScrollDirections & DIRECTION_RIGHT) > 0
+ && displacement.x >= mTouchSlop;
+ boolean canScrollDown = (mScrollDirections & DIRECTION_DOWN) > 0
+ && displacement.y >= mTouchSlop;
+ boolean canScrollLeft = (mScrollDirections & DIRECTION_LEFT) > 0
+ && displacement.x <= -mTouchSlop;
+ return canScrollUp || canScrollRight || canScrollDown || canScrollLeft;
+ }
+
+ @Override
+ protected void reportDragStartInternal(boolean recatch) {
+ mListener.onDragStart(!recatch);
+ }
+
+ @Override
+ protected void reportDraggingInternal(PointF displacement, MotionEvent event) {
+ mListener.onDrag(displacement, event);
+ }
+
+ @Override
+ protected void reportDragEndInternal(PointF velocity) {
+ mListener.onDragEnd(velocity);
+ }
+
+ /** Listener to receive updates on the swipe. */
+ public interface Listener {
+ /** @param start whether this was the original drag start, as opposed to a recatch. */
+ void onDragStart(boolean start);
+
+ boolean onDrag(PointF displacement, MotionEvent motionEvent);
+
+ void onDragEnd(PointF velocity);
+ }
+}
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index babbcdd16..86d2b3901 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -17,10 +17,12 @@ package com.android.launcher3.touch;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
+
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
+import android.util.Log;
import android.view.View;
import android.view.View.OnLongClickListener;
@@ -32,6 +34,9 @@ import com.android.launcher3.Launcher;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.folder.Folder;
+import com.android.launcher3.testing.TestProtocol;
+
+import java.util.Arrays;
/**
* Class to handle long-clicks on workspace items and start drag as a result.
@@ -60,7 +65,7 @@ public class ItemLongClickListener {
if (info.container >= 0) {
Folder folder = Folder.getOpen(launcher);
if (folder != null) {
- if (!folder.getItemsInReadingOrder().contains(v)) {
+ if (!folder.getIconsInReadingOrder().contains(v)) {
folder.close(true);
} else {
folder.startDrag(v, dragOptions);
@@ -74,10 +79,19 @@ public class ItemLongClickListener {
}
private static boolean onAllAppsItemLongClick(View v) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_CONTEXT_MENU, "onAllAppsItemLongClick1");
+ }
Launcher launcher = Launcher.getLauncher(v.getContext());
if (!canStartDrag(launcher)) return false;
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_CONTEXT_MENU, "onAllAppsItemLongClick2");
+ }
// When we have exited all apps or are in transition, disregard long clicks
if (!launcher.isInState(ALL_APPS) && !launcher.isInState(OVERVIEW)) return false;
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_CONTEXT_MENU, "onAllAppsItemLongClick3");
+ }
if (launcher.getWorkspace().isSwitchingState()) return false;
// Start the drag
diff --git a/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
new file mode 100644
index 000000000..f2ebc4519
--- /dev/null
+++ b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2019 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.touch;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.launcher3.Utilities;
+
+/**
+ * One dimensional scroll/drag/swipe gesture detector (either HORIZONTAL or VERTICAL).
+ */
+public class SingleAxisSwipeDetector extends BaseSwipeDetector {
+
+ public static final int DIRECTION_POSITIVE = 1 << 0;
+ public static final int DIRECTION_NEGATIVE = 1 << 1;
+ public static final int DIRECTION_BOTH = DIRECTION_NEGATIVE | DIRECTION_POSITIVE;
+
+ public static final Direction VERTICAL = new Direction() {
+
+ @Override
+ boolean isPositive(float displacement) {
+ // Up
+ return displacement < 0;
+ }
+
+ @Override
+ boolean isNegative(float displacement) {
+ // Down
+ return displacement > 0;
+ }
+
+ @Override
+ float extractDirection(PointF direction) {
+ return direction.y;
+ }
+
+ @Override
+ boolean canScrollStart(PointF displacement, float touchSlop) {
+ return Math.abs(displacement.y) >= Math.max(Math.abs(displacement.x), touchSlop);
+ }
+
+ };
+
+ public static final Direction HORIZONTAL = new Direction() {
+
+ @Override
+ boolean isPositive(float displacement) {
+ // Right
+ return displacement > 0;
+ }
+
+ @Override
+ boolean isNegative(float displacement) {
+ // Left
+ return displacement < 0;
+ }
+
+ @Override
+ float extractDirection(PointF direction) {
+ return direction.x;
+ }
+
+ @Override
+ boolean canScrollStart(PointF displacement, float touchSlop) {
+ return Math.abs(displacement.x) >= Math.max(Math.abs(displacement.y), touchSlop);
+ }
+ };
+
+ private final Direction mDir;
+ /* Client of this gesture detector can register a callback. */
+ private final Listener mListener;
+
+ private int mScrollDirections;
+
+ public SingleAxisSwipeDetector(@NonNull Context context, @NonNull Listener l,
+ @NonNull Direction dir) {
+ this(ViewConfiguration.get(context), l, dir, Utilities.isRtl(context.getResources()));
+ }
+
+ @VisibleForTesting
+ protected SingleAxisSwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l,
+ @NonNull Direction dir, boolean isRtl) {
+ super(config, isRtl);
+ mListener = l;
+ mDir = dir;
+ }
+
+ public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) {
+ mScrollDirections = scrollDirectionFlags;
+ mIgnoreSlopWhenSettling = ignoreSlop;
+ }
+
+ public int getScrollDirections() {
+ return mScrollDirections;
+ }
+
+ /**
+ * Returns if the start drag was towards the positive direction or negative.
+ *
+ * @see #setDetectableScrollConditions(int, boolean)
+ * @see #DIRECTION_BOTH
+ */
+ public boolean wasInitialTouchPositive() {
+ return mDir.isPositive(mDir.extractDirection(mSubtractDisplacement));
+ }
+
+ @Override
+ protected boolean shouldScrollStart(PointF displacement) {
+ // Reject cases where the angle or slop condition is not met.
+ if (!mDir.canScrollStart(displacement, mTouchSlop)) {
+ return false;
+ }
+
+ // Check if the client is interested in scroll in current direction.
+ float displacementComponent = mDir.extractDirection(displacement);
+ return canScrollNegative(displacementComponent) || canScrollPositive(displacementComponent);
+ }
+
+ private boolean canScrollNegative(float displacement) {
+ return (mScrollDirections & DIRECTION_NEGATIVE) > 0 && mDir.isNegative(displacement);
+ }
+
+ private boolean canScrollPositive(float displacement) {
+ return (mScrollDirections & DIRECTION_POSITIVE) > 0 && mDir.isPositive(displacement);
+ }
+
+ @Override
+ protected void reportDragStartInternal(boolean recatch) {
+ mListener.onDragStart(!recatch);
+ }
+
+ @Override
+ protected void reportDraggingInternal(PointF displacement, MotionEvent event) {
+ mListener.onDrag(mDir.extractDirection(displacement), event);
+ }
+
+ @Override
+ protected void reportDragEndInternal(PointF velocity) {
+ float velocityComponent = mDir.extractDirection(velocity);
+ mListener.onDragEnd(velocityComponent);
+ }
+
+ /** Listener to receive updates on the swipe. */
+ public interface Listener {
+ /** @param start whether this was the original drag start, as opposed to a recatch. */
+ void onDragStart(boolean start);
+
+ // TODO remove
+ boolean onDrag(float displacement);
+
+ default boolean onDrag(float displacement, MotionEvent event) {
+ return onDrag(displacement);
+ }
+
+ void onDragEnd(float velocity);
+ }
+
+ public abstract static class Direction {
+
+ abstract boolean isPositive(float displacement);
+
+ abstract boolean isNegative(float displacement);
+
+ /** Returns the part of the given {@link PointF} that is relevant to this direction. */
+ abstract float extractDirection(PointF point);
+
+ /** Reject cases where the angle or slop condition is not met. */
+ abstract boolean canScrollStart(PointF displacement, float touchSlop);
+
+ }
+}
diff --git a/src/com/android/launcher3/touch/SwipeDetector.java b/src/com/android/launcher3/touch/SwipeDetector.java
deleted file mode 100644
index 3777a41ad..000000000
--- a/src/com/android/launcher3/touch/SwipeDetector.java
+++ /dev/null
@@ -1,407 +0,0 @@
-/*
- * Copyright (C) 2017 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.touch;
-
-import static android.view.MotionEvent.INVALID_POINTER_ID;
-
-import android.content.Context;
-import android.graphics.PointF;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.ViewConfiguration;
-
-import com.android.launcher3.Utilities;
-import com.android.launcher3.testing.TestProtocol;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-
-/**
- * One dimensional scroll/drag/swipe gesture detector.
- *
- * Definition of swipe is different from android system in that this detector handles
- * 'swipe to dismiss', 'swiping up/down a container' but also keeps scrolling state before
- * swipe action happens
- */
-public class SwipeDetector {
-
- private static final boolean DBG = false;
- private static final String TAG = "SwipeDetector";
-
- private int mScrollConditions;
- public static final int DIRECTION_POSITIVE = 1 << 0;
- public static final int DIRECTION_NEGATIVE = 1 << 1;
- public static final int DIRECTION_BOTH = DIRECTION_NEGATIVE | DIRECTION_POSITIVE;
-
- private static final float ANIMATION_DURATION = 1200;
-
- protected int mActivePointerId = INVALID_POINTER_ID;
-
- /**
- * The minimum release velocity in pixels per millisecond that triggers fling..
- */
- public static final float RELEASE_VELOCITY_PX_MS = 1.0f;
-
- /* Scroll state, this is set to true during dragging and animation. */
- private ScrollState mState = ScrollState.IDLE;
-
- enum ScrollState {
- IDLE,
- DRAGGING, // onDragStart, onDrag
- SETTLING // onDragEnd
- }
-
- public static abstract class Direction {
-
- abstract float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint,
- boolean isRtl);
-
- /**
- * Distance in pixels a touch can wander before we think the user is scrolling.
- */
- abstract float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos);
-
- abstract float getVelocity(VelocityTracker tracker, boolean isRtl);
-
- abstract boolean isPositive(float displacement);
-
- abstract boolean isNegative(float displacement);
- }
-
- public static final Direction VERTICAL = new Direction() {
-
- @Override
- float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint, boolean isRtl) {
- return ev.getY(pointerIndex) - refPoint.y;
- }
-
- @Override
- float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) {
- return Math.abs(ev.getX(pointerIndex) - downPos.x);
- }
-
- @Override
- float getVelocity(VelocityTracker tracker, boolean isRtl) {
- return tracker.getYVelocity();
- }
-
- @Override
- boolean isPositive(float displacement) {
- // Up
- return displacement < 0;
- }
-
- @Override
- boolean isNegative(float displacement) {
- // Down
- return displacement > 0;
- }
- };
-
- public static final Direction HORIZONTAL = new Direction() {
-
- @Override
- float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint, boolean isRtl) {
- float displacement = ev.getX(pointerIndex) - refPoint.x;
- if (isRtl) {
- displacement = -displacement;
- }
- return displacement;
- }
-
- @Override
- float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) {
- return Math.abs(ev.getY(pointerIndex) - downPos.y);
- }
-
- @Override
- float getVelocity(VelocityTracker tracker, boolean isRtl) {
- float velocity = tracker.getXVelocity();
- if (isRtl) {
- velocity = -velocity;
- }
- return velocity;
- }
-
- @Override
- boolean isPositive(float displacement) {
- // Right
- return displacement > 0;
- }
-
- @Override
- boolean isNegative(float displacement) {
- // Left
- return displacement < 0;
- }
- };
-
- //------------------- ScrollState transition diagram -----------------------------------
- //
- // IDLE -> (mDisplacement > mTouchSlop) -> DRAGGING
- // DRAGGING -> (MotionEvent#ACTION_UP, MotionEvent#ACTION_CANCEL) -> SETTLING
- // SETTLING -> (MotionEvent#ACTION_DOWN) -> DRAGGING
- // SETTLING -> (View settled) -> IDLE
-
- private void setState(ScrollState newState) {
- if (DBG) {
- Log.d(TAG, "setState:" + mState + "->" + newState);
- }
- // onDragStart and onDragEnd is reported ONLY on state transition
- if (newState == ScrollState.DRAGGING) {
- initializeDragging();
- if (mState == ScrollState.IDLE) {
- reportDragStart(false /* recatch */);
- } else if (mState == ScrollState.SETTLING) {
- reportDragStart(true /* recatch */);
- }
- }
- if (newState == ScrollState.SETTLING) {
- reportDragEnd();
- }
-
- mState = newState;
- }
-
- public boolean isDraggingOrSettling() {
- return mState == ScrollState.DRAGGING || mState == ScrollState.SETTLING;
- }
-
- public int getDownX() {
- return (int) mDownPos.x;
- }
-
- public int getDownY() {
- return (int) mDownPos.y;
- }
- /**
- * There's no touch and there's no animation.
- */
- public boolean isIdleState() {
- return mState == ScrollState.IDLE;
- }
-
- public boolean isSettlingState() {
- return mState == ScrollState.SETTLING;
- }
-
- public boolean isDraggingState() {
- return mState == ScrollState.DRAGGING;
- }
-
- private final PointF mDownPos = new PointF();
- private final PointF mLastPos = new PointF();
- private final Direction mDir;
- private final boolean mIsRtl;
-
- private final float mTouchSlop;
- private final float mMaxVelocity;
-
- /* Client of this gesture detector can register a callback. */
- private final Listener mListener;
-
- private VelocityTracker mVelocityTracker;
-
- private float mLastDisplacement;
- private float mDisplacement;
-
- private float mSubtractDisplacement;
- private boolean mIgnoreSlopWhenSettling;
-
- public interface Listener {
- void onDragStart(boolean start);
-
- boolean onDrag(float displacement);
-
- default boolean onDrag(float displacement, MotionEvent event) {
- return onDrag(displacement);
- }
-
- void onDragEnd(float velocity, boolean fling);
- }
-
- public SwipeDetector(@NonNull Context context, @NonNull Listener l, @NonNull Direction dir) {
- this(ViewConfiguration.get(context), l, dir, Utilities.isRtl(context.getResources()));
- }
-
- @VisibleForTesting
- protected SwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l,
- @NonNull Direction dir, boolean isRtl) {
- mListener = l;
- mDir = dir;
- mIsRtl = isRtl;
- mTouchSlop = config.getScaledTouchSlop();
- mMaxVelocity = config.getScaledMaximumFlingVelocity();
- }
-
- public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) {
- mScrollConditions = scrollDirectionFlags;
- mIgnoreSlopWhenSettling = ignoreSlop;
- }
-
- public int getScrollDirections() {
- return mScrollConditions;
- }
-
- private boolean shouldScrollStart(MotionEvent ev, int pointerIndex) {
- // reject cases where the angle or slop condition is not met.
- if (Math.max(mDir.getActiveTouchSlop(ev, pointerIndex, mDownPos), mTouchSlop)
- > Math.abs(mDisplacement)) {
- return false;
- }
-
- // Check if the client is interested in scroll in current direction.
- if (((mScrollConditions & DIRECTION_NEGATIVE) > 0 && mDir.isNegative(mDisplacement)) ||
- ((mScrollConditions & DIRECTION_POSITIVE) > 0 && mDir.isPositive(mDisplacement))) {
- return true;
- }
- return false;
- }
-
- public boolean onTouchEvent(MotionEvent ev) {
- int actionMasked = ev.getActionMasked();
- if (actionMasked == MotionEvent.ACTION_DOWN && mVelocityTracker != null) {
- mVelocityTracker.clear();
- }
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- mVelocityTracker.addMovement(ev);
-
- switch (actionMasked) {
- case MotionEvent.ACTION_DOWN:
- mActivePointerId = ev.getPointerId(0);
- mDownPos.set(ev.getX(), ev.getY());
- mLastPos.set(mDownPos);
- mLastDisplacement = 0;
- mDisplacement = 0;
-
- if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
- setState(ScrollState.DRAGGING);
- }
- break;
- //case MotionEvent.ACTION_POINTER_DOWN:
- case MotionEvent.ACTION_POINTER_UP:
- int ptrIdx = ev.getActionIndex();
- int ptrId = ev.getPointerId(ptrIdx);
- if (ptrId == mActivePointerId) {
- final int newPointerIdx = ptrIdx == 0 ? 1 : 0;
- mDownPos.set(
- ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x),
- ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y));
- mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx));
- mActivePointerId = ev.getPointerId(newPointerIdx);
- }
- break;
- case MotionEvent.ACTION_MOVE:
- int pointerIndex = ev.findPointerIndex(mActivePointerId);
- if (pointerIndex == INVALID_POINTER_ID) {
- break;
- }
- mDisplacement = mDir.getDisplacement(ev, pointerIndex, mDownPos, mIsRtl);
-
- // handle state and listener calls.
- if (mState != ScrollState.DRAGGING && shouldScrollStart(ev, pointerIndex)) {
- setState(ScrollState.DRAGGING);
- }
- if (mState == ScrollState.DRAGGING) {
- reportDragging(ev);
- }
- mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
- break;
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- // These are synthetic events and there is no need to update internal values.
- if (mState == ScrollState.DRAGGING) {
- setState(ScrollState.SETTLING);
- }
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- break;
- default:
- break;
- }
- return true;
- }
-
- public void finishedScrolling() {
- setState(ScrollState.IDLE);
- }
-
- private boolean reportDragStart(boolean recatch) {
- mListener.onDragStart(!recatch);
- if (DBG) {
- Log.d(TAG, "onDragStart recatch:" + recatch);
- }
- return true;
- }
-
- private void initializeDragging() {
- if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
- mSubtractDisplacement = 0;
- }
- if (mDisplacement > 0) {
- mSubtractDisplacement = mTouchSlop;
- } else {
- mSubtractDisplacement = -mTouchSlop;
- }
- }
-
- /**
- * Returns if the start drag was towards the positive direction or negative.
- *
- * @see #setDetectableScrollConditions(int, boolean)
- * @see #DIRECTION_BOTH
- */
- public boolean wasInitialTouchPositive() {
- return mDir.isPositive(mSubtractDisplacement);
- }
-
- private boolean reportDragging(MotionEvent event) {
- if (mDisplacement != mLastDisplacement) {
- if (DBG) {
- Log.d(TAG, String.format("onDrag disp=%.1f", mDisplacement));
- }
-
- mLastDisplacement = mDisplacement;
- return mListener.onDrag(mDisplacement - mSubtractDisplacement, event);
- }
- return true;
- }
-
- private void reportDragEnd() {
- mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
- float velocity = mDir.getVelocity(mVelocityTracker, mIsRtl) / 1000;
- if (DBG) {
- Log.d(TAG, String.format("onScrollEnd disp=%.1f, velocity=%.1f",
- mDisplacement, velocity));
- }
-
- mListener.onDragEnd(velocity, Math.abs(velocity) > RELEASE_VELOCITY_PX_MS);
- }
-
- public static long calculateDuration(float velocity, float progressNeeded) {
- // TODO: make these values constants after tuning.
- float velocityDivisor = Math.max(2f, Math.abs(0.5f * velocity));
- float travelDistance = Math.max(0.2f, progressNeeded);
- long duration = (long) Math.max(100, ANIMATION_DURATION / velocityDivisor * travelDistance);
- if (DBG) {
- Log.d(TAG, String.format("calculateDuration=%d, v=%f, d=%f", duration, velocity, progressNeeded));
- }
- return duration;
- }
-}