summaryrefslogtreecommitdiff
path: root/packages/DocumentsUI
diff options
context:
space:
mode:
authorSteve McKay <smckay@google.com>2016-07-13 13:03:16 -0700
committerSteve McKay <smckay@google.com>2016-07-15 01:28:04 +0000
commit07886170d5f1f0f0a688e31209d90bb6b28a20c1 (patch)
tree5a4c9e55156f429eafd6d07be141bac49a1b5621 /packages/DocumentsUI
parent6a8581339ba4ac35f55906b9d55bf21a1e7c23ce (diff)
Addition UserInputHandler test coverage.
Pull event handling logic out of MultiSelectManager into UserInputHandler. Pull out a couple common test support classes. Change-Id: I8958fec9cda05f52192a07a682c318e049871a8d
Diffstat (limited to 'packages/DocumentsUI')
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java4
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java80
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/dirlist/UserInputHandler.java332
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java266
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SingleSelectTest.java66
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestFocusHandler.java44
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/UserInputHandler_MouseTest.java104
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/UserInputHandler_RangeTest.java147
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/UserInputHandler_TouchTest.java118
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/testing/TestEvent.java138
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/testing/dirlist/SelectionProbe.java89
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/testing/dirlist/TestSelectionListener.java56
12 files changed, 948 insertions, 496 deletions
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 9c57e175ccd2..85c988e6fdaa 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -142,7 +142,7 @@ public class DirectoryFragment extends Fragment
private Model mModel;
private MultiSelectManager mSelectionMgr;
private Model.UpdateListener mModelUpdateListener = new ModelUpdateListener();
- private UserInputHandler mInputHandler;
+ private UserInputHandler<InputEvent> mInputHandler;
private SelectionModeListener mSelectionModeListener;
private FocusManager mFocusManager;
@@ -280,7 +280,7 @@ public class DirectoryFragment extends Fragment
// Make sure this is done after the RecyclerView is set up.
mFocusManager = new FocusManager(context, mRecView, mModel);
- mInputHandler = new UserInputHandler(
+ mInputHandler = new UserInputHandler<>(
mSelectionMgr,
mFocusManager,
new Function<MotionEvent, InputEvent>() {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
index e58971a4f443..9c0b967b7ce4 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
@@ -21,13 +21,10 @@ import static com.android.documentsui.Shared.DEBUG;
import android.annotation.IntDef;
import android.os.Parcel;
import android.os.Parcelable;
-import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
-import com.android.documentsui.Events.InputEvent;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -39,6 +36,8 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
+import javax.annotation.Nullable;
+
/**
* MultiSelectManager provides support traditional multi-item selection support to RecyclerView.
* Additionally it can be configured to restrict selection to a single element, @see
@@ -62,7 +61,7 @@ public final class MultiSelectManager {
private final DocumentsAdapter mAdapter;
private final List<MultiSelectManager.Callback> mCallbacks = new ArrayList<>(1);
- private Range mRanger;
+ private @Nullable Range mRanger;
private boolean mSingleSelect;
public MultiSelectManager(DocumentsAdapter adapter, @SelectionMode int mode) {
@@ -223,70 +222,13 @@ public final class MultiSelectManager {
}
}
- @VisibleForTesting
- void onLongPress(InputEvent input) {
- if (DEBUG) Log.d(TAG, "Handling long press event.");
+ void snapSelection(int position) {
+ mRanger.snapSelection(position);
- if (!input.isOverItem()) {
- if (DEBUG) Log.i(TAG, "Cannot handle tap. No adapter position available.");
- }
-
- handleAdapterEvent(input);
- }
-
- boolean onSingleTapUp(InputEvent input) {
- if (DEBUG) Log.d(TAG, "Processing tap event.");
- if (!hasSelection()) {
- // No selection active - do nothing.
- return false;
- }
-
- if (!input.isOverItem()) {
- if (DEBUG) Log.d(TAG, "Activity has no position. Canceling selection.");
- clearSelection();
- return false;
- }
-
- handleAdapterEvent(input);
- return true;
- }
-
- /**
- * Handles a change caused by a click on the item with the given position. If the Shift key is
- * held down, this performs a range select; otherwise, it simply toggles the item's selection
- * state.
- */
- private void handleAdapterEvent(InputEvent input) {
- if (mRanger != null && input.isShiftKeyDown()) {
- mRanger.snapSelection(input.getItemPosition());
-
- // We're being lazy here notifying even when something might not have changed.
- // To make this more correct, we'd need to update the Ranger class to return
- // information about what has changed.
- notifySelectionChanged();
- } else {
- int position = input.getItemPosition();
- toggleSelection(position);
- setSelectionRangeBegin(position);
- }
- }
-
- /**
- * A convenience method for toggling selection by adapter position.
- *
- * @param position Adapter position to toggle.
- */
- private void toggleSelection(int position) {
- // Position may be special "no position" during certain
- // transitional phases. If so, skip handling of the event.
- if (position == RecyclerView.NO_POSITION) {
- if (DEBUG) Log.d(TAG, "Ignoring toggle for element with no position.");
- return;
- }
- String id = mAdapter.getModelId(position);
- if (id != null) {
- toggleSelection(id);
- }
+ // We're being lazy here notifying even when something might not have changed.
+ // To make this more correct, we'd need to update the Ranger class to return
+ // information about what has changed.
+ notifySelectionChanged();
}
/**
@@ -329,7 +271,9 @@ public final class MultiSelectManager {
* @param pos The new end position for the selection range.
*/
void snapRangeSelection(int pos) {
- assert(mRanger != null);
+ if (!isRangeSelectionActive()) {
+ throw new IllegalStateException("Range start point not set.");
+ }
mRanger.snapSelection(pos);
notifySelectionChanged();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/UserInputHandler.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/UserInputHandler.java
index 943815c8dc41..07b0cd8f7248 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/UserInputHandler.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/UserInputHandler.java
@@ -16,6 +16,10 @@
package com.android.documentsui.dirlist;
+import static com.android.documentsui.Shared.DEBUG;
+
+import android.support.annotation.VisibleForTesting;
+import android.util.Log;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -30,25 +34,29 @@ import java.util.function.Predicate;
/**
* Grand unified-ish gesture/event listener for items in the directory list.
*/
-final class UserInputHandler extends GestureDetector.SimpleOnGestureListener
+public final class UserInputHandler<T extends InputEvent>
+ extends GestureDetector.SimpleOnGestureListener
implements KeyboardEventListener {
+ private static final String TAG = "UserInputHandler";
+
private final MultiSelectManager mSelectionMgr;
private final FocusHandler mFocusHandler;
- private final Function<MotionEvent, InputEvent> mEventConverter;
- private final Function<InputEvent, DocumentDetails> mDocFinder;
+ private final Function<MotionEvent, T> mEventConverter;
+ private final Function<T, DocumentDetails> mDocFinder;
private final Predicate<DocumentDetails> mSelectable;
private final EventHandler mRightClickHandler;
private final DocumentHandler mActivateHandler;
private final DocumentHandler mDeleteHandler;
private final TouchInputDelegate mTouchDelegate;
private final MouseInputDelegate mMouseDelegate;
+ private final KeyInputHandler mKeyListener;
public UserInputHandler(
MultiSelectManager selectionMgr,
FocusHandler focusHandler,
- Function<MotionEvent, InputEvent> eventConverter,
- Function<InputEvent, DocumentDetails> docFinder,
+ Function<MotionEvent, T> eventConverter,
+ Function<T, DocumentDetails> docFinder,
Predicate<DocumentDetails> selectable,
EventHandler rightClickHandler,
DocumentHandler activateHandler,
@@ -65,55 +73,116 @@ final class UserInputHandler extends GestureDetector.SimpleOnGestureListener
mTouchDelegate = new TouchInputDelegate();
mMouseDelegate = new MouseInputDelegate();
+ mKeyListener = new KeyInputHandler();
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
- try (InputEvent event = mEventConverter.apply(e)) {
- return event.isMouseEvent()
- ? mMouseDelegate.onSingleTapUp(event)
- : mTouchDelegate.onSingleTapUp(event);
+ try (T event = mEventConverter.apply(e)) {
+ return onSingleTapUp(event);
}
}
+ @VisibleForTesting
+ boolean onSingleTapUp(T event) {
+ return event.isMouseEvent()
+ ? mMouseDelegate.onSingleTapUp(event)
+ : mTouchDelegate.onSingleTapUp(event);
+ }
+
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
- try (InputEvent event = mEventConverter.apply(e)) {
- return event.isMouseEvent()
- ? mMouseDelegate.onSingleTapConfirmed(event)
- : mTouchDelegate.onSingleTapConfirmed(event);
+ try (T event = mEventConverter.apply(e)) {
+ return onSingleTapConfirmed(event);
}
}
+ @VisibleForTesting
+ boolean onSingleTapConfirmed(T event) {
+ return event.isMouseEvent()
+ ? mMouseDelegate.onSingleTapConfirmed(event)
+ : mTouchDelegate.onSingleTapConfirmed(event);
+ }
+
@Override
public boolean onDoubleTap(MotionEvent e) {
- try (InputEvent event = mEventConverter.apply(e)) {
- return event.isMouseEvent()
- ? mMouseDelegate.onDoubleTap(event)
- : mTouchDelegate.onDoubleTap(event);
+ try (T event = mEventConverter.apply(e)) {
+ return onDoubleTap(event);
}
}
+ @VisibleForTesting
+ boolean onDoubleTap(T event) {
+ return event.isMouseEvent()
+ ? mMouseDelegate.onDoubleTap(event)
+ : mTouchDelegate.onDoubleTap(event);
+ }
+
@Override
public void onLongPress(MotionEvent e) {
- try (InputEvent event = mEventConverter.apply(e)) {
- if (event.isMouseEvent()) {
- mMouseDelegate.onLongPress(event);
- }
- mTouchDelegate.onLongPress(event);
+ try (T event = mEventConverter.apply(e)) {
+ onLongPress(event);
+ }
+ }
+
+ @VisibleForTesting
+ void onLongPress(T event) {
+ if (event.isMouseEvent()) {
+ mMouseDelegate.onLongPress(event);
}
+ mTouchDelegate.onLongPress(event);
}
- private boolean onSelect(DocumentDetails doc) {
+ public boolean onSingleRightClickUp(MotionEvent e) {
+ try (T event = mEventConverter.apply(e)) {
+ return mMouseDelegate.onSingleRightClickUp(event);
+ }
+ }
+
+ @Override
+ public boolean onKey(DocumentHolder doc, int keyCode, KeyEvent event) {
+ return mKeyListener.onKey(doc, keyCode, event);
+ }
+
+ // TODO: Isolate this hack...see if we can't get this solved at the platform level.
+ public void setLastButtonState(int state) {
+ mMouseDelegate.setLastButtonState(state);
+ }
+
+ private boolean activateDocument(DocumentDetails doc) {
+ return mActivateHandler.accept(doc);
+ }
+
+ private boolean selectDocument(DocumentDetails doc) {
+ assert(doc != null);
mSelectionMgr.toggleSelection(doc.getModelId());
mSelectionMgr.setSelectionRangeBegin(doc.getAdapterPosition());
return true;
}
+ boolean isRangeExtension(T event) {
+ return event.isShiftKeyDown() && mSelectionMgr.isRangeSelectionActive();
+ }
+
+ private void extendSelectionRange(T event) {
+ mSelectionMgr.snapSelection(event.getItemPosition());
+ }
+
private final class TouchInputDelegate {
- public boolean onSingleTapUp(InputEvent event) {
- if (mSelectionMgr.onSingleTapUp(event)) {
+ boolean onSingleTapUp(T event) {
+ if (!event.isOverItem()) {
+ if (DEBUG) Log.d(TAG, "Tap on non-item. Clearing selection.");
+ mSelectionMgr.clearSelection();
+ return false;
+ }
+
+ if (mSelectionMgr.hasSelection()) {
+ if (isRangeExtension(event)) {
+ mSelectionMgr.snapSelection(event.getItemPosition());
+ } else {
+ selectDocument(mDocFinder.apply(event));
+ }
return true;
}
@@ -123,23 +192,31 @@ final class UserInputHandler extends GestureDetector.SimpleOnGestureListener
// Touch events select if they occur in the selection hotspot,
// otherwise they activate.
return doc.isInSelectionHotspot(event)
- ? onSelect(doc)
- : mActivateHandler.accept(doc);
+ ? selectDocument(doc)
+ : activateDocument(doc);
}
return false;
}
- public boolean onSingleTapConfirmed(InputEvent event) {
+ boolean onSingleTapConfirmed(T event) {
return false;
}
- public boolean onDoubleTap(InputEvent event) {
+ boolean onDoubleTap(T event) {
return false;
}
- public void onLongPress(InputEvent event) {
- mSelectionMgr.onLongPress(event);
+ final void onLongPress(T event) {
+ if (!event.isOverItem()) {
+ return;
+ }
+
+ if (isRangeExtension(event)) {
+ extendSelectionRange(event);
+ } else {
+ selectDocument(mDocFinder.apply(event));
+ }
}
}
@@ -155,18 +232,28 @@ final class UserInputHandler extends GestureDetector.SimpleOnGestureListener
private int mLastButtonState = -1;
// true when the previous event has consumed a right click motion event
- private boolean ateRightClick;
+ private boolean mAteRightClick;
// The event has been handled in onSingleTapUp
- private boolean handledTapUp;
+ private boolean mHandledTapUp;
- public boolean onSingleTapUp(InputEvent event) {
+ boolean onSingleTapUp(T event) {
if (eatRightClick()) {
return onSingleRightClickUp(event);
}
- if (mSelectionMgr.onSingleTapUp(event)) {
- handledTapUp = true;
+ if (!event.isOverItem()) {
+ mSelectionMgr.clearSelection();
+ return false;
+ }
+
+ if (mSelectionMgr.hasSelection()) {
+ if (isRangeExtension(event)) {
+ extendSelectionRange(event);
+ } else {
+ selectDocument(mDocFinder.apply(event));
+ }
+ mHandledTapUp = true;
return true;
}
@@ -181,17 +268,17 @@ final class UserInputHandler extends GestureDetector.SimpleOnGestureListener
return false;
}
- handledTapUp = true;
- return onSelect(doc);
+ mHandledTapUp = true;
+ return selectDocument(doc);
}
- public boolean onSingleTapConfirmed(InputEvent event) {
- if (ateRightClick) {
- ateRightClick = false;
+ boolean onSingleTapConfirmed(T event) {
+ if (mAteRightClick) {
+ mAteRightClick = false;
return false;
}
- if (handledTapUp) {
- handledTapUp = false;
+ if (mHandledTapUp) {
+ mHandledTapUp = false;
return false;
}
@@ -204,25 +291,33 @@ final class UserInputHandler extends GestureDetector.SimpleOnGestureListener
return false;
}
- return onSelect(doc);
+ return selectDocument(doc);
}
- public boolean onDoubleTap(InputEvent event) {
- handledTapUp = false;
+ boolean onDoubleTap(T event) {
+ mHandledTapUp = false;
DocumentDetails doc = mDocFinder.apply(event);
if (doc != null) {
return mSelectionMgr.hasSelection()
- ? onSelect(doc)
- : mActivateHandler.accept(doc);
+ ? selectDocument(doc)
+ : activateDocument(doc);
}
return false;
}
- public void onLongPress(InputEvent event) {
- mSelectionMgr.onLongPress(event);
+ final void onLongPress(T event) {
+ if (!event.isOverItem()) {
+ return;
+ }
+
+ if (isRangeExtension(event)) {
+ extendSelectionRange(event);
+ } else {
+ selectDocument(mDocFinder.apply(event));
+ }
}
- private boolean onSingleRightClickUp(InputEvent event) {
+ private boolean onSingleRightClickUp(T event) {
return mRightClickHandler.apply(event);
}
@@ -234,96 +329,77 @@ final class UserInputHandler extends GestureDetector.SimpleOnGestureListener
private boolean eatRightClick() {
if (mLastButtonState == MotionEvent.BUTTON_SECONDARY) {
mLastButtonState = -1;
- ateRightClick = true;
+ mAteRightClick = true;
return true;
}
return false;
}
}
- public boolean onSingleRightClickUp(MotionEvent e) {
- try (InputEvent event = mEventConverter.apply(e)) {
- return mMouseDelegate.onSingleRightClickUp(event);
- }
- }
-
- // TODO: Isolate this hack...see if we can't get this solved at the platform level.
- public void setLastButtonState(int state) {
- mMouseDelegate.setLastButtonState(state);
- }
-
- // TODO: Refactor FocusManager to depend only on DocumentDetails so we can eliminate
- // difficult to test dependency on DocumentHolder.
- @Override
- public boolean onKey(DocumentHolder doc, int keyCode, KeyEvent event) {
- // Only handle key-down events. This is simpler, consistent with most other UIs, and
- // enables the handling of repeated key events from holding down a key.
- if (event.getAction() != KeyEvent.ACTION_DOWN) {
- return false;
- }
+ private final class KeyInputHandler {
+ // TODO: Refactor FocusManager to depend only on DocumentDetails so we can eliminate
+ // difficult to test dependency on DocumentHolder.
- // Ignore tab key events. Those should be handled by the top-level key handler.
- if (keyCode == KeyEvent.KEYCODE_TAB) {
- return false;
- }
+ boolean onKey(DocumentHolder doc, int keyCode, KeyEvent event) {
+ // Only handle key-down events. This is simpler, consistent with most other UIs, and
+ // enables the handling of repeated key events from holding down a key.
+ if (event.getAction() != KeyEvent.ACTION_DOWN) {
+ return false;
+ }
- if (mFocusHandler.handleKey(doc, keyCode, event)) {
- // Handle range selection adjustments. Extending the selection will adjust the
- // bounds of the in-progress range selection. Each time an unshifted navigation
- // event is received, the range selection is restarted.
- if (shouldExtendSelection(doc, event)) {
- if (!mSelectionMgr.isRangeSelectionActive()) {
- // Start a range selection if one isn't active
- mSelectionMgr.startRangeSelection(doc.getAdapterPosition());
- }
- mSelectionMgr.snapRangeSelection(mFocusHandler.getFocusPosition());
- } else {
- mSelectionMgr.endRangeSelection();
+ // Ignore tab key events. Those should be handled by the top-level key handler.
+ if (keyCode == KeyEvent.KEYCODE_TAB) {
+ return false;
}
- return true;
- }
- // Handle enter key events
- switch (keyCode) {
- case KeyEvent.KEYCODE_ENTER:
- if (event.isShiftPressed()) {
- onSelect(doc);
- }
- // For non-shifted enter keypresses, fall through.
- case KeyEvent.KEYCODE_DPAD_CENTER:
- case KeyEvent.KEYCODE_BUTTON_A:
- return mActivateHandler.accept(doc);
- case KeyEvent.KEYCODE_FORWARD_DEL:
- // This has to be handled here instead of in a keyboard shortcut, because
- // keyboard shortcuts all have to be modified with the 'Ctrl' key.
- if (mSelectionMgr.hasSelection()) {
- mDeleteHandler.accept(doc);
+ if (mFocusHandler.handleKey(doc, keyCode, event)) {
+ // Handle range selection adjustments. Extending the selection will adjust the
+ // bounds of the in-progress range selection. Each time an unshifted navigation
+ // event is received, the range selection is restarted.
+ if (shouldExtendSelection(doc, event)) {
+ if (!mSelectionMgr.isRangeSelectionActive()) {
+ // Start a range selection if one isn't active
+ mSelectionMgr.startRangeSelection(doc.getAdapterPosition());
+ }
+ mSelectionMgr.snapRangeSelection(mFocusHandler.getFocusPosition());
+ } else {
+ mSelectionMgr.endRangeSelection();
}
- // Always handle the key, even if there was nothing to delete. This is a
- // precaution to prevent other handlers from potentially picking up the event
- // and triggering extra behaviors.
return true;
- }
+ }
- return false;
- }
+ // Handle enter key events
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_ENTER:
+ if (event.isShiftPressed()) {
+ selectDocument(doc);
+ }
+ // For non-shifted enter keypresses, fall through.
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ case KeyEvent.KEYCODE_BUTTON_A:
+ return activateDocument(doc);
+ case KeyEvent.KEYCODE_FORWARD_DEL:
+ // This has to be handled here instead of in a keyboard shortcut, because
+ // keyboard shortcuts all have to be modified with the 'Ctrl' key.
+ if (mSelectionMgr.hasSelection()) {
+ mDeleteHandler.accept(doc);
+ }
+ // Always handle the key, even if there was nothing to delete. This is a
+ // precaution to prevent other handlers from potentially picking up the event
+ // and triggering extra behaviors.
+ return true;
+ }
- private boolean shouldExtendSelection(DocumentDetails doc, KeyEvent event) {
- if (!Events.isNavigationKeyCode(event.getKeyCode()) || !event.isShiftPressed()) {
return false;
}
- return mSelectable.test(doc);
- }
-
- @FunctionalInterface
- interface EventHandler {
- boolean apply(InputEvent input);
- }
+ private boolean shouldExtendSelection(DocumentDetails doc, KeyEvent event) {
+ if (!Events.isNavigationKeyCode(event.getKeyCode()) || !event.isShiftPressed()) {
+ return false;
+ }
- @FunctionalInterface
- interface DocumentHandler {
- boolean accept(DocumentDetails doc);
+ return mSelectable.test(doc);
+ }
}
/**
@@ -334,4 +410,14 @@ final class UserInputHandler extends GestureDetector.SimpleOnGestureListener
int getAdapterPosition();
boolean isInSelectionHotspot(InputEvent event);
}
+
+ @FunctionalInterface
+ interface EventHandler {
+ boolean apply(InputEvent event);
+ }
+
+ @FunctionalInterface
+ interface DocumentHandler {
+ boolean accept(DocumentDetails doc);
+ }
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
index 7eb3c2ebf248..57b9f926af47 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
@@ -16,15 +16,13 @@
package com.android.documentsui.dirlist;
-import android.support.v7.widget.RecyclerView;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.SparseBooleanArray;
-import com.android.documentsui.TestInputEvent;
import com.android.documentsui.dirlist.MultiSelectManager.Selection;
-
-import com.google.common.collect.Lists;
+import com.android.documentsui.testing.dirlist.SelectionProbe;
+import com.android.documentsui.testing.dirlist.TestSelectionListener;
import java.util.HashSet;
import java.util.List;
@@ -33,180 +31,66 @@ import java.util.Set;
@SmallTest
public class MultiSelectManagerTest extends AndroidTestCase {
- private static final List<String> items = TestData.create(100);
+ private static final List<String> ITEMS = TestData.create(100);
private MultiSelectManager mManager;
- private TestCallback mCallback;
+ private TestSelectionListener mCallback;
private TestDocumentsAdapter mAdapter;
+ private SelectionProbe mSelection;
@Override
public void setUp() throws Exception {
- mCallback = new TestCallback();
- mAdapter = new TestDocumentsAdapter(items);
+ mCallback = new TestSelectionListener();
+ mAdapter = new TestDocumentsAdapter(ITEMS);
mManager = new MultiSelectManager(mAdapter, MultiSelectManager.MODE_MULTIPLE);
mManager.addCallback(mCallback);
+
+ mSelection = new SelectionProbe(mManager);
}
public void testSelection() {
// Check selection.
- mManager.toggleSelection(items.get(7));
- assertSelection(items.get(7));
+ mManager.toggleSelection(ITEMS.get(7));
+ mSelection.assertSelection(7);
// Check deselection.
- mManager.toggleSelection(items.get(7));
- assertSelectionSize(0);
+ mManager.toggleSelection(ITEMS.get(7));
+ mSelection.assertNoSelection();
}
public void testSelection_NotifiesSelectionChanged() {
// Selection should notify.
- mManager.toggleSelection(items.get(7));
+ mManager.toggleSelection(ITEMS.get(7));
mCallback.assertSelectionChanged();
// Deselection should notify.
- mManager.toggleSelection(items.get(7));
+ mManager.toggleSelection(ITEMS.get(7));
mCallback.assertSelectionChanged();
}
- public void testMouseClick_ShiftClickExtendsSelection() {
- longPress(7);
- shiftClick(11);
- assertRangeSelection(7, 11);
- }
-
- public void testMouseClick_NoPosition_ClearsSelection() {
- longPress(7);
- click(11);
- click(RecyclerView.NO_POSITION);
- assertSelection();
- }
-
- public void testSetSelectionFocusBegin() {
- mManager.setItemsSelected(Lists.newArrayList(items.get(7)), true);
- mManager.setSelectionRangeBegin(7);
- shiftClick(11);
- assertRangeSelection(7, 11);
- }
-
- public void testLongPress_StartsSelectionMode() {
- longPress(7);
- assertSelection(items.get(7));
- }
-
- public void testLongPress_SecondPressExtendsSelection() {
- longPress(7);
- longPress(99);
- assertSelection(items.get(7), items.get(99));
- }
-
- public void testSingleTapUp_UnselectsSelectedItem() {
- longPress(7);
- tap(7);
- assertSelection();
- }
-
- public void testSingleTapUp_NoPosition_ClearsSelection() {
- longPress(7);
- tap(11);
- tap(RecyclerView.NO_POSITION);
- assertSelection();
- }
-
- public void testSingleTapUp_ExtendsSelection() {
- longPress(99);
- tap(7);
- tap(13);
- assertSelection(items.get(7), items.get(99), items.get(13));
- }
-
- public void testSingleTapUp_ShiftCreatesRangeSelection() {
- longPress(7);
- shiftTap(17);
- assertRangeSelection(7, 17);
- }
-
- public void testSingleTapUp_ShiftCreatesRangeSeletion_Backwards() {
- longPress(17);
- shiftTap(7);
- assertRangeSelection(7, 17);
- }
-
- public void testSingleTapUp_SecondShiftClickExtendsSelection() {
- longPress(7);
- shiftTap(11);
- shiftTap(17);
- assertRangeSelection(7, 17);
- }
-
- public void testSingleTapUp_MultipleContiguousRangesSelected() {
- longPress(7);
- shiftTap(11);
- tap(20);
- shiftTap(25);
- assertRangeSelected(7, 11);
- assertRangeSelected(20, 25);
- assertSelectionSize(11);
- }
-
- public void testSingleTapUp_ShiftReducesSelectionRange_FromPreviousShiftClick() {
- longPress(7);
- shiftTap(17);
- shiftTap(10);
- assertRangeSelection(7, 10);
- }
-
- public void testSingleTapUp_ShiftReducesSelectionRange_FromPreviousShiftClick_Backwards() {
- mManager.onLongPress(TestInputEvent.tap(17));
- shiftTap(7);
- shiftTap(14);
- assertRangeSelection(14, 17);
- }
-
- public void testSingleTapUp_ShiftReversesSelectionDirection() {
- longPress(7);
- shiftTap(17);
- shiftTap(0);
- assertRangeSelection(0, 7);
- }
-
- public void testSingleSelectMode() {
- mManager = new MultiSelectManager(mAdapter, MultiSelectManager.MODE_SINGLE);
- mManager.addCallback(mCallback);
- longPress(20);
- tap(13);
- assertSelection(items.get(13));
- }
-
- public void testSingleSelectMode_ShiftTap() {
- mManager = new MultiSelectManager(mAdapter, MultiSelectManager.MODE_SINGLE);
- mManager.addCallback(mCallback);
- longPress(13);
- shiftTap(20);
- assertSelection(items.get(20));
- }
-
public void testRangeSelection() {
mManager.startRangeSelection(15);
mManager.snapRangeSelection(19);
- assertRangeSelection(15, 19);
+ mSelection.assertRangeSelection(15, 19);
}
public void testRangeSelection_snapExpand() {
mManager.startRangeSelection(15);
mManager.snapRangeSelection(19);
mManager.snapRangeSelection(27);
- assertRangeSelection(15, 27);
+ mSelection.assertRangeSelection(15, 27);
}
public void testRangeSelection_snapContract() {
mManager.startRangeSelection(15);
mManager.snapRangeSelection(27);
mManager.snapRangeSelection(19);
- assertRangeSelection(15, 19);
+ mSelection.assertRangeSelection(15, 19);
}
public void testRangeSelection_snapInvert() {
mManager.startRangeSelection(15);
mManager.snapRangeSelection(27);
mManager.snapRangeSelection(3);
- assertRangeSelection(3, 15);
+ mSelection.assertRangeSelection(3, 15);
}
public void testRangeSelection_multiple() {
@@ -215,30 +99,21 @@ public class MultiSelectManagerTest extends AndroidTestCase {
mManager.endRangeSelection();
mManager.startRangeSelection(42);
mManager.snapRangeSelection(57);
- assertSelectionSize(29);
- assertRangeSelected(15, 27);
- assertRangeSelected(42, 57);
-
- }
+ mSelection.assertSelectionSize(29);
+ mSelection.assertRangeSelected(15, 27);
+ mSelection.assertRangeSelected(42, 57);
- public void testRangeSelection_singleSelect() {
- mManager = new MultiSelectManager(mAdapter, MultiSelectManager.MODE_SINGLE);
- mManager.addCallback(mCallback);
- mManager.startRangeSelection(11);
- mManager.snapRangeSelection(19);
- assertSelectionSize(1);
- assertSelection(items.get(19));
}
public void testProvisionalSelection() {
Selection s = mManager.getSelection();
- assertSelection();
+ mSelection.assertNoSelection();
SparseBooleanArray provisional = new SparseBooleanArray();
provisional.append(1, true);
provisional.append(2, true);
s.setProvisionalSelection(getItemIds(provisional));
- assertSelection(items.get(1), items.get(2));
+ mSelection.assertSelection(1, 2);
}
public void testProvisionalSelection_Replace() {
@@ -253,7 +128,7 @@ public class MultiSelectManagerTest extends AndroidTestCase {
provisional.append(3, true);
provisional.append(4, true);
s.setProvisionalSelection(getItemIds(provisional));
- assertSelection(items.get(3), items.get(4));
+ mSelection.assertSelection(3, 4);
}
public void testProvisionalSelection_IntersectsExistingProvisionalSelection() {
@@ -267,7 +142,7 @@ public class MultiSelectManagerTest extends AndroidTestCase {
provisional.clear();
provisional.append(1, true);
s.setProvisionalSelection(getItemIds(provisional));
- assertSelection(items.get(1));
+ mSelection.assertSelection(1);
}
public void testProvisionalSelection_Apply() {
@@ -278,12 +153,12 @@ public class MultiSelectManagerTest extends AndroidTestCase {
provisional.append(2, true);
s.setProvisionalSelection(getItemIds(provisional));
s.applyProvisionalSelection();
- assertSelection(items.get(1), items.get(2));
+ mSelection.assertSelection(1, 2);
}
public void testProvisionalSelection_Cancel() {
- mManager.toggleSelection(items.get(1));
- mManager.toggleSelection(items.get(2));
+ mManager.toggleSelection(ITEMS.get(1));
+ mManager.toggleSelection(ITEMS.get(2));
Selection s = mManager.getSelection();
SparseBooleanArray provisional = new SparseBooleanArray();
@@ -293,19 +168,19 @@ public class MultiSelectManagerTest extends AndroidTestCase {
s.cancelProvisionalSelection();
// Original selection should remain.
- assertSelection(items.get(1), items.get(2));
+ mSelection.assertSelection(1, 2);
}
public void testProvisionalSelection_IntersectsAppliedSelection() {
- mManager.toggleSelection(items.get(1));
- mManager.toggleSelection(items.get(2));
+ mManager.toggleSelection(ITEMS.get(1));
+ mManager.toggleSelection(ITEMS.get(2));
Selection s = mManager.getSelection();
SparseBooleanArray provisional = new SparseBooleanArray();
provisional.append(2, true);
provisional.append(3, true);
s.setProvisionalSelection(getItemIds(provisional));
- assertSelection(items.get(1), items.get(2), items.get(3));
+ mSelection.assertSelection(1, 2, 3);
}
private static Set<String> getItemIds(SparseBooleanArray selection) {
@@ -313,82 +188,9 @@ public class MultiSelectManagerTest extends AndroidTestCase {
int count = selection.size();
for (int i = 0; i < count; ++i) {
- ids.add(items.get(selection.keyAt(i)));
+ ids.add(ITEMS.get(selection.keyAt(i)));
}
return ids;
}
-
- private void longPress(int position) {
- mManager.onLongPress(TestInputEvent.tap(position));
- }
-
- private void tap(int position) {
- mManager.onSingleTapUp(TestInputEvent.tap(position));
- }
-
- private void shiftTap(int position) {
- mManager.onSingleTapUp(TestInputEvent.shiftTap(position));
- }
-
- private void click(int position) {
- mManager.onSingleTapUp(TestInputEvent.click(position));
- }
-
- private void shiftClick(int position) {
- mManager.onSingleTapUp(TestInputEvent.shiftClick(position));
- }
-
- private void assertSelected(String... expected) {
- for (int i = 0; i < expected.length; i++) {
- Selection selection = mManager.getSelection();
- String err = String.format(
- "Selection %s does not contain %s", selection, expected[i]);
- assertTrue(err, selection.contains(expected[i]));
- }
- }
-
- private void assertSelection(String... expected) {
- assertSelectionSize(expected.length);
- assertSelected(expected);
- }
-
- private void assertRangeSelected(int begin, int end) {
- for (int i = begin; i <= end; i++) {
- assertSelected(items.get(i));
- }
- }
-
- private void assertRangeSelection(int begin, int end) {
- assertSelectionSize(end - begin + 1);
- assertRangeSelected(begin, end);
- }
-
- private void assertSelectionSize(int expected) {
- Selection selection = mManager.getSelection();
- assertEquals(selection.toString(), expected, selection.size());
- }
-
- private static final class TestCallback implements MultiSelectManager.Callback {
-
- Set<String> ignored = new HashSet<>();
- private boolean mSelectionChanged = false;
-
- @Override
- public void onItemStateChanged(String modelId, boolean selected) {}
-
- @Override
- public boolean onBeforeItemStateChange(String modelId, boolean selected) {
- return !ignored.contains(modelId);
- }
-
- @Override
- public void onSelectionChanged() {
- mSelectionChanged = true;
- }
-
- void assertSelectionChanged() {
- assertTrue(mSelectionChanged);
- }
- }
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SingleSelectTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SingleSelectTest.java
new file mode 100644
index 000000000000..62cb1b05fa55
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SingleSelectTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2015 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.dirlist;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.documentsui.testing.dirlist.SelectionProbe;
+import com.android.documentsui.testing.dirlist.TestSelectionListener;
+
+import java.util.List;
+
+@SmallTest
+public class MultiSelectManager_SingleSelectTest extends AndroidTestCase {
+
+ private static final List<String> ITEMS = TestData.create(100);
+
+ private MultiSelectManager mManager;
+ private TestSelectionListener mCallback;
+ private TestDocumentsAdapter mAdapter;
+ private SelectionProbe mSelection;
+
+ @Override
+ public void setUp() throws Exception {
+ mCallback = new TestSelectionListener();
+ mAdapter = new TestDocumentsAdapter(ITEMS);
+ mManager = new MultiSelectManager(mAdapter, MultiSelectManager.MODE_SINGLE);
+ mManager.addCallback(mCallback);
+
+ mSelection = new SelectionProbe(mManager);
+ }
+
+ public void testSimpleSelect() {
+ mManager.toggleSelection(ITEMS.get(3));
+ mManager.toggleSelection(ITEMS.get(4));
+ mCallback.assertSelectionChanged();
+ mSelection.assertSelection(4);
+ }
+
+ public void testRangeSelectionNotEstablished() {
+ mManager.toggleSelection(ITEMS.get(3));
+ mCallback.reset();
+
+ try {
+ mManager.snapRangeSelection(10);
+ fail("Should have thrown.");
+ } catch (Exception expected) {}
+
+ mCallback.assertSelectionUnchanged();
+ mSelection.assertSelection(3);
+ }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestFocusHandler.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestFocusHandler.java
new file mode 100644
index 000000000000..0585b9fd7afb
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestFocusHandler.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2016 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.dirlist;
+
+import android.view.KeyEvent;
+import android.view.View;
+
+/**
+ * A purely dummy instance of FocusHandler.
+ */
+public final class TestFocusHandler implements FocusHandler {
+
+ @Override
+ public boolean handleKey(DocumentHolder doc, int keyCode, KeyEvent event) {
+ return false;
+ }
+
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ }
+
+ @Override
+ public void restoreLastFocus() {
+ }
+
+ @Override
+ public int getFocusPosition() {
+ return 0;
+ }
+} \ No newline at end of file
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/UserInputHandler_MouseTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/UserInputHandler_MouseTest.java
index d808fe8eb221..4c34546a82b6 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/UserInputHandler_MouseTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/UserInputHandler_MouseTest.java
@@ -16,19 +16,17 @@
package com.android.documentsui.dirlist;
-import static org.junit.Assert.*;
-
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
-import android.view.KeyEvent;
+import android.support.v7.widget.RecyclerView;
import android.view.MotionEvent;
-import android.view.View;
import com.android.documentsui.Events.InputEvent;
-import com.android.documentsui.TestInputEvent;
-import com.android.documentsui.dirlist.MultiSelectManager.Selection;
import com.android.documentsui.dirlist.UserInputHandler.DocumentDetails;
+import com.android.documentsui.testing.TestEvent;
+import com.android.documentsui.testing.TestEvent.Builder;
import com.android.documentsui.testing.TestPredicate;
+import com.android.documentsui.testing.dirlist.SelectionProbe;
import org.junit.Before;
import org.junit.Test;
@@ -42,107 +40,71 @@ public final class UserInputHandler_MouseTest {
private static final List<String> ITEMS = TestData.create(100);
+ private UserInputHandler<TestEvent> mInputHandler;
+
private TestDocumentsAdapter mAdapter;
- private MultiSelectManager mSelectionMgr;
+ private SelectionProbe mSelection;
private TestPredicate<DocumentDetails> mCanSelect;
private TestPredicate<InputEvent> mRightClickHandler;
private TestPredicate<DocumentDetails> mActivateHandler;
private TestPredicate<DocumentDetails> mDeleteHandler;
- private TestInputEvent mTestEvent;
- private TestDocDetails mTestDoc;
-
- private UserInputHandler mInputHandler;
+ private Builder mEvent;
@Before
public void setUp() {
mAdapter = new TestDocumentsAdapter(ITEMS);
- mSelectionMgr = new MultiSelectManager(mAdapter, MultiSelectManager.MODE_MULTIPLE);
+ MultiSelectManager selectionMgr =
+ new MultiSelectManager(mAdapter, MultiSelectManager.MODE_MULTIPLE);
+
+ mSelection = new SelectionProbe(selectionMgr);
mCanSelect = new TestPredicate<>();
mRightClickHandler = new TestPredicate<>();
mActivateHandler = new TestPredicate<>();
mDeleteHandler = new TestPredicate<>();
- mInputHandler = new UserInputHandler(
- mSelectionMgr,
+ mInputHandler = new UserInputHandler<>(
+ selectionMgr,
new TestFocusHandler(),
(MotionEvent event) -> {
- return mTestEvent;
+ throw new UnsupportedOperationException("Not exercised in tests.");
},
- (InputEvent event) -> {
- return mTestDoc;
+ (TestEvent event) -> {
+ return event.getDocument();
},
mCanSelect,
mRightClickHandler::test,
mActivateHandler::test,
mDeleteHandler::test);
- mTestEvent = new TestInputEvent();
- mTestEvent.mouseEvent = true;
- mTestDoc = new TestDocDetails();
+ mEvent = TestEvent.builder().mouse();
}
@Test
public void testConfirmedClick_StartsSelection() {
- mTestDoc.modelId = "11";
- mInputHandler.onSingleTapConfirmed(null);
- assertSelected("11");
+ mInputHandler.onSingleTapConfirmed(mEvent.at(11).build());
+ mSelection.assertSelection(11);
}
@Test
- public void testDoubleClick_Activates() {
- mTestDoc.modelId = "11";
- mInputHandler.onDoubleTap(null);
- mActivateHandler.assertLastArgument(mTestDoc);
- }
+ public void testUnconfirmedClick_AddsToExistingSelection() {
+ mInputHandler.onSingleTapConfirmed(mEvent.at(7).build());
- void assertSelected(String id) {
- Selection sel = mSelectionMgr.getSelection();
- assertTrue(sel.contains(id));
+ mInputHandler.onSingleTapUp(mEvent.at(11).build());
+ mSelection.assertSelection(7, 11);
}
- private final class TestDocDetails implements DocumentDetails {
-
- private String modelId;
- private int position;
- private boolean inHotspot;
-
- @Override
- public String getModelId() {
- return modelId;
- }
-
- @Override
- public int getAdapterPosition() {
- return position;
- }
-
- @Override
- public boolean isInSelectionHotspot(InputEvent event) {
- return inHotspot;
- }
-
+ @Test
+ public void testDoubleClick_Activates() {
+ mInputHandler.onDoubleTap(mEvent.at(11).build());
+ mActivateHandler.assertLastArgument(mEvent.build());
}
- private final class TestFocusHandler implements FocusHandler {
-
- @Override
- public boolean handleKey(DocumentHolder doc, int keyCode, KeyEvent event) {
- return false;
- }
-
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- }
-
- @Override
- public void restoreLastFocus() {
- }
-
- @Override
- public int getFocusPosition() {
- return 0;
- }
+ @Test
+ public void testClickOff_ClearsSelection() {
+ mInputHandler.onSingleTapConfirmed(mEvent.at(11).build());
+ mInputHandler.onSingleTapUp(mEvent.at(RecyclerView.NO_POSITION).build());
+ mSelection.assertNoSelection();
}
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/UserInputHandler_RangeTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/UserInputHandler_RangeTest.java
new file mode 100644
index 000000000000..1d763f9d9632
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/UserInputHandler_RangeTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2016 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.dirlist;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.MotionEvent;
+
+import com.android.documentsui.Events.InputEvent;
+import com.android.documentsui.dirlist.UserInputHandler.DocumentDetails;
+import com.android.documentsui.testing.TestEvent;
+import com.android.documentsui.testing.TestEvent.Builder;
+import com.android.documentsui.testing.TestPredicate;
+import com.android.documentsui.testing.dirlist.SelectionProbe;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+/**
+ * UserInputHandler / MultiSelectManager integration test covering the shared
+ * responsibility of range selection.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class UserInputHandler_RangeTest {
+
+ private static final List<String> ITEMS = TestData.create(100);
+
+ private UserInputHandler<TestEvent> mInputHandler;
+
+ private TestDocumentsAdapter mAdapter;
+ private SelectionProbe mSelection;
+ private TestPredicate<DocumentDetails> mCanSelect;
+ private TestPredicate<InputEvent> mRightClickHandler;
+ private TestPredicate<DocumentDetails> mActivateHandler;
+ private TestPredicate<DocumentDetails> mDeleteHandler;
+ private Builder mEvent;
+
+ @Before
+ public void setUp() {
+
+ mAdapter = new TestDocumentsAdapter(ITEMS);
+ MultiSelectManager selectionMgr =
+ new MultiSelectManager(mAdapter, MultiSelectManager.MODE_MULTIPLE);
+
+ mSelection = new SelectionProbe(selectionMgr);
+ mCanSelect = new TestPredicate<>();
+ mRightClickHandler = new TestPredicate<>();
+ mActivateHandler = new TestPredicate<>();
+ mDeleteHandler = new TestPredicate<>();
+
+ mInputHandler = new UserInputHandler<>(
+ selectionMgr,
+ new TestFocusHandler(),
+ (MotionEvent event) -> {
+ throw new UnsupportedOperationException("Not exercised in tests.");
+ },
+ (TestEvent event) -> {
+ return event.getDocument();
+ },
+ mCanSelect,
+ mRightClickHandler::test,
+ mActivateHandler::test,
+ mDeleteHandler::test);
+
+ mEvent = TestEvent.builder().mouse();
+ }
+
+ @Test
+ public void testExtendRange() {
+ mInputHandler.onSingleTapConfirmed(mEvent.at(7).build());
+ mInputHandler.onSingleTapUp(mEvent.at(11).shift().build());
+ mSelection.assertRangeSelection(7, 11);
+ }
+
+ @Test
+ public void testExtendRangeContinues() {
+ mInputHandler.onSingleTapConfirmed(mEvent.at(7).build());
+ mInputHandler.onSingleTapUp(mEvent.at(11).shift().build());
+ mInputHandler.onSingleTapUp(mEvent.at(21).shift().build());
+ mSelection.assertRangeSelection(7, 21);
+ }
+
+ @Test
+ public void testMultipleContiguousRanges() {
+ mInputHandler.onSingleTapConfirmed(mEvent.at(7).build());
+ mInputHandler.onSingleTapUp(mEvent.at(11).shift().build());
+
+ // click without shift sets a new range start point.
+ mInputHandler.onSingleTapUp(mEvent.at(20).unshift().build());
+ mInputHandler.onSingleTapUp(mEvent.at(25).shift().build());
+
+ mSelection.assertRangeSelected(7, 11);
+ mSelection.assertRangeSelected(20, 25);
+
+ mSelection.assertRangeNotSelected(12, 19);
+ mSelection.assertSelectionSize(11);
+ }
+
+ @Test
+ public void testReducesSelectionRange() {
+ mInputHandler.onSingleTapConfirmed(mEvent.at(7).build());
+ mInputHandler.onSingleTapUp(mEvent.at(17).shift().build());
+ mInputHandler.onSingleTapUp(mEvent.at(10).shift().build());
+ mSelection.assertRangeSelection(7, 10);
+ }
+
+ @Test
+ public void testReducesSelectionRange_Reverse() {
+ mInputHandler.onSingleTapConfirmed(mEvent.at(17).build());
+ mInputHandler.onSingleTapUp(mEvent.at(7).shift().build());
+ mInputHandler.onSingleTapUp(mEvent.at(14).shift().build());
+ mSelection.assertRangeSelection(14, 17);
+ }
+
+ @Test
+ public void testExtendsRange_Reverse() {
+ mInputHandler.onSingleTapConfirmed(mEvent.at(12).build());
+ mInputHandler.onSingleTapUp(mEvent.at(5).shift().build());
+ mSelection.assertRangeSelection(5, 12);
+ }
+
+ @Test
+ public void testExtendsRange_ReversesAfterForwardClick() {
+ mInputHandler.onSingleTapConfirmed(mEvent.at(7).build());
+ mInputHandler.onSingleTapUp(mEvent.at(11).shift().build());
+ mInputHandler.onSingleTapUp(mEvent.at(0).shift().build());
+ mSelection.assertRangeSelection(0, 7);
+ }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/UserInputHandler_TouchTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/UserInputHandler_TouchTest.java
new file mode 100644
index 000000000000..2d1453eaf0dc
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/UserInputHandler_TouchTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2016 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.dirlist;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v7.widget.RecyclerView;
+import android.view.MotionEvent;
+
+import com.android.documentsui.Events.InputEvent;
+import com.android.documentsui.dirlist.UserInputHandler.DocumentDetails;
+import com.android.documentsui.testing.TestEvent;
+import com.android.documentsui.testing.TestEvent.Builder;
+import com.android.documentsui.testing.TestPredicate;
+import com.android.documentsui.testing.dirlist.SelectionProbe;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class UserInputHandler_TouchTest {
+
+ private static final List<String> ITEMS = TestData.create(100);
+
+ private UserInputHandler<TestEvent> mInputHandler;
+
+ private TestDocumentsAdapter mAdapter;
+ private SelectionProbe mSelection;
+ private TestPredicate<DocumentDetails> mCanSelect;
+ private TestPredicate<InputEvent> mRightClickHandler;
+ private TestPredicate<DocumentDetails> mActivateHandler;
+ private TestPredicate<DocumentDetails> mDeleteHandler;
+
+ private Builder mEvent;
+
+ @Before
+ public void setUp() {
+
+ mAdapter = new TestDocumentsAdapter(ITEMS);
+ MultiSelectManager selectionMgr =
+ new MultiSelectManager(mAdapter, MultiSelectManager.MODE_MULTIPLE);
+
+ mSelection = new SelectionProbe(selectionMgr);
+ mCanSelect = new TestPredicate<>();
+ mRightClickHandler = new TestPredicate<>();
+ mActivateHandler = new TestPredicate<>();
+ mDeleteHandler = new TestPredicate<>();
+
+ mInputHandler = new UserInputHandler<>(
+ selectionMgr,
+ new TestFocusHandler(),
+ (MotionEvent event) -> {
+ throw new UnsupportedOperationException("Not exercised in tests.");
+ },
+ (TestEvent event) -> {
+ return event.getDocument();
+ },
+ mCanSelect,
+ mRightClickHandler::test,
+ mActivateHandler::test,
+ mDeleteHandler::test);
+
+ mEvent = TestEvent.builder();
+ }
+
+ @Test
+ public void testTap_ActivatesWhenNoExistingSelection() {
+ mInputHandler.onSingleTapUp(mEvent.at(11).build());
+ mActivateHandler.assertLastArgument(mEvent.build());
+ }
+
+ @Test
+ public void testLongPress_StartsSelectionMode() {
+ mInputHandler.onLongPress(mEvent.at(7).build());
+ mSelection.assertSelection(7);
+ }
+
+ @Test
+ public void testLongPress_SecondPressExtendsSelection() {
+ mInputHandler.onLongPress(mEvent.at(7).build());
+ mInputHandler.onLongPress(mEvent.at(99).build());
+ mInputHandler.onLongPress(mEvent.at(13).build());
+ mSelection.assertSelection(7, 13, 99);
+ }
+
+ @Test
+ public void testTap_UnselectsSelectedItem() {
+ mInputHandler.onLongPress(mEvent.at(7).build());
+ mInputHandler.onSingleTapUp(mEvent.at(7).build());
+ mSelection.assertNoSelection();
+ }
+
+ @Test
+ public void testTapOff_ClearsSelection() {
+ mInputHandler.onLongPress(mEvent.at(7).build());
+ mInputHandler.onSingleTapUp(mEvent.at(11).build());
+ mInputHandler.onSingleTapUp(mEvent.at(RecyclerView.NO_POSITION).build());
+ mSelection.assertNoSelection();
+ }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/testing/TestEvent.java b/packages/DocumentsUI/tests/src/com/android/documentsui/testing/TestEvent.java
new file mode 100644
index 000000000000..98fa2de9a508
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/testing/TestEvent.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2016 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.testing;
+
+import com.android.documentsui.Events.InputEvent;
+import com.android.documentsui.TestInputEvent;
+import com.android.documentsui.dirlist.UserInputHandler.DocumentDetails;
+
+/**
+ * Events and DocDetails are closely related. For the pursposes of this test
+ * we coalesce the two in a single, handy-dandy test class.
+ */
+public class TestEvent extends TestInputEvent implements DocumentDetails {
+
+ private String modelId;
+ private boolean inHotspot;
+
+ @Override
+ public String getModelId() {
+ return modelId;
+ }
+
+ @Override
+ public int getAdapterPosition() {
+ return getItemPosition();
+ }
+
+ @Override
+ public boolean isInSelectionHotspot(InputEvent event) {
+ return inHotspot;
+ }
+
+ public TestEvent at(int position) {
+ this.position = position; // this is both "adapter position" and "item position".
+ modelId = String.valueOf(position);
+ return this;
+ }
+
+ public TestEvent shift() {
+ this.shiftKeyDow = true;
+ return this;
+ }
+
+ public TestEvent inHotspot() {
+ this.inHotspot = true;
+ return this;
+ }
+
+ public DocumentDetails getDocument() {
+ return this;
+ }
+
+ @Override
+ public int hashCode() {
+ return modelId != null ? modelId.hashCode() : -1;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof TestEvent)) {
+ return false;
+ }
+
+ TestEvent other = (TestEvent) o;
+ return position == other.position
+ && modelId == other.modelId
+ && shiftKeyDow == other.shiftKeyDow
+ && mouseEvent == other.mouseEvent;
+ }
+
+ public static final Builder builder() {
+ return new Builder();
+ }
+
+ public static final class Builder {
+
+ private TestEvent mState = new TestEvent();
+
+ public Builder reset() {
+ mState = new TestEvent();
+ return this;
+ }
+
+ public Builder at(int position) {
+ mState.position = position; // this is both "adapter position" and "item position".
+ mState.modelId = String.valueOf(position);
+ return this;
+ }
+
+ public Builder shift() {
+ mState.shiftKeyDow = true;
+ return this;
+ }
+
+ public Builder unshift() {
+ mState.shiftKeyDow = false;
+ return this;
+ }
+
+ public Builder inHotspot() {
+ mState.inHotspot = true;
+ return this;
+ }
+
+ public Builder mouse() {
+ mState.mouseEvent = true;
+ return this;
+ }
+
+ public TestEvent build() {
+ // Return a copy, so nobody can mess w/ our internal state.
+ TestEvent e = new TestEvent();
+ e.position = mState.position;
+ e.modelId = mState.modelId;
+ e.shiftKeyDow = mState.shiftKeyDow;
+ e.mouseEvent = mState.mouseEvent;
+ return e;
+ }
+ }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/testing/dirlist/SelectionProbe.java b/packages/DocumentsUI/tests/src/com/android/documentsui/testing/dirlist/SelectionProbe.java
new file mode 100644
index 000000000000..12f4642110e2
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/testing/dirlist/SelectionProbe.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2016 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.testing.dirlist;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.documentsui.dirlist.MultiSelectManager;
+import com.android.documentsui.dirlist.MultiSelectManager.Selection;
+
+/**
+ * Helper class for making assertions against the state of a MultiSelectManager instance.
+ */
+public final class SelectionProbe {
+
+ private final MultiSelectManager mMgr;
+
+ public SelectionProbe(MultiSelectManager mgr) {
+ mMgr = mgr;
+ }
+
+ public void assertRangeSelected(int begin, int end) {
+ for (int i = begin; i <= end; i++) {
+ assertSelected(i);
+ }
+ }
+
+ public void assertRangeNotSelected(int begin, int end) {
+ for (int i = begin; i <= end; i++) {
+ assertNotSelected(i);
+ }
+ }
+
+ public void assertRangeSelection(int begin, int end) {
+ assertSelectionSize(end - begin + 1);
+ assertRangeSelected(begin, end);
+ }
+
+ public void assertSelectionSize(int expected) {
+ Selection selection = mMgr.getSelection();
+ assertEquals(selection.toString(), expected, selection.size());
+ }
+
+ public void assertNoSelection() {
+ assertSelectionSize(0);
+ }
+
+ public void assertSelection(int... ids) {
+ assertSelected(ids);
+ assertEquals(ids.length, mMgr.getSelection().size());
+ }
+
+ public void assertSelected(int... ids) {
+ Selection sel = mMgr.getSelection();
+ for (int id : ids) {
+ String sid = String.valueOf(id);
+ assertTrue(sid + " is not in selection " + sel, sel.contains(sid));
+ }
+ }
+
+ public void assertNotSelected(int... ids) {
+ Selection sel = mMgr.getSelection();
+ for (int id : ids) {
+ String sid = String.valueOf(id);
+ assertFalse(sid + " is in selection " + sel, sel.contains(sid));
+ }
+ }
+
+ public void select(int...positions) {
+ for (int position : positions) {
+ mMgr.toggleSelection(String.valueOf(position));
+ }
+ }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/testing/dirlist/TestSelectionListener.java b/packages/DocumentsUI/tests/src/com/android/documentsui/testing/dirlist/TestSelectionListener.java
new file mode 100644
index 000000000000..08f29f0132af
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/testing/dirlist/TestSelectionListener.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2016 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.testing.dirlist;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.documentsui.dirlist.MultiSelectManager;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public final class TestSelectionListener implements MultiSelectManager.Callback {
+
+ Set<String> ignored = new HashSet<>();
+ private boolean mSelectionChanged = false;
+
+ @Override
+ public void onItemStateChanged(String modelId, boolean selected) {}
+
+ @Override
+ public boolean onBeforeItemStateChange(String modelId, boolean selected) {
+ return !ignored.contains(modelId);
+ }
+
+ @Override
+ public void onSelectionChanged() {
+ mSelectionChanged = true;
+ }
+
+ public void reset() {
+ mSelectionChanged = false;
+ }
+
+ public void assertSelectionChanged() {
+ assertTrue(mSelectionChanged);
+ }
+
+ public void assertSelectionUnchanged() {
+ assertFalse(mSelectionChanged);
+ }
+} \ No newline at end of file