aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorandroid-build-prod (mdb) <android-build-team-robot@google.com>2020-08-19 02:16:57 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2020-08-19 02:16:57 +0000
commitdedac03a1dce2342f54c90ff72f4890303cc3570 (patch)
tree77506be8d0a0ee38a554d2d49cb923ea999ef46c
parentafc0c630a4c9a68dae618b377396d0bdbdd3a9e7 (diff)
parentef96e0ebc91580d379fcc9577a2beb6901eb0b62 (diff)
downloadsupport-dedac03a1dce2342f54c90ff72f4890303cc3570.tar.gz
Merge "Handle termination of disallow correctly." into snap-temp-L23500000669881336
-rw-r--r--recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/EventRouterTest.java31
-rw-r--r--recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/GestureDetectorOnItemTouchListenerAdapterTest.java107
-rw-r--r--recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/GestureDetectorWrapperTest.java176
-rw-r--r--recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/ResetMangerTest.java2
-rw-r--r--recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/EventRouter.java23
-rw-r--r--recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/GestureDetectorWrapper.java (renamed from recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/GestureDetectorOnItemTouchListenerAdapter.java)43
-rw-r--r--recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/MotionEvents.java15
-rw-r--r--recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/OperationMonitor.java2
-rw-r--r--recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionTracker.java26
9 files changed, 275 insertions, 150 deletions
diff --git a/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/EventRouterTest.java b/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/EventRouterTest.java
index 41aca917aa1..1d7c9116109 100644
--- a/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/EventRouterTest.java
+++ b/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/EventRouterTest.java
@@ -17,6 +17,8 @@
package androidx.recyclerview.selection;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import android.view.MotionEvent;
@@ -64,38 +66,37 @@ public class EventRouterTest {
public void testDropsEventAfterDisallowCalled() {
mRouter.onRequestDisallowInterceptTouchEvent(true);
- mRouter.onInterceptTouchEvent(mRecyclerView, TestEvents.Touch.DOWN);
- mRouter.onTouchEvent(mRecyclerView, TestEvents.Touch.DOWN);
+ mRouter.onInterceptTouchEvent(mRecyclerView, TestEvents.Touch.MOVE);
+ mRouter.onTouchEvent(mRecyclerView, TestEvents.Touch.MOVE);
mListener.assertOnInterceptTouchEventCalled(0);
mListener.assertOnTouchEventCalled(0);
}
@Test
- public void testResetsDisallowOnUplEvents() {
+ public void testResetsDisallowOnDownEvents() {
mRouter.onRequestDisallowInterceptTouchEvent(true);
- mRouter.onInterceptTouchEvent(mRecyclerView, TestEvents.Touch.UP);
mRouter.onInterceptTouchEvent(mRecyclerView, TestEvents.Touch.DOWN);
- mRouter.onTouchEvent(mRecyclerView, TestEvents.Touch.DOWN);
+ mRouter.onInterceptTouchEvent(mRecyclerView, TestEvents.Touch.UP);
+ mRouter.onTouchEvent(mRecyclerView, TestEvents.Touch.UP);
- // The FINGER/UP event that resets "disallow" is also dispatched to touch-event
+ // The FINGER/DOWN event that resets "disallow" is also dispatched to touch-event
// listeners. So expect 2 calls.
mListener.assertOnInterceptTouchEventCalled(2);
mListener.assertOnTouchEventCalled(1);
}
@Test
- public void testResetsDisallowOnCancelEvents() {
+ public void testResettable() {
+ // Only resettable when disallowIntercept has been requested.
mRouter.onRequestDisallowInterceptTouchEvent(true);
- mRouter.onInterceptTouchEvent(mRecyclerView,
- TestEvents.builder().action(MotionEvent.ACTION_CANCEL).build());
- mRouter.onInterceptTouchEvent(mRecyclerView, TestEvents.Touch.DOWN);
- mRouter.onTouchEvent(mRecyclerView, TestEvents.Touch.DOWN);
+ assertTrue(mRouter.isResetRequired());
- // The UNKNOWN/CANCEL event that resets "disallow" is not dispatched to any listeners
- // (because there are no listeners registered for "UNKNOWN" tooltype. So expect 1 call.
- mListener.assertOnInterceptTouchEventCalled(1);
- mListener.assertOnTouchEventCalled(1);
+ ResetManager<?> mgr = new ResetManager<>();
+ mgr.addResetHandler(mRouter);
+ mgr.getSelectionObserver().onSelectionCleared(); // Results in reset.
+
+ assertFalse(mRouter.isResetRequired());
}
private static final class TestOnItemTouchListener implements OnItemTouchListener {
diff --git a/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/GestureDetectorOnItemTouchListenerAdapterTest.java b/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/GestureDetectorOnItemTouchListenerAdapterTest.java
deleted file mode 100644
index 7c26ec6fb02..00000000000
--- a/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/GestureDetectorOnItemTouchListenerAdapterTest.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright 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 androidx.recyclerview.selection;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.os.Looper;
-import android.view.GestureDetector;
-import android.view.MotionEvent;
-
-import androidx.recyclerview.selection.testing.TestEvents;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public final class GestureDetectorOnItemTouchListenerAdapterTest {
-
- private GestureDetectorOnItemTouchListenerAdapter mAdapter;
- private TestGestureDetector mDetector;
-
- @Before
- public void setUp() {
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
- mDetector = new TestGestureDetector(
- ApplicationProvider.getApplicationContext(),
- new GestureDetector.SimpleOnGestureListener() // do nothing listener
- );
- mAdapter = new GestureDetectorOnItemTouchListenerAdapter(mDetector);
- }
-
- @Test
- public void testReflectsGestureDetectorReturnValue() {
- mDetector.mReturnValue = true;
- assertTrue(mAdapter.onInterceptTouchEvent(null, TestEvents.Mouse.SECONDARY_CLICK));
-
- mDetector.mReturnValue = false;
- assertFalse(mAdapter.onInterceptTouchEvent(null, TestEvents.Mouse.SECONDARY_CLICK));
- }
-
- @Test
- public void testAdapterRespectsDisallowIntercept() {
- mAdapter.onInterceptTouchEvent(/* RecyclerView */ null, TestEvents.Touch.DOWN);
- mDetector.assertOnTouchCalled(1);
-
- // After disallow intercept, no more calls to onTouch should be forwarded.
- mAdapter.onRequestDisallowInterceptTouchEvent(true);
- mAdapter.onInterceptTouchEvent(/* RecyclerView */ null, TestEvents.Touch.MOVE);
- mDetector.assertOnTouchCalled(1);
-
- // UP event should reset event handling, allowing forwarding of events again.
- // Num touch events forwarded to GestureDetector should increase by 1 (for the DOWN)
- mAdapter.onInterceptTouchEvent(/* RecyclerView */ null, TestEvents.Touch.UP);
- mAdapter.onInterceptTouchEvent(/* RecyclerView */ null, TestEvents.Touch.DOWN);
- mDetector.assertOnTouchCalled(2);
- }
-
- private static final class TestGestureDetector extends GestureDetector {
-
- private final List<MotionEvent> mDetectorEvents = new ArrayList<>();
- boolean mReturnValue;
-
- TestGestureDetector(Context context, SimpleOnGestureListener listener) {
- super(context, listener);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- mDetectorEvents.add(ev);
- return mReturnValue;
- }
-
- void assertOnTouchCalled(int expectedTimes) {
- assertEquals(expectedTimes, mDetectorEvents.size());
- }
- }
-
- ;
-}
diff --git a/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/GestureDetectorWrapperTest.java b/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/GestureDetectorWrapperTest.java
new file mode 100644
index 00000000000..584bacb26ba
--- /dev/null
+++ b/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/GestureDetectorWrapperTest.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 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 androidx.recyclerview.selection;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.os.Looper;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+
+import androidx.recyclerview.selection.testing.TestEvents;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class GestureDetectorWrapperTest {
+
+ private GestureDetectorWrapper mAdapter;
+ private TestGestureDetector mDetector;
+ private TestListener mListener;
+
+ @Before
+ public void setUp() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ mListener = new TestListener();
+ mDetector = new TestGestureDetector(
+ ApplicationProvider.getApplicationContext(),
+ mListener);
+
+ mAdapter = new GestureDetectorWrapper(mDetector);
+ }
+
+ @Test
+ public void testReflectsGestureDetectorReturnValue() {
+ mListener.mNextReturnVal = true;
+ assertTrue(mAdapter.onInterceptTouchEvent(null, TestEvents.Touch.DOWN));
+
+ mListener.mNextReturnVal = false;
+ assertFalse(mAdapter.onInterceptTouchEvent(null, TestEvents.Touch.DOWN));
+ }
+
+ @Test
+ public void testRelaysActions() {
+ mAdapter.onInterceptTouchEvent(/* RecyclerView */ null, TestEvents.Touch.DOWN);
+ mAdapter.onInterceptTouchEvent(/* RecyclerView */ null, TestEvents.Touch.MOVE);
+ mAdapter.onInterceptTouchEvent(/* RecyclerView */ null, TestEvents.Touch.UP);
+ mDetector.assertOnTouchCalled(1, MotionEvent.ACTION_DOWN);
+ mDetector.assertOnTouchCalled(1, MotionEvent.ACTION_MOVE);
+ mDetector.assertOnTouchCalled(1, MotionEvent.ACTION_UP);
+
+ mDetector.assertOnTouchCalled(3);
+ }
+
+ @Test
+ public void testRequestDisallowSendsFakeCancelEvent() {
+ mAdapter.onRequestDisallowInterceptTouchEvent(true);
+
+ // The adapter sends a synthetic ACTION_CANCEL event to GD when disallow is called.
+ mDetector.assertOnTouchCalled(1, MotionEvent.ACTION_CANCEL);
+ mDetector.assertOnTouchCalled(1);
+ }
+
+ @Test
+ public void testResettable() {
+ ResetManager<?> mgr = new ResetManager<>();
+ mgr.addResetHandler(mAdapter);
+ mgr.getSelectionObserver().onSelectionCleared(); // Results in reset.
+
+ // The adapter sends a synthetic ACTION_CANCEL event to GD when disallow is called.
+ mDetector.assertOnTouchCalled(1, MotionEvent.ACTION_CANCEL);
+ mDetector.assertOnTouchCalled(1);
+ }
+
+ @Test
+ public void testRespectsDisallowIntercept() {
+ mAdapter.onRequestDisallowInterceptTouchEvent(true);
+
+ mAdapter.onInterceptTouchEvent(/* RecyclerView */ null, TestEvents.Touch.MOVE);
+ mAdapter.onInterceptTouchEvent(/* RecyclerView */ null, TestEvents.Touch.UP);
+ // Sending DOWN would reset state, so we only test with MOVE and UP.
+
+ mDetector.assertOnTouchCalled(0, MotionEvent.ACTION_MOVE);
+ mDetector.assertOnTouchCalled(0, MotionEvent.ACTION_UP);
+
+ // Only the initial CANCEL event sent. Everything else ignored.
+ mDetector.assertOnTouchCalled(1);
+ }
+
+ @Test
+ public void testResetsDisallowInterceptOnDown() {
+ mAdapter.onRequestDisallowInterceptTouchEvent(true);
+ // Synthetic CANCEL event used to coerce GestureDetector into resetting internal state.
+ // Issued whenever disallowIntercept = true
+ mDetector.assertOnTouchCalled(1, MotionEvent.ACTION_CANCEL);
+
+ // Should reset DOWN
+ mAdapter.onInterceptTouchEvent(/* RecyclerView */ null, TestEvents.Touch.DOWN);
+ mAdapter.onInterceptTouchEvent(/* RecyclerView */ null, TestEvents.Touch.MOVE);
+ mAdapter.onInterceptTouchEvent(/* RecyclerView */ null, TestEvents.Touch.UP);
+ mDetector.assertOnTouchCalled(1, MotionEvent.ACTION_DOWN);
+ mDetector.assertOnTouchCalled(1, MotionEvent.ACTION_MOVE);
+ mDetector.assertOnTouchCalled(1, MotionEvent.ACTION_UP);
+
+ mDetector.assertOnTouchCalled(4); // CANCEL + DOWN,MOVE,UP
+ }
+
+ private static final class TestGestureDetector extends GestureDetector {
+
+ private final List<MotionEvent> mDetectorEvents = new ArrayList<>();
+
+ TestGestureDetector(Context context, SimpleOnGestureListener listener) {
+ super(context, listener);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ mDetectorEvents.add(ev);
+ return super.onTouchEvent(ev);
+ }
+
+ void assertOnTouchCalled(int expectedTimes) {
+ assertEquals(expectedTimes, mDetectorEvents.size());
+ }
+
+ void assertOnTouchCalled(int expectedTimes, int action) {
+ int total = 0;
+ for (MotionEvent e : mDetectorEvents) {
+ if (e.getAction() == action) {
+ total++;
+ }
+ }
+ assertEquals(expectedTimes, total);
+ }
+ }
+
+ private static class TestListener extends GestureDetector.SimpleOnGestureListener {
+ boolean mNextReturnVal = false;
+
+ public boolean onSingleTapUp(MotionEvent e) {
+ return mNextReturnVal;
+ }
+
+ public boolean onDown(MotionEvent e) {
+ return mNextReturnVal;
+ }
+ }
+}
diff --git a/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/ResetMangerTest.java b/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/ResetMangerTest.java
index 2080ea75ca2..abbc75e526d 100644
--- a/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/ResetMangerTest.java
+++ b/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/ResetMangerTest.java
@@ -44,7 +44,7 @@ public final class ResetMangerTest {
@Before
public void setUp() {
- mManager = new ResetManager();
+ mManager = new ResetManager<>();
mResettable1 = new TestResettable(true);
mResettable2 = new TestResettable(true);
mManager.addResetHandler(mResettable1);
diff --git a/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/EventRouter.java b/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/EventRouter.java
index 9cb8aa0beb0..c2a454cb907 100644
--- a/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/EventRouter.java
+++ b/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/EventRouter.java
@@ -33,7 +33,7 @@ import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener;
* {@link RecyclerView#addOnItemTouchListener(OnItemTouchListener)}. Despite "Touch"
* being in the name, it receives MotionEvents for all types of tools.
*/
-final class EventRouter implements OnItemTouchListener {
+final class EventRouter implements OnItemTouchListener, Resettable {
private final ToolHandlerRegistry<OnItemTouchListener> mDelegates;
private boolean mDisallowIntercept;
@@ -55,10 +55,8 @@ final class EventRouter implements OnItemTouchListener {
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
- // Reset disallow intercept when the event is Up or Cancel as described
- // in https://developer.android.com/reference/android/widget/HorizontalScrollView
- // #requestDisallowInterceptTouchEvent(boolean)
- if (MotionEvents.isActionUp(e) || MotionEvents.isActionCancel(e)) {
+ // Reset disallow when the event is down as advised in http://b/139141511#comment20.
+ if (mDisallowIntercept && MotionEvents.isActionDown(e)) {
mDisallowIntercept = false;
}
return !mDisallowIntercept && mDelegates.get(e).onInterceptTouchEvent(rv, e);
@@ -73,10 +71,25 @@ final class EventRouter implements OnItemTouchListener {
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+ if (!disallowIntercept) {
+ return; // Ignore as advised in http://b/139141511#comment20
+ }
+
// Some types of views, such as HorizontalScrollView, may want
// to take over the input stream. In this case they'll call this method
// with disallowIntercept=true. mDisallowIntercept is reset on UP or CANCEL
// events in onInterceptTouchEvent.
mDisallowIntercept = disallowIntercept;
}
+
+
+ @Override
+ public boolean isResetRequired() {
+ return mDisallowIntercept;
+ }
+
+ @Override
+ public void reset() {
+ mDisallowIntercept = false;
+ }
}
diff --git a/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/GestureDetectorOnItemTouchListenerAdapter.java b/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/GestureDetectorWrapper.java
index 94ffd03d19d..2a51ec9483e 100644
--- a/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/GestureDetectorOnItemTouchListenerAdapter.java
+++ b/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/GestureDetectorWrapper.java
@@ -25,14 +25,15 @@ import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
/**
- * Class allowing GestureDetector to listen directly to RecyclerView touch events.
+ * A wrapper class for GestureDetector allowing it interact with SelectionTracker
+ * and its dependencies (like RecyclerView) on terms more amenable to SelectionTracker.
*/
-final class GestureDetectorOnItemTouchListenerAdapter implements RecyclerView.OnItemTouchListener {
+final class GestureDetectorWrapper implements RecyclerView.OnItemTouchListener, Resettable {
private final GestureDetector mDetector;
private boolean mDisallowIntercept;
- GestureDetectorOnItemTouchListenerAdapter(@NonNull GestureDetector detector) {
+ GestureDetectorWrapper(@NonNull GestureDetector detector) {
checkArgument(detector != null);
mDetector = detector;
@@ -40,12 +41,9 @@ final class GestureDetectorOnItemTouchListenerAdapter implements RecyclerView.On
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
- // Reset disallow intercept when the event is Up or Cancel as described
- // in https://developer.android.com/reference/android/widget/HorizontalScrollView
- // #requestDisallowInterceptTouchEvent(boolean)
- if (MotionEvents.isActionUp(e) || MotionEvents.isActionCancel(e)) {
+ // Reset disallow when the event is down as advised in http://b/139141511#comment20.
+ if (mDisallowIntercept && MotionEvents.isActionDown(e)) {
mDisallowIntercept = false;
- return false;
}
// While the idea of "intercepting" an event stream isn't consistent
@@ -62,10 +60,39 @@ final class GestureDetectorOnItemTouchListenerAdapter implements RecyclerView.On
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+ if (!disallowIntercept) {
+ return; // Ignore as advised in http://b/139141511#comment20
+ }
+
// Some types of views, such as HorizontalScrollView, may want
// to take over the input stream. In this case they'll call this method
// with disallowIntercept=true. mDisallowIntercept is reset on UP or CANCEL
// events in onInterceptTouchEvent.
mDisallowIntercept = disallowIntercept;
+
+
+ // GestureDetector may have internal state (such as timers) that can
+ // result in subsequent event handlers being called, even after
+ // we receive a request to disallow intercept (e.g. LONG_PRESS).
+ // For that reason we proactively reset GestureDetector.
+ sendCancelEvent();
+ }
+
+ @Override
+ public boolean isResetRequired() {
+ // Always resettable as we don't know the specifics of GD's internal state.
+ return true;
+ }
+
+ @Override
+ public void reset() {
+ mDisallowIntercept = false;
+ sendCancelEvent();
+ }
+
+ private void sendCancelEvent() {
+ // GestureDetector does not provide a public affordance for resetting
+ // it's internal state, so we send it a synthetic ACTION_CANCEL event.
+ mDetector.onTouchEvent(MotionEvents.createCancelEvent());
}
}
diff --git a/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/MotionEvents.java b/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/MotionEvents.java
index aac3c0674d5..d2278a39b1d 100644
--- a/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/MotionEvents.java
+++ b/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/MotionEvents.java
@@ -27,7 +27,8 @@ import androidx.annotation.NonNull;
*/
final class MotionEvents {
- private MotionEvents() {}
+ private MotionEvents() {
+ }
static boolean isMouseEvent(@NonNull MotionEvent e) {
return e.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE;
@@ -107,7 +108,6 @@ final class MotionEvents {
/**
* Returns true if the event is a drag event (which is presumbaly, but not
* explicitly required to be a mouse event).
- * @param e
*/
static boolean isPointerDragEvent(MotionEvent e) {
return isPrimaryMouseButtonPressed(e)
@@ -117,4 +117,15 @@ final class MotionEvents {
private static boolean hasBit(int metaState, int bit) {
return (metaState & bit) != 0;
}
+
+ static MotionEvent createCancelEvent() {
+ return MotionEvent.obtain(
+ 0, // down time
+ 1, // event time
+ MotionEvent.ACTION_CANCEL,
+ 0, // x
+ 0, // y
+ 0 // metaState
+ );
+ }
}
diff --git a/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/OperationMonitor.java b/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/OperationMonitor.java
index 53d4b82ed38..8d0ac571146 100644
--- a/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/OperationMonitor.java
+++ b/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/OperationMonitor.java
@@ -52,7 +52,7 @@ public final class OperationMonitor {
// Ideally OperationMonitor would implement Resettable
// directly, but Metalava couldn't understand that
// `OperationMonitor` was public API while `Resettable` was
- // not. This is our klunkuy workaround.
+ // not. This is our clever workaround :)
private final Resettable mResettable = new Resettable() {
@Override
diff --git a/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionTracker.java b/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionTracker.java
index dd045c66838..b8dc24aa15a 100644
--- a/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionTracker.java
+++ b/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionTracker.java
@@ -734,19 +734,20 @@ public abstract class SelectionTracker<K> {
GestureDetector gestureDetector = new GestureDetector(mContext, gestureRouter);
// GestureSelectionHelper provides logic that interprets a combination
- // of motions and gestures in order to provide gesture driven selection support
- // when used in conjunction with RecyclerView.
- final GestureSelectionHelper gestureHelper = GestureSelectionHelper.create(
+ // of motions and gestures in order to provide fluid "long-press and drag"
+ // finger driven selection support.
+ final GestureSelectionHelper gestureSelectionHelper = GestureSelectionHelper.create(
tracker, mSelectionPredicate, mRecyclerView, scroller, mMonitor);
// EventRouter receives events for RecyclerView, dispatching to handlers
// registered by tool-type.
EventRouter eventRouter = new EventRouter();
+ GestureDetectorWrapper gestureDetectorWrapper =
+ new GestureDetectorWrapper(gestureDetector);
// Finally hook the framework up to listening to RecycleView events.
mRecyclerView.addOnItemTouchListener(eventRouter);
- mRecyclerView.addOnItemTouchListener(
- new GestureDetectorOnItemTouchListenerAdapter(gestureDetector));
+ mRecyclerView.addOnItemTouchListener(gestureDetectorWrapper);
// Reset manager listens for cancel events from RecyclerView. In response to that it
// advises other classes it is time to reset state.
@@ -756,20 +757,23 @@ public abstract class SelectionTracker<K> {
//
// 1. Monitor selection reset which can be invoked by clients in response
// to back key press and some application lifecycle events.
- //
- // 2. Monitor ACTION_CANCEL events (which arrive exclusively
- // via TOOL_TYPE_UNKNOWN).
tracker.addObserver(resetMgr.getSelectionObserver());
+ // ...and 2. Monitor ACTION_CANCEL events (which arrive exclusively
+ // via TOOL_TYPE_UNKNOWN).
+ //
// CAUTION! Registering resetMgr directly with RecyclerView#addOnItemTouchListener
// will not work as expected. Once EventRouter returns true, RecyclerView will
// no longer dispatch any events to other listeners for the duration of the
// stream, not even ACTION_CANCEL events.
eventRouter.set(MotionEvent.TOOL_TYPE_UNKNOWN, resetMgr.getInputListener());
+ // Finally register all of the Resettables.
resetMgr.addResetHandler(tracker);
resetMgr.addResetHandler(mMonitor.asResettable());
- resetMgr.addResetHandler(gestureHelper);
+ resetMgr.addResetHandler(gestureSelectionHelper);
+ resetMgr.addResetHandler(gestureDetectorWrapper);
+ resetMgr.addResetHandler(eventRouter);
// But before you move on, there's more work to do. Event plumbing has been
// installed, but we haven't registered any of our helpers or callbacks.
@@ -821,7 +825,7 @@ public abstract class SelectionTracker<K> {
@Override
public void run() {
if (mSelectionPredicate.canSelectMultiple()) {
- gestureHelper.start();
+ gestureSelectionHelper.start();
}
}
},
@@ -837,7 +841,7 @@ public abstract class SelectionTracker<K> {
for (int toolType : mGestureToolTypes) {
gestureRouter.register(toolType, touchHandler);
- eventRouter.set(toolType, gestureHelper);
+ eventRouter.set(toolType, gestureSelectionHelper);
}
// Provides high level glue for binding mouse events and gestures