Merge "Better event handling when coming from a trackpad." into arc-apps
am: 14b30ae40a
Change-Id: I47fe8abf63a05285e039e4b66178e1bcab639aa7
diff --git a/src/com/android/documentsui/base/Events.java b/src/com/android/documentsui/base/Events.java
index 3c57c81..dbadbfb 100644
--- a/src/com/android/documentsui/base/Events.java
+++ b/src/com/android/documentsui/base/Events.java
@@ -134,6 +134,20 @@
/** Returns true if the action is the final release of a mouse or touch. */
boolean isActionUp();
+ /**
+ * Returns true when the action is the initial press of a non-primary (ex. second finger)
+ * pointer.
+ * See {@link MotionEvent#ACTION_POINTER_DOWN}.
+ */
+ boolean isMultiPointerActionDown();
+
+ /**
+ * Returns true when the action is the final of a non-primary (ex. second finger)
+ * pointer.
+ * * See {@link MotionEvent#ACTION_POINTER_UP}.
+ */
+ boolean isMultiPointerActionUp();
+
/** Returns true if the action is neither the initial nor the final release of a mouse
* or touch. */
boolean isActionMove();
@@ -150,6 +164,7 @@
float getY();
float getRawX();
float getRawY();
+ int getPointerCount();
/** Returns true if there is an item under the finger/cursor. */
boolean isOverItem();
@@ -161,9 +176,17 @@
*/
boolean isOverModelItem();
- /** Returns true if the event is over an area that can be dragged via touch */
+ /**
+ * Returns true if the event is over an area that can be dragged via touch.
+ * List items have a white area that is not draggable.
+ */
boolean isOverDragHotspot();
+ /**
+ * Returns true if the event is a two/three-finger scroll on touchpad.
+ */
+ boolean isTouchpadScroll();
+
/** Returns the adapter position of the item under the finger/cursor. */
int getItemPosition();
@@ -268,6 +291,17 @@
}
@Override
+ public boolean isMultiPointerActionDown() {
+ return mEvent.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN;
+ }
+
+ @Override
+ public boolean isMultiPointerActionUp() {
+ return mEvent.getActionMasked() == MotionEvent.ACTION_POINTER_UP;
+ }
+
+
+ @Override
public boolean isActionMove() {
return mEvent.getActionMasked() == MotionEvent.ACTION_MOVE;
}
@@ -303,6 +337,18 @@
}
@Override
+ public int getPointerCount() {
+ return mEvent.getPointerCount();
+ }
+
+ @Override
+ public boolean isTouchpadScroll() {
+ // Touchpad inputs are treated as mouse inputs, and when scrolling, there are no buttons
+ // returned.
+ return isMouseEvent() && isActionMove() && mEvent.getButtonState() == 0;
+ }
+
+ @Override
public boolean isOverDragHotspot() {
return isOverItem() ? getDocumentDetails().isInDragHotspot(this) : false;
}
@@ -357,6 +403,7 @@
.append(" isOverItem=").append(isOverItem())
.append(" getItemPosition=").append(getItemPosition())
.append(" getDocumentDetails=").append(getDocumentDetails())
+ .append(" getPointerCount=").append(getPointerCount())
.append("}")
.toString();
}
diff --git a/src/com/android/documentsui/dirlist/UserInputHandler.java b/src/com/android/documentsui/dirlist/UserInputHandler.java
index 8da25cf..e2a5d56 100644
--- a/src/com/android/documentsui/dirlist/UserInputHandler.java
+++ b/src/com/android/documentsui/dirlist/UserInputHandler.java
@@ -305,7 +305,8 @@
// Don't scroll content window in response to mouse drag
boolean onScroll(T event) {
if (VERBOSE) Log.v(MTAG, "Delegated onScroll event.");
- return true;
+ // If it's two-finger trackpad scrolling, we want to scroll
+ return !event.isTouchpadScroll();
}
boolean onSingleTapUp(T event) {
diff --git a/src/com/android/documentsui/selection/BandController.java b/src/com/android/documentsui/selection/BandController.java
index 5415faf..f429a8c 100644
--- a/src/com/android/documentsui/selection/BandController.java
+++ b/src/com/android/documentsui/selection/BandController.java
@@ -82,7 +82,8 @@
this(new RuntimeSelectionEnvironment(view), adapter, selectionManager, lock, gridItemTester);
}
- private BandController(
+ @VisibleForTesting
+ BandController(
SelectionEnvironment env,
DocumentsAdapter adapter,
SelectionManager selectionManager,
@@ -174,7 +175,8 @@
};
}
- private boolean isActive() {
+ @VisibleForTesting
+ boolean isActive() {
return mModel != null;
}
@@ -210,8 +212,8 @@
}
public boolean shouldStart(InputEvent e) {
- // Don't start, or extend bands on right click.
- if (e.isSecondaryButtonPressed()) {
+ // Don't start, or extend bands on non-left clicks.
+ if (!e.isPrimaryButtonPressed()) {
return false;
}
@@ -237,7 +239,7 @@
public boolean shouldStop(InputEvent input) {
return isActive()
&& input.isMouseEvent()
- && (input.isActionUp() || input.isActionCancel());
+ && (input.isActionUp() || input.isMultiPointerActionUp() || input.isActionCancel());
}
/**
diff --git a/tests/common/com/android/documentsui/testing/TestEvent.java b/tests/common/com/android/documentsui/testing/TestEvent.java
index 90261e9..391db15 100644
--- a/tests/common/com/android/documentsui/testing/TestEvent.java
+++ b/tests/common/com/android/documentsui/testing/TestEvent.java
@@ -73,6 +73,7 @@
private @Action int mAction;
private @ToolType int mToolType;
+ private int mPointerCount;
private Set<Integer> mButtons;
private Set<Integer> mKeys;
private Point mLocation;
@@ -87,6 +88,7 @@
mLocation = new Point(0, 0);
mRawLocation = new Point(0, 0);
mDetails = new Details();
+ mPointerCount = 0;
}
private TestEvent(TestEvent source) {
@@ -98,6 +100,7 @@
mLocation = source.mLocation;
mRawLocation = source.mRawLocation;
mDetails = new Details(source.mDetails);
+ mPointerCount = source.mPointerCount;
}
@Override
@@ -126,6 +129,11 @@
}
@Override
+ public int getPointerCount() {
+ return mPointerCount;
+ }
+
+ @Override
public boolean isMouseEvent() {
return mToolType == MotionEvent.TOOL_TYPE_MOUSE;
}
@@ -171,6 +179,16 @@
}
@Override
+ public boolean isMultiPointerActionDown() {
+ return mAction == MotionEvent.ACTION_POINTER_DOWN;
+ }
+
+ @Override
+ public boolean isMultiPointerActionUp() {
+ return mAction == MotionEvent.ACTION_POINTER_UP;
+ }
+
+ @Override
public boolean isActionMove() {
return mAction == MotionEvent.ACTION_MOVE;
}
@@ -187,7 +205,7 @@
@Override
public boolean isOverDragHotspot() {
- return mDetails.isOverInteractiveArea();
+ return isOverItem() && mDetails.isInDragHotspot(this);
}
@Override
@@ -200,6 +218,11 @@
}
@Override
+ public boolean isTouchpadScroll() {
+ return isMouseEvent() && mButtons.isEmpty() && isActionMove();
+ }
+
+ @Override
public int getItemPosition() {
return mDetails.mPosition;
}
@@ -260,10 +283,6 @@
return mPosition != Integer.MIN_VALUE && mPosition != RecyclerView.NO_POSITION;
}
- private boolean isOverInteractiveArea() {
- return mPosition != Integer.MIN_VALUE && mPosition != RecyclerView.NO_POSITION;
- }
-
@Override
public boolean hasModelId() {
return !TextUtils.isEmpty(mModelId);
@@ -352,6 +371,11 @@
return this;
}
+ public Builder pointerCount(int count) {
+ mState.mPointerCount = count;
+ return this;
+ }
+
/**
* Adds one or more button press attributes.
*/
@@ -408,6 +432,11 @@
return this;
}
+ public Builder notInDragHotspot() {
+ mState.mDetails.mInDragHotspot = false;
+ return this;
+ }
+
public Builder touch() {
type(MotionEvent.TOOL_TYPE_FINGER);
return this;
@@ -478,4 +507,4 @@
return new TestEvent(mState);
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/unit/com/android/documentsui/dirlist/DragStartListenerTest.java b/tests/unit/com/android/documentsui/dirlist/DragStartListenerTest.java
index b898653..76cff7c 100644
--- a/tests/unit/com/android/documentsui/dirlist/DragStartListenerTest.java
+++ b/tests/unit/com/android/documentsui/dirlist/DragStartListenerTest.java
@@ -90,6 +90,7 @@
.action(MotionEvent.ACTION_MOVE)
.mouse()
.at(1)
+ .inDragHotspot()
.primary();
}
diff --git a/tests/unit/com/android/documentsui/dirlist/UserInputHandler_MouseTest.java b/tests/unit/com/android/documentsui/dirlist/UserInputHandler_MouseTest.java
index bf27f81..6aff697 100644
--- a/tests/unit/com/android/documentsui/dirlist/UserInputHandler_MouseTest.java
+++ b/tests/unit/com/android/documentsui/dirlist/UserInputHandler_MouseTest.java
@@ -16,6 +16,7 @@
package com.android.documentsui.dirlist;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.support.test.filters.SmallTest;
@@ -105,7 +106,12 @@
@Test
public void testScroll_shouldTrap() {
- assertTrue(mInputHandler.onScroll(mEvent.at(0).build()));
+ assertTrue(mInputHandler.onScroll(mEvent.at(0).action(MotionEvent.ACTION_MOVE).primary().build()));
+ }
+
+ @Test
+ public void testScroll_NoTrapForTwoFinger() {
+ assertFalse(mInputHandler.onScroll(mEvent.at(0).action(MotionEvent.ACTION_MOVE).build()));
}
@Test
diff --git a/tests/unit/com/android/documentsui/selection/BandControllerTest.java b/tests/unit/com/android/documentsui/selection/BandControllerTest.java
new file mode 100644
index 0000000..fea021c
--- /dev/null
+++ b/tests/unit/com/android/documentsui/selection/BandControllerTest.java
@@ -0,0 +1,237 @@
+/*
+ * 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.documentsui.selection;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.support.v7.widget.RecyclerView.OnScrollListener;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.MotionEvent;
+
+import com.android.documentsui.DirectoryReloadLock;
+import com.android.documentsui.dirlist.TestData;
+import com.android.documentsui.dirlist.TestDocumentsAdapter;
+import com.android.documentsui.testing.SelectionManagers;
+import com.android.documentsui.testing.TestEvent.Builder;
+
+import java.util.Collections;
+import java.util.List;
+
+@SmallTest
+public class BandControllerTest extends AndroidTestCase {
+
+ private static final List<String> ITEMS = TestData.create(10);
+ private BandController mBandController;
+ private boolean mIsActive;
+
+ @Override
+ public void setUp() throws Exception {
+ mIsActive = false;
+ mBandController = new BandController(new TestSelectionEnvironment(),
+ new TestDocumentsAdapter(ITEMS), SelectionManagers.createTestInstance(ITEMS),
+ new DirectoryReloadLock(), null) {
+ @Override
+ public boolean isActive() {
+ return mIsActive;
+ }
+ };
+ }
+
+ public void testGoodStart() {
+ assertTrue(mBandController.shouldStart(goodStartEventBuilder().build()));
+ }
+
+ public void testBadStart_NoButtons() {
+ assertFalse(mBandController.shouldStart(
+ goodStartEventBuilder().releaseButton(MotionEvent.BUTTON_PRIMARY).build()));
+ }
+
+ public void testBadStart_SecondaryButton() {
+ assertFalse(
+ mBandController.shouldStart(goodStartEventBuilder().secondary().build()));
+ }
+
+ public void testBadStart_TertiaryButton() {
+ assertFalse(
+ mBandController.shouldStart(goodStartEventBuilder().tertiary().build()));
+ }
+
+ public void testBadStart_Touch() {
+ assertFalse(mBandController.shouldStart(
+ goodStartEventBuilder().touch().releaseButton(MotionEvent.BUTTON_PRIMARY).build()));
+ }
+
+ public void testBadStart_inDragSpot() {
+ assertFalse(
+ mBandController.shouldStart(goodStartEventBuilder().at(1).inDragHotspot().build()));
+ }
+
+ public void testBadStart_ActionDown() {
+ assertFalse(mBandController
+ .shouldStart(goodStartEventBuilder().action(MotionEvent.ACTION_DOWN).build()));
+ }
+
+ public void testBadStart_ActionUp() {
+ assertFalse(mBandController
+ .shouldStart(goodStartEventBuilder().action(MotionEvent.ACTION_UP).build()));
+ }
+
+ public void testBadStart_ActionPointerDown() {
+ assertFalse(mBandController.shouldStart(
+ goodStartEventBuilder().action(MotionEvent.ACTION_POINTER_DOWN).build()));
+ }
+
+ public void testBadStart_ActionPointerUp() {
+ assertFalse(mBandController.shouldStart(
+ goodStartEventBuilder().action(MotionEvent.ACTION_POINTER_UP).build()));
+ }
+
+ public void testBadStart_NoItems() {
+ mBandController = new BandController(new TestSelectionEnvironment(),
+ new TestDocumentsAdapter(Collections.EMPTY_LIST),
+ SelectionManagers.createTestInstance(ITEMS),
+ new DirectoryReloadLock(), null);
+ assertFalse(mBandController.shouldStart(goodStartEventBuilder().build()));
+ }
+
+ public void testBadStart_alreadyActive() {
+ mIsActive = true;
+ assertFalse(mBandController.shouldStart(goodStartEventBuilder().build()));
+ }
+
+ public void testGoodStop() {
+ mIsActive = true;
+ assertTrue(mBandController.shouldStop(goodStopEventBuilder().build()));
+ }
+
+ public void testGoodStop_PointerUp() {
+ mIsActive = true;
+ assertTrue(mBandController
+ .shouldStop(goodStopEventBuilder().action(MotionEvent.ACTION_POINTER_UP).build()));
+ }
+
+ public void testGoodStop_Cancel() {
+ mIsActive = true;
+ assertTrue(mBandController
+ .shouldStop(goodStopEventBuilder().action(MotionEvent.ACTION_CANCEL).build()));
+ }
+
+ public void testBadStop_NotActive() {
+ assertFalse(mBandController.shouldStop(goodStopEventBuilder().build()));
+ }
+
+ public void testBadStop_NonMouse() {
+ mIsActive = true;
+ assertFalse(mBandController.shouldStop(goodStopEventBuilder().touch().build()));
+ }
+
+ public void testBadStop_Move() {
+ mIsActive = true;
+ assertFalse(mBandController.shouldStop(
+ goodStopEventBuilder().action(MotionEvent.ACTION_MOVE).touch().build()));
+ }
+
+ public void testBadStop_Down() {
+ mIsActive = true;
+ assertFalse(mBandController.shouldStop(
+ goodStopEventBuilder().action(MotionEvent.ACTION_DOWN).touch().build()));
+ }
+
+
+ private Builder goodStartEventBuilder() {
+ return new Builder().mouse().primary().action(MotionEvent.ACTION_MOVE).notInDragHotspot();
+ }
+
+ private Builder goodStopEventBuilder() {
+ return new Builder().mouse().action(MotionEvent.ACTION_UP).notInDragHotspot();
+ }
+
+ private final class TestSelectionEnvironment implements BandController.SelectionEnvironment {
+ @Override
+ public void scrollBy(int dy) {
+ }
+
+ @Override
+ public void runAtNextFrame(Runnable r) {
+ }
+
+ @Override
+ public void removeCallback(Runnable r) {
+ }
+
+ @Override
+ public void showBand(Rect rect) {
+ }
+
+ @Override
+ public void hideBand() {
+ }
+
+ @Override
+ public void addOnScrollListener(OnScrollListener listener) {
+ }
+
+ @Override
+ public void removeOnScrollListener(OnScrollListener listener) {
+ }
+
+ @Override
+ public int getHeight() {
+ return 0;
+ }
+
+ @Override
+ public void invalidateView() {
+ }
+
+ @Override
+ public Point createAbsolutePoint(Point relativePoint) {
+ return null;
+ }
+
+ @Override
+ public Rect getAbsoluteRectForChildViewAt(int index) {
+ return null;
+ }
+
+ @Override
+ public int getAdapterPositionAt(int index) {
+ return 0;
+ }
+
+ @Override
+ public int getColumnCount() {
+ return 0;
+ }
+
+ @Override
+ public int getChildCount() {
+ return 0;
+ }
+
+ @Override
+ public int getVisibleChildCount() {
+ return 0;
+ }
+
+ @Override
+ public boolean hasView(int adapterPosition) {
+ return false;
+ }
+ }
+}