diff options
author | Steve McKay <smckay@google.com> | 2016-07-13 13:03:16 -0700 |
---|---|---|
committer | Steve McKay <smckay@google.com> | 2016-07-15 01:28:04 +0000 |
commit | 07886170d5f1f0f0a688e31209d90bb6b28a20c1 (patch) | |
tree | 5a4c9e55156f429eafd6d07be141bac49a1b5621 /packages/DocumentsUI | |
parent | 6a8581339ba4ac35f55906b9d55bf21a1e7c23ce (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')
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 |