Merge "Add shift-selection to DocumentsUI."
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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<DocumentInfo> selectedDocs = mModel.getSelectedDocuments();
|
||||
final List<DocumentInfo> 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<DocumentInfo> getSelectedDocuments() {
|
||||
Selection sel = getSelection(new Selection());
|
||||
return getDocuments(sel);
|
||||
}
|
||||
|
||||
List<DocumentInfo> getDocuments(Selection items) {
|
||||
final int size = (items != null) ? items.size() : 0;
|
||||
|
||||
|
||||
@@ -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<MultiSelectManager.Callback> 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<OnSelectionChangedListener> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user