diff options
| author | 2016-02-10 14:01:19 -0800 | |
|---|---|---|
| committer | 2016-02-11 21:01:04 +0000 | |
| commit | 83df50f9971d79fcffe78a9ea1a9eeebcea996bc (patch) | |
| tree | 707c9281dd44a6ed64198633996c24bc75036e87 | |
| parent | fd32b894ff42fc20b373410b66ae924287537c47 (diff) | |
Allow multiple range selections using the shift key.
- Introduce an API on MultiSelectManager for starting/ending range
selections.
- Navigation with the shift key pressed extends the current range
selection (or starts a new one, if one isn't in progress).
- Navigation without the shift key pressed will end the current range
selection.
BUG=27124371
Change-Id: Ieddf3ee816812bf5210463536fe63179ef1809ad
(cherry picked from commit 09792ef1506f4cbd944e16651508be435d92c5be)
4 files changed, 100 insertions, 36 deletions
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java index 726538e34402..1e3cae7fac6a 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java @@ -101,7 +101,6 @@ import com.android.documentsui.model.RootInfo; import com.android.documentsui.services.FileOperationService; import com.android.documentsui.services.FileOperationService.OpType; import com.android.documentsui.services.FileOperations; - import com.google.common.collect.Lists; import java.lang.annotation.Retention; @@ -264,7 +263,7 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi mSelectionManager.addCallback(selectionListener); // Make sure this is done after the RecyclerView is set up. - mFocusManager = new FocusManager(mRecView, mSelectionManager); + mFocusManager = new FocusManager(mRecView); mModel = new Model(); mModel.addUpdateListener(mAdapter); @@ -1262,6 +1261,18 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi } if (mFocusManager.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(event)) { + if (!mSelectionManager.isRangeSelectionActive()) { + // Start a range selection if one isn't active + mSelectionManager.startRangeSelection(doc.getAdapterPosition()); + } + mSelectionManager.snapRangeSelection(mFocusManager.getFocusPosition()); + } else { + mSelectionManager.endRangeSelection(); + } return true; } @@ -1272,6 +1283,11 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi return false; } + + private boolean shouldExtendSelection(KeyEvent event) { + return Events.isNavigationKeyCode(event.getKeyCode()) && + event.isShiftPressed(); + } } private final class ModelUpdateListener implements Model.UpdateListener { diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java index ad010a6891fe..e1e39438a776 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java @@ -33,15 +33,13 @@ class FocusManager implements View.OnFocusChangeListener { private RecyclerView mView; private RecyclerView.Adapter<?> mAdapter; private LinearLayoutManager mLayout; - private MultiSelectManager mSelectionManager; private int mLastFocusPosition = RecyclerView.NO_POSITION; - public FocusManager(RecyclerView view, MultiSelectManager selectionManager) { + public FocusManager(RecyclerView view) { mView = view; mAdapter = view.getAdapter(); mLayout = (LinearLayoutManager) view.getLayoutManager(); - mSelectionManager = selectionManager; } /** @@ -60,13 +58,6 @@ class FocusManager implements View.OnFocusChangeListener { if (endPos != RecyclerView.NO_POSITION) { focusItem(endPos); - boolean extendSelection = event.isShiftPressed(); - - // Handle any necessary adjustments to selection. - if (extendSelection) { - int startPos = doc.getAdapterPosition(); - mSelectionManager.selectRange(startPos, endPos); - } } // Swallow all navigation keystrokes. Otherwise they go to the app's global // key-handler, which will route them back to the DF and cause focus to be reset. @@ -97,6 +88,13 @@ class FocusManager implements View.OnFocusChangeListener { } /** + * @return The adapter position of the last focused item. + */ + public int getFocusPosition() { + return mLastFocusPosition; + } + + /** * Finds the destination position where the focus should land for a given navigation event. * * @param view The view that received the event. diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java index d60825baa7cf..c8b6f8528272 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java @@ -370,39 +370,41 @@ public final class MultiSelectManager { } /** - * Handle a range selection event. - * <li> If the MSM is currently in single-select mode, only the last item in the range will - * actually be selected. - * <li>If a range selection is not already active, one will be started, and the given range of - * items will be selected. The given startPos becomes the anchor for the range selection. - * <li>If a range selection is already active, the anchor is not changed. The range is extended - * from its current anchor to endPos. + * Starts a range selection. If a range selection is already active, this will start a new range + * selection (which will reset the range anchor). * - * @param startPos - * @param endPos + * @param pos The anchor position for the selection range. */ - public void selectRange(int startPos, int endPos) { - // In single-select mode, just select the last item in the range. - if (mSingleSelect) { - attemptSelect(mAdapter.getModelId(endPos)); - return; - } + void startRangeSelection(int pos) { + attemptSelect(mAdapter.getModelId(pos)); + setSelectionRangeBegin(pos); + } - // In regular (i.e. multi-select) mode - if (!isRangeSelectionActive()) { - // If a range selection isn't active, start one up - attemptSelect(mAdapter.getModelId(startPos)); - setSelectionRangeBegin(startPos); - } - // Extend the range selection - mRanger.snapSelection(endPos); + /** + * Sets the end point for the current range selection, started by a call to + * {@link #startRangeSelection(int)}. This function should only be called when a range selection + * is active (see {@link #isRangeSelectionActive()}. Items in the range [anchor, end] will be + * selected. + * + * @param pos The new end position for the selection range. + */ + void snapRangeSelection(int pos) { + checkNotNull(mRanger); + mRanger.snapSelection(pos); notifySelectionChanged(); } /** + * Stops an in-progress range selection. + */ + void endRangeSelection() { + mRanger = null; + } + + /** * @return Whether or not there is a current range selection active. */ - private boolean isRangeSelectionActive() { + boolean isRangeSelectionActive() { return mRanger != null; } 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 d95fb490d81e..9447d9c18a83 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java @@ -189,6 +189,54 @@ public class MultiSelectManagerTest extends AndroidTestCase { assertSelection(items.get(20)); } + public void testRangeSelection() { + mManager.startRangeSelection(15); + mManager.snapRangeSelection(19); + assertRangeSelection(15, 19); + } + + public void testRangeSelection_snapExpand() { + mManager.startRangeSelection(15); + mManager.snapRangeSelection(19); + mManager.snapRangeSelection(27); + assertRangeSelection(15, 27); + } + + public void testRangeSelection_snapContract() { + mManager.startRangeSelection(15); + mManager.snapRangeSelection(27); + mManager.snapRangeSelection(19); + assertRangeSelection(15, 19); + } + + public void testRangeSelection_snapInvert() { + mManager.startRangeSelection(15); + mManager.snapRangeSelection(27); + mManager.snapRangeSelection(3); + assertRangeSelection(3, 15); + } + + public void testRangeSelection_multiple() { + mManager.startRangeSelection(15); + mManager.snapRangeSelection(27); + mManager.endRangeSelection(); + mManager.startRangeSelection(42); + mManager.snapRangeSelection(57); + assertSelectionSize(29); + assertRangeSelected(15, 27); + assertRangeSelected(42, 57); + + } + + public void testRangeSelection_singleSelect() { + mManager = new MultiSelectManager(mEnv, mAdapter, MultiSelectManager.MODE_SINGLE, null); + mManager.addCallback(mCallback); + mManager.startRangeSelection(11); + mManager.snapRangeSelection(19); + assertSelectionSize(1); + assertSelection(items.get(19)); + } + public void testProvisionalSelection() { Selection s = mManager.getSelection(); assertSelection(); |