Loading packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java +18 −1 Original line number Original line Diff line number Diff line Loading @@ -263,7 +263,7 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi mSelectionManager.addCallback(selectionListener); mSelectionManager.addCallback(selectionListener); // Make sure this is done after the RecyclerView is set up. // Make sure this is done after the RecyclerView is set up. mFocusManager = new FocusManager(mRecView, mSelectionManager); mFocusManager = new FocusManager(mRecView); mModel = new Model(); mModel = new Model(); mModel.addUpdateListener(mAdapter); mModel.addUpdateListener(mAdapter); Loading Loading @@ -1261,6 +1261,18 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi } } if (mFocusManager.handleKey(doc, keyCode, event)) { 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; return true; } } Loading @@ -1275,6 +1287,11 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi return false; return false; } } private boolean shouldExtendSelection(KeyEvent event) { return Events.isNavigationKeyCode(event.getKeyCode()) && event.isShiftPressed(); } } } private final class ModelUpdateListener implements Model.UpdateListener { private final class ModelUpdateListener implements Model.UpdateListener { Loading packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java +8 −10 Original line number Original line Diff line number Diff line Loading @@ -33,15 +33,13 @@ class FocusManager implements View.OnFocusChangeListener { private RecyclerView mView; private RecyclerView mView; private RecyclerView.Adapter<?> mAdapter; private RecyclerView.Adapter<?> mAdapter; private LinearLayoutManager mLayout; private LinearLayoutManager mLayout; private MultiSelectManager mSelectionManager; private int mLastFocusPosition = RecyclerView.NO_POSITION; private int mLastFocusPosition = RecyclerView.NO_POSITION; public FocusManager(RecyclerView view, MultiSelectManager selectionManager) { public FocusManager(RecyclerView view) { mView = view; mView = view; mAdapter = view.getAdapter(); mAdapter = view.getAdapter(); mLayout = (LinearLayoutManager) view.getLayoutManager(); mLayout = (LinearLayoutManager) view.getLayoutManager(); mSelectionManager = selectionManager; } } /** /** Loading Loading @@ -72,13 +70,6 @@ class FocusManager implements View.OnFocusChangeListener { if (endPos != RecyclerView.NO_POSITION) { if (endPos != RecyclerView.NO_POSITION) { focusItem(endPos); 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 // 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. // key-handler, which will route them back to the DF and cause focus to be reset. Loading Loading @@ -108,6 +99,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. * Finds the destination position where the focus should land for a given navigation event. * * Loading packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java +26 −24 Original line number Original line Diff line number Diff line Loading @@ -370,39 +370,41 @@ public final class MultiSelectManager { } } /** /** * Handle a range selection event. * Starts a range selection. If a range selection is already active, this will start a new range * <li> If the MSM is currently in single-select mode, only the last item in the range will * selection (which will reset the range anchor). * 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. * * * @param startPos * @param pos The anchor position for the selection range. * @param endPos */ */ public void selectRange(int startPos, int endPos) { void startRangeSelection(int pos) { // In single-select mode, just select the last item in the range. attemptSelect(mAdapter.getModelId(pos)); if (mSingleSelect) { setSelectionRangeBegin(pos); attemptSelect(mAdapter.getModelId(endPos)); return; } } // In regular (i.e. multi-select) mode /** if (!isRangeSelectionActive()) { * Sets the end point for the current range selection, started by a call to // If a range selection isn't active, start one up * {@link #startRangeSelection(int)}. This function should only be called when a range selection attemptSelect(mAdapter.getModelId(startPos)); * is active (see {@link #isRangeSelectionActive()}. Items in the range [anchor, end] will be setSelectionRangeBegin(startPos); * selected. } * // Extend the range selection * @param pos The new end position for the selection range. mRanger.snapSelection(endPos); */ void snapRangeSelection(int pos) { checkNotNull(mRanger); mRanger.snapSelection(pos); notifySelectionChanged(); notifySelectionChanged(); } } /** * Stops an in-progress range selection. */ void endRangeSelection() { mRanger = null; } /** /** * @return Whether or not there is a current range selection active. * @return Whether or not there is a current range selection active. */ */ private boolean isRangeSelectionActive() { boolean isRangeSelectionActive() { return mRanger != null; return mRanger != null; } } Loading packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java +48 −0 Original line number Original line Diff line number Diff line Loading @@ -189,6 +189,54 @@ public class MultiSelectManagerTest extends AndroidTestCase { assertSelection(items.get(20)); 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() { public void testProvisionalSelection() { Selection s = mManager.getSelection(); Selection s = mManager.getSelection(); assertSelection(); assertSelection(); Loading Loading
packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java +18 −1 Original line number Original line Diff line number Diff line Loading @@ -263,7 +263,7 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi mSelectionManager.addCallback(selectionListener); mSelectionManager.addCallback(selectionListener); // Make sure this is done after the RecyclerView is set up. // Make sure this is done after the RecyclerView is set up. mFocusManager = new FocusManager(mRecView, mSelectionManager); mFocusManager = new FocusManager(mRecView); mModel = new Model(); mModel = new Model(); mModel.addUpdateListener(mAdapter); mModel.addUpdateListener(mAdapter); Loading Loading @@ -1261,6 +1261,18 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi } } if (mFocusManager.handleKey(doc, keyCode, event)) { 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; return true; } } Loading @@ -1275,6 +1287,11 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi return false; return false; } } private boolean shouldExtendSelection(KeyEvent event) { return Events.isNavigationKeyCode(event.getKeyCode()) && event.isShiftPressed(); } } } private final class ModelUpdateListener implements Model.UpdateListener { private final class ModelUpdateListener implements Model.UpdateListener { Loading
packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java +8 −10 Original line number Original line Diff line number Diff line Loading @@ -33,15 +33,13 @@ class FocusManager implements View.OnFocusChangeListener { private RecyclerView mView; private RecyclerView mView; private RecyclerView.Adapter<?> mAdapter; private RecyclerView.Adapter<?> mAdapter; private LinearLayoutManager mLayout; private LinearLayoutManager mLayout; private MultiSelectManager mSelectionManager; private int mLastFocusPosition = RecyclerView.NO_POSITION; private int mLastFocusPosition = RecyclerView.NO_POSITION; public FocusManager(RecyclerView view, MultiSelectManager selectionManager) { public FocusManager(RecyclerView view) { mView = view; mView = view; mAdapter = view.getAdapter(); mAdapter = view.getAdapter(); mLayout = (LinearLayoutManager) view.getLayoutManager(); mLayout = (LinearLayoutManager) view.getLayoutManager(); mSelectionManager = selectionManager; } } /** /** Loading Loading @@ -72,13 +70,6 @@ class FocusManager implements View.OnFocusChangeListener { if (endPos != RecyclerView.NO_POSITION) { if (endPos != RecyclerView.NO_POSITION) { focusItem(endPos); 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 // 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. // key-handler, which will route them back to the DF and cause focus to be reset. Loading Loading @@ -108,6 +99,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. * Finds the destination position where the focus should land for a given navigation event. * * Loading
packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java +26 −24 Original line number Original line Diff line number Diff line Loading @@ -370,39 +370,41 @@ public final class MultiSelectManager { } } /** /** * Handle a range selection event. * Starts a range selection. If a range selection is already active, this will start a new range * <li> If the MSM is currently in single-select mode, only the last item in the range will * selection (which will reset the range anchor). * 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. * * * @param startPos * @param pos The anchor position for the selection range. * @param endPos */ */ public void selectRange(int startPos, int endPos) { void startRangeSelection(int pos) { // In single-select mode, just select the last item in the range. attemptSelect(mAdapter.getModelId(pos)); if (mSingleSelect) { setSelectionRangeBegin(pos); attemptSelect(mAdapter.getModelId(endPos)); return; } } // In regular (i.e. multi-select) mode /** if (!isRangeSelectionActive()) { * Sets the end point for the current range selection, started by a call to // If a range selection isn't active, start one up * {@link #startRangeSelection(int)}. This function should only be called when a range selection attemptSelect(mAdapter.getModelId(startPos)); * is active (see {@link #isRangeSelectionActive()}. Items in the range [anchor, end] will be setSelectionRangeBegin(startPos); * selected. } * // Extend the range selection * @param pos The new end position for the selection range. mRanger.snapSelection(endPos); */ void snapRangeSelection(int pos) { checkNotNull(mRanger); mRanger.snapSelection(pos); notifySelectionChanged(); notifySelectionChanged(); } } /** * Stops an in-progress range selection. */ void endRangeSelection() { mRanger = null; } /** /** * @return Whether or not there is a current range selection active. * @return Whether or not there is a current range selection active. */ */ private boolean isRangeSelectionActive() { boolean isRangeSelectionActive() { return mRanger != null; return mRanger != null; } } Loading
packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java +48 −0 Original line number Original line Diff line number Diff line Loading @@ -189,6 +189,54 @@ public class MultiSelectManagerTest extends AndroidTestCase { assertSelection(items.get(20)); 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() { public void testProvisionalSelection() { Selection s = mManager.getSelection(); Selection s = mManager.getSelection(); assertSelection(); assertSelection(); Loading