From 8250db41cb0a06d4e3d684f24a234d53b61a0005 Mon Sep 17 00:00:00 2001 From: Ben Kwa Date: Wed, 7 Oct 2015 14:15:12 -0700 Subject: [PATCH] Add shift-selection to DocumentsUI. - Move the key listener from BaseActivity into DirectoryFragment, where it belongs. - Add code to detect the shift key during keyboard navigation, and extend the selection in that case. BUG=20859059 Change-Id: Ia7d3c7d4343f0185873deeaf1a35028a716b6e19 --- .../com/android/documentsui/BaseActivity.java | 19 --- .../documentsui/DirectoryFragment.java | 143 +++++------------- .../documentsui/MultiSelectManager.java | 131 ++++++++++++++-- .../DirectoryFragmentModelTest.java | 2 +- .../MultiSelectManager_GridModelTest.java | 27 +++- 5 files changed, 175 insertions(+), 147 deletions(-) diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java index 83e34408db7de..e3b13246090b6 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java @@ -466,25 +466,6 @@ abstract class BaseActivity extends Activity { } } - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - if (DEBUG) Log.d(mTag, "onKeyUp: keycode = " + keyCode); - - // TODO: Support for RecentsCreateFragment. - DirectoryFragment fragment = DirectoryFragment.get(getFragmentManager()); - if (fragment != null) { - switch (keyCode) { - case KeyEvent.KEYCODE_MOVE_HOME: - fragment.focusFirstFile(); - return true; - case KeyEvent.KEYCODE_MOVE_END: - fragment.focusLastFile(); - return true; - } - } - return super.onKeyUp(keyCode, event); - } - public void onStackPicked(DocumentStack stack) { try { // Update the restored stack to ensure we have freshest data diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java index a3a431f872cd1..6e26c88002954 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java @@ -17,7 +17,6 @@ package com.android.documentsui; import static com.android.documentsui.Shared.DEBUG; -import static com.android.documentsui.Shared.TAG; import static com.android.documentsui.State.ACTION_BROWSE; import static com.android.documentsui.State.ACTION_CREATE; import static com.android.documentsui.State.ACTION_MANAGE; @@ -79,6 +78,7 @@ import android.util.TypedValue; import android.view.ActionMode; import android.view.DragEvent; import android.view.GestureDetector; +import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; @@ -97,7 +97,6 @@ import com.android.documentsui.RecentsProvider.StateColumns; import com.android.documentsui.model.DocumentInfo; import com.android.documentsui.model.DocumentStack; import com.android.documentsui.model.RootInfo; - import com.google.common.collect.Lists; import java.util.ArrayList; @@ -109,6 +108,8 @@ import java.util.List; */ public class DirectoryFragment extends Fragment { + public static final String TAG = "DirectoryFragment"; + public static final int TYPE_NORMAL = 1; public static final int TYPE_SEARCH = 2; public static final int TYPE_RECENT_OPEN = 3; @@ -130,6 +131,7 @@ public class DirectoryFragment extends Fragment { private static final String EXTRA_IGNORE_STATE = "ignoreState"; private Model mModel; + private MultiSelectManager mSelectionManager; private Model.UpdateListener mModelUpdateListener = new ModelUpdateListener(); private View mEmptyView; @@ -268,7 +270,7 @@ public class DirectoryFragment extends Fragment { } // Clear any outstanding selection - mModel.clearSelection(); + mSelectionManager.clearSelection(); } @Override @@ -299,16 +301,16 @@ public class DirectoryFragment extends Fragment { // TODO: instead of inserting the view into the constructor, extract listener-creation code // and set the listener on the view after the fact. Then the view doesn't need to be passed - // into the selection manager which is passed into the model. - MultiSelectManager selMgr= new MultiSelectManager( + // into the selection manager. + mSelectionManager = new MultiSelectManager( mRecView, listener, state.allowMultiple ? MultiSelectManager.MODE_MULTIPLE : MultiSelectManager.MODE_SINGLE); - selMgr.addCallback(new SelectionModeListener()); + mSelectionManager.addCallback(new SelectionModeListener()); - mModel = new Model(context, selMgr, mAdapter); + mModel = new Model(context, mAdapter); mModel.addUpdateListener(mModelUpdateListener); mType = getArguments().getInt(EXTRA_TYPE); @@ -430,7 +432,7 @@ public class DirectoryFragment extends Fragment { } private boolean onSingleTapUp(MotionEvent e) { - if (Events.isTouchEvent(e) && mModel.getSelection().isEmpty()) { + if (Events.isTouchEvent(e) && mSelectionManager.getSelection().isEmpty()) { int position = getEventAdapterPosition(e); if (position != RecyclerView.NO_POSITION) { return handleViewItem(position); @@ -458,7 +460,7 @@ public class DirectoryFragment extends Fragment { if (isDocumentEnabled(docMimeType, docFlags)) { final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor); ((BaseActivity) getActivity()).onDocumentPicked(doc, mModel); - mModel.clearSelection(); + mSelectionManager.clearSelection(); return true; } return false; @@ -564,7 +566,7 @@ public class DirectoryFragment extends Fragment { mRecView.setLayoutManager(layout); // TODO: Once b/23691541 is resolved, use a listener within MultiSelectManager instead of // imperatively calling this function. - mModel.mSelectionManager.handleLayoutChanged(); + mSelectionManager.handleLayoutChanged(); // setting layout manager automatically invalidates existing ViewHolders. mThumbSize = new Point(thumbSize, thumbSize); } @@ -620,7 +622,7 @@ public class DirectoryFragment extends Fragment { @Override public void onSelectionChanged() { - mModel.getSelection(mSelected); + mSelectionManager.getSelection(mSelected); TypedValue color = new TypedValue(); if (mSelected.size() > 0) { if (DEBUG) Log.d(TAG, "Maybe starting action mode."); @@ -628,8 +630,7 @@ public class DirectoryFragment extends Fragment { if (DEBUG) Log.d(TAG, "Yeah. Starting action mode."); mActionMode = getActivity().startActionMode(this); } - getActivity().getTheme().resolveAttribute( - R.attr.colorActionMode, color, true); + getActivity().getTheme().resolveAttribute(R.attr.colorActionMode, color, true); updateActionMenu(); } else { if (DEBUG) Log.d(TAG, "Finishing action mode."); @@ -652,16 +653,17 @@ public class DirectoryFragment extends Fragment { if (DEBUG) Log.d(TAG, "Handling action mode destroyed."); mActionMode = null; // clear selection - mModel.clearSelection(); + mSelectionManager.clearSelection(); mSelected.clear(); mNoDeleteCount = 0; } @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { + int size = mSelectionManager.getSelection().size(); mode.getMenuInflater().inflate(R.menu.mode_directory, menu); - mode.setTitle(TextUtils.formatSelectedCount(mModel.getSelection().size())); - return mModel.getSelection().size() > 0; + mode.setTitle(TextUtils.formatSelectedCount(size)); + return (size > 0); } @Override @@ -681,7 +683,7 @@ public class DirectoryFragment extends Fragment { @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - Selection selection = mModel.getSelection(new Selection()); + Selection selection = mSelectionManager.getSelection(new Selection()); final int id = item.getItemId(); if (id == R.id.menu_open) { @@ -920,15 +922,22 @@ public class DirectoryFragment extends Fragment { public DocumentHolder onCreateViewHolder(ViewGroup parent, int viewType) { final State state = getDisplayState(DirectoryFragment.this); final LayoutInflater inflater = LayoutInflater.from(getContext()); + View item = null; switch (state.derivedMode) { case MODE_GRID: - return new DocumentHolder(inflater.inflate(R.layout.item_doc_grid, parent, false)); + item = inflater.inflate(R.layout.item_doc_grid, parent, false); + break; case MODE_LIST: - return new DocumentHolder(inflater.inflate(R.layout.item_doc_list, parent, false)); + item = inflater.inflate(R.layout.item_doc_list, parent, false); + break; case MODE_UNKNOWN: default: throw new IllegalStateException("Unsupported layout mode."); } + // Key event bubbling doesn't work properly, so instead of setting one key listener on + // the RecyclerView, we have to set it on each Item. See b/24865023. + item.setOnKeyListener(mSelectionManager); + return new DocumentHolder(item); } @Override @@ -957,7 +966,7 @@ public class DirectoryFragment extends Fragment { holder.docId = docId; final View itemView = holder.view; - itemView.setActivated(mModel.isSelected(position)); + itemView.setActivated(isSelected(position)); final View line1 = itemView.findViewById(R.id.line1); final View line2 = itemView.findViewById(R.id.line2); @@ -1289,7 +1298,7 @@ public class DirectoryFragment extends Fragment { } void copySelectedToClipboard() { - Selection sel = mModel.getSelection(new Selection()); + Selection sel = mSelectionManager.getSelection(new Selection()); copySelectionToClipboard(sel); } @@ -1338,51 +1347,12 @@ public class DirectoryFragment extends Fragment { } void selectAllFiles() { - boolean changed = mModel.selectAll(); + boolean changed = mSelectionManager.setItemsSelected(0, mModel.getItemCount(), true); if (changed) { updateDisplayState(); } } - /** - * Scrolls to the top of the file list and focuses the first file. - */ - void focusFirstFile() { - focusFile(0); - } - - /** - * Scrolls to the bottom of the file list and focuses the last file. - */ - void focusLastFile() { - focusFile(mAdapter.getItemCount() - 1); - } - - /** - * Scrolls to and then focuses on the file at the given position. - */ - private void focusFile(final int pos) { - // Don't smooth scroll; that taxes the system unnecessarily and makes the scroll handling - // logic below more complicated. - mRecView.scrollToPosition(pos); - - // If the item is already in view, focus it; otherwise, set a one-time listener to focus it - // when the scroll is completed. - RecyclerView.ViewHolder vh = mRecView.findViewHolderForAdapterPosition(pos); - if (vh != null) { - vh.itemView.requestFocus(); - } else { - mRecView.addOnScrollListener( - new RecyclerView.OnScrollListener() { - @Override - public void onScrolled(RecyclerView view, int dx, int dy) { - view.findViewHolderForAdapterPosition(pos).itemView.requestFocus(); - view.removeOnScrollListener(this); - } - }); - } - } - private void setupDragAndDropOnDirectoryView(View view) { // Listen for drops on non-directory items and empty space. view.setOnDragListener(mOnDragListener); @@ -1472,9 +1442,10 @@ public class DirectoryFragment extends Fragment { return Collections.EMPTY_LIST; } - final List selectedDocs = mModel.getSelectedDocuments(); + final List selectedDocs = + mModel.getDocuments(mSelectionManager.getSelection()); if (!selectedDocs.isEmpty()) { - if (!mModel.isSelected(position)) { + if (!isSelected(position)) { // There is a selection that does not include the current item, drag nothing. return Collections.EMPTY_LIST; } @@ -1694,12 +1665,15 @@ public class DirectoryFragment extends Fragment { public void afterActivityCreated(DirectoryFragment fragment) {} } + boolean isSelected(int position) { + return mSelectionManager.getSelection().contains(position); + } + /** * The data model for the current loaded directory. */ @VisibleForTesting public static final class Model implements DocumentContext { - private MultiSelectManager mSelectionManager; private RecyclerView.Adapter mViewAdapter; private Context mContext; private int mCursorCount; @@ -1710,45 +1684,11 @@ public class DirectoryFragment extends Fragment { @Nullable private String info; @Nullable private String error; - Model(Context context, MultiSelectManager selectionManager, - RecyclerView.Adapter viewAdapter) { + Model(Context context, RecyclerView.Adapter viewAdapter) { mContext = context; - mSelectionManager = selectionManager; mViewAdapter = viewAdapter; } - /** - * Selects all files in the current directory. - * @return true if the selection state changed for any files. - */ - boolean selectAll() { - return mSelectionManager.setItemsSelected(0, mCursorCount, true); - } - - /** - * Clones the current selection into the given Selection object. - * @param selection - * @return The selection that was passed in, for convenience. - */ - Selection getSelection(Selection selection) { - return mSelectionManager.getSelection(selection); - } - - /** - * @return The current selection (the live instance, not a copy). - */ - Selection getSelection() { - return mSelectionManager.getSelection(); - } - - boolean isSelected(int position) { - return mSelectionManager.getSelection().contains(position); - } - - void clearSelection() { - mSelectionManager.clearSelection(); - } - void update(DirectoryResult result) { if (DEBUG) Log.i(TAG, "Updating model with new result set."); @@ -1821,11 +1761,6 @@ public class DirectoryFragment extends Fragment { return mIsLoading; } - private List getSelectedDocuments() { - Selection sel = getSelection(new Selection()); - return getDocuments(sel); - } - List getDocuments(Selection items) { final int size = (items != null) ? items.size() : 0; diff --git a/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java b/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java index 858fb423fe067..a33781eaf8d41 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java +++ b/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java @@ -39,6 +39,7 @@ import android.util.SparseIntArray; import android.view.GestureDetector; import android.view.GestureDetector.OnDoubleTapListener; import android.view.GestureDetector.OnGestureListener; +import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; @@ -56,7 +57,7 @@ import java.util.List; * Additionally it can be configured to restrict selection to a single element, @see * #setSelectMode. */ -public final class MultiSelectManager { +public final class MultiSelectManager implements View.OnKeyListener { /** Selection mode for multiple select. **/ public static final int MODE_MULTIPLE = 0; @@ -72,6 +73,8 @@ public final class MultiSelectManager { private Selection mIntermediateSelection; private Range mRanger; + private SelectionEnvironment mEnvironment; + private final List mCallbacks = new ArrayList<>(1); private Adapter mAdapter; @@ -95,10 +98,10 @@ public final class MultiSelectManager { new RuntimeItemFinder(recyclerView), mode); + mEnvironment = new RuntimeSelectionEnvironment(recyclerView); + if (mode == MODE_MULTIPLE) { - mBandManager = new BandController( - mHelper, - new RuntimeBandEnvironment(recyclerView)); + mBandManager = new BandController(mHelper); } GestureDetector.SimpleOnGestureListener listener = @@ -900,7 +903,7 @@ public final class MultiSelectManager { * Provides functionality for BandController. Exists primarily to tests that are * fully isolated from RecyclerView. */ - interface BandEnvironment { + interface SelectionEnvironment { void showBand(Rect rect); void hideBand(); void addOnScrollListener(RecyclerView.OnScrollListener listener); @@ -913,29 +916,39 @@ public final class MultiSelectManager { Point createAbsolutePoint(Point relativePoint); Rect getAbsoluteRectForChildViewAt(int index); int getAdapterPositionAt(int index); + int getAdapterPositionForChildView(View view); int getColumnCount(); int getRowCount(); int getChildCount(); int getVisibleChildCount(); + void focusItem(int position); } /** RvFacade implementation backed by good ol' RecyclerView. */ - private static final class RuntimeBandEnvironment implements BandEnvironment { + private static final class RuntimeSelectionEnvironment implements SelectionEnvironment { private final RecyclerView mView; private final Drawable mBand; private boolean mIsOverlayShown = false; - RuntimeBandEnvironment(RecyclerView rv) { + RuntimeSelectionEnvironment(RecyclerView rv) { mView = rv; mBand = mView.getContext().getTheme().getDrawable(R.drawable.band_select_overlay); } + @Override + public int getAdapterPositionForChildView(View view) { + if (view.getParent() == mView) { + return mView.getChildAdapterPosition(view); + } else { + return RecyclerView.NO_POSITION; + } + } + @Override public int getAdapterPositionAt(int index) { - View child = mView.getChildAt(index); - return mView.getChildViewHolder(child).getAdapterPosition(); + return getAdapterPositionForChildView(mView.getChildAt(index)); } @Override @@ -1032,6 +1045,28 @@ public final class MultiSelectManager { public void hideBand() { mView.getOverlay().remove(mBand); } + + @Override + public void focusItem(final int pos) { + // If the item is already in view, focus it; otherwise, scroll to it and focus it. + RecyclerView.ViewHolder vh = mView.findViewHolderForAdapterPosition(pos); + if (vh != null) { + vh.itemView.requestFocus(); + } else { + // Don't smooth scroll; that taxes the system unnecessarily and makes the scroll + // handling logic below more complicated. See b/24865658. + mView.scrollToPosition(pos); + // Set a one-time listener to request focus when the scroll has completed. + mView.addOnScrollListener( + new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(RecyclerView view, int dx, int dy) { + view.findViewHolderForAdapterPosition(pos).itemView.requestFocus(); + view.removeOnScrollListener(this); + } + }); + } + } } public interface Callback { @@ -1175,7 +1210,6 @@ public final class MultiSelectManager { private static final int NOT_SET = -1; private final ItemFinder mItemFinder; - private final BandEnvironment mEnvironment; private final Runnable mModelBuilder; @Nullable private Rect mBounds; @@ -1188,15 +1222,14 @@ public final class MultiSelectManager { private long mScrollStartTime = NOT_SET; private final Runnable mViewScroller = new ViewScroller(); - public BandController(ItemFinder finder, final BandEnvironment environment) { + public BandController(ItemFinder finder) { mItemFinder = finder; - mEnvironment = environment; mEnvironment.addOnScrollListener(this); mModelBuilder = new Runnable() { @Override public void run() { - mModel = new GridModel(environment); + mModel = new GridModel(mEnvironment); mModel.addOnSelectionChangedListener(BandController.this); } }; @@ -1459,7 +1492,7 @@ public final class MultiSelectManager { private static final int LOWER_LEFT = LOWER | LEFT; private static final int LOWER_RIGHT = LOWER | RIGHT; - private final BandEnvironment mHelper; + private final SelectionEnvironment mHelper; private final List mOnSelectionChangedListeners = new ArrayList<>(); @@ -1497,7 +1530,7 @@ public final class MultiSelectManager { // should expand from when Shift+click is used. private int mPositionNearestOrigin = NOT_SET; - GridModel(BandEnvironment helper) { + GridModel(SelectionEnvironment helper) { mHelper = helper; mHelper.addOnScrollListener(this); } @@ -2041,4 +2074,72 @@ public final class MultiSelectManager { return true; } } + + // TODO: Might have to move this to a more global level. e.g. What should happen if the + // user taps a file and then presses shift-down? Currently the RecyclerView never even sees + // the key event. Perhaps install a global key handler to catch those events while in + // selection mode? + @Override + public boolean onKey(View view, int keyCode, KeyEvent event) { + // Listen for key-down events. This allows the handler to respond appropriately when + // the user holds down the arrow keys for navigation. + if (event.getAction() != KeyEvent.ACTION_DOWN) { + return false; + } + + int target = RecyclerView.NO_POSITION; + if (keyCode == KeyEvent.KEYCODE_MOVE_HOME) { + target = 0; + } else if (keyCode == KeyEvent.KEYCODE_MOVE_END) { + target = mAdapter.getItemCount() - 1; + } else { + // Find a navigation target based on the arrow key that the user pressed. Ignore + // navigation targets that aren't items in the recycler view. + int searchDir = -1; + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_UP: + searchDir = View.FOCUS_UP; + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + searchDir = View.FOCUS_DOWN; + break; + case KeyEvent.KEYCODE_DPAD_LEFT: + searchDir = View.FOCUS_LEFT; + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + searchDir = View.FOCUS_RIGHT; + break; + } + if (searchDir != -1) { + View targetView = view.focusSearch(searchDir); + target = mEnvironment.getAdapterPositionForChildView(targetView); + } + } + + if (target == RecyclerView.NO_POSITION) { + // If there is no valid navigation target, don't handle the keypress. + return false; + } + + // Focus the new file. + mEnvironment.focusItem(target); + + if (event.isShiftPressed()) { + if (mSelection.isEmpty()) { + // If there is no selection, start a selection when the user presses shift-arrow. + toggleSelection(mEnvironment.getAdapterPositionForChildView(view)); + } else { + // Deal with b/24802917 (selected items can't be focused) by adjusting the + // selection sorted the focused item isn't in the selection. + target -= Integer.signum(target - mRanger.mBegin); + mRanger.snapSelection(target); + } + } else if (!event.isShiftPressed() && !mSelection.isEmpty()) { + // If there is a selection, clear it if the user presses arrow with no shift. + clearSelection(); + } + + return true; + } + } diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/DirectoryFragmentModelTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/DirectoryFragmentModelTest.java index 98ffb77c4cd69..14cc2d7e9f329 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/DirectoryFragmentModelTest.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/DirectoryFragmentModelTest.java @@ -64,7 +64,7 @@ public class DirectoryFragmentModelTest extends AndroidTestCase { r.cursor = cursor; // Instantiate the model with a dummy view adapter and listener that (for now) do nothing. - model = new Model(mContext, null, new DummyAdapter()); + model = new Model(mContext, new DummyAdapter()); model.addUpdateListener(new DummyListener()); model.update(r); } diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/MultiSelectManager_GridModelTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/MultiSelectManager_GridModelTest.java index aa50b48e6c163..f6683fa840e2a 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/MultiSelectManager_GridModelTest.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/MultiSelectManager_GridModelTest.java @@ -24,6 +24,7 @@ import android.graphics.Rect; import android.support.v7.widget.RecyclerView.OnScrollListener; import android.test.AndroidTestCase; import android.util.SparseBooleanArray; +import android.view.View; import com.android.documentsui.MultiSelectManager.GridModel; @@ -34,14 +35,14 @@ public class MultiSelectManager_GridModelTest extends AndroidTestCase { private static final int VIEWPORT_HEIGHT = 500; private static GridModel model; - private static TestHelper helper; + private static TestEnvironment env; private static SparseBooleanArray lastSelection; private static int viewWidth; private static void setUp(int numChildren, int numColumns) { - helper = new TestHelper(numChildren, numColumns); + env = new TestEnvironment(numChildren, numColumns); viewWidth = VIEW_PADDING_PX + numColumns * (VIEW_PADDING_PX + CHILD_VIEW_EDGE_PX); - model = new GridModel(helper); + model = new GridModel(env); model.addOnSelectionChangedListener( new GridModel.OnSelectionChangedListener() { @Override @@ -54,7 +55,7 @@ public class MultiSelectManager_GridModelTest extends AndroidTestCase { @Override public void tearDown() { model = null; - helper = null; + env = null; lastSelection = null; } @@ -176,12 +177,12 @@ public class MultiSelectManager_GridModelTest extends AndroidTestCase { } private static void scroll(int dy) { - assertTrue(helper.verticalOffset + VIEWPORT_HEIGHT + dy <= helper.getTotalHeight()); - helper.verticalOffset += dy; + assertTrue(env.verticalOffset + VIEWPORT_HEIGHT + dy <= env.getTotalHeight()); + env.verticalOffset += dy; model.onScrolled(null, 0, dy); } - private static final class TestHelper implements MultiSelectManager.BandEnvironment { + private static final class TestEnvironment implements MultiSelectManager.SelectionEnvironment { public int horizontalOffset = 0; public int verticalOffset = 0; @@ -189,7 +190,7 @@ public class MultiSelectManager_GridModelTest extends AndroidTestCase { private final int mNumRows; private final int mNumChildren; - public TestHelper(int numChildren, int numColumns) { + public TestEnvironment(int numChildren, int numColumns) { mNumChildren = numChildren; mNumColumns = numColumns; mNumRows = (int) Math.ceil((double) numChildren / mNumColumns); @@ -307,5 +308,15 @@ public class MultiSelectManager_GridModelTest extends AndroidTestCase { public void removeCallback(Runnable r) { throw new UnsupportedOperationException(); } + + @Override + public int getAdapterPositionForChildView(View view) { + throw new UnsupportedOperationException(); + } + + @Override + public void focusItem(int i) { + throw new UnsupportedOperationException(); + } } }