Loading packages/DocumentsUI/src/com/android/documentsui/Events.java +24 −2 Original line number Original line Diff line number Diff line Loading @@ -115,7 +115,8 @@ public final class Events { * A facade over MotionEvent primarily designed to permit for unit testing * A facade over MotionEvent primarily designed to permit for unit testing * of related code. * of related code. */ */ public interface InputEvent { public interface InputEvent extends AutoCloseable { boolean isTouchEvent(); boolean isMouseEvent(); boolean isMouseEvent(); boolean isPrimaryButtonPressed(); boolean isPrimaryButtonPressed(); boolean isSecondaryButtonPressed(); boolean isSecondaryButtonPressed(); Loading @@ -127,9 +128,15 @@ public final class Events { /** Returns true if the action is the final release of a mouse or touch. */ /** Returns true if the action is the final release of a mouse or touch. */ boolean isActionUp(); boolean isActionUp(); // Eliminate the checked Exception from Autoclosable. @Override public void close(); Point getOrigin(); Point getOrigin(); float getX(); float getX(); float getY(); float getY(); float getRawX(); float getRawY(); /** Returns true if the there is an item under the finger/cursor. */ /** Returns true if the there is an item under the finger/cursor. */ boolean isOverItem(); boolean isOverItem(); Loading @@ -138,7 +145,7 @@ public final class Events { int getItemPosition(); int getItemPosition(); } } public static final class MotionInputEvent implements InputEvent, AutoCloseable { public static final class MotionInputEvent implements InputEvent { private static final String TAG = "MotionInputEvent"; private static final String TAG = "MotionInputEvent"; private static final Pools.SimplePool<MotionInputEvent> sPool = new Pools.SimplePool<>(1); private static final Pools.SimplePool<MotionInputEvent> sPool = new Pools.SimplePool<>(1); Loading Loading @@ -204,6 +211,11 @@ public final class Events { recycle(); recycle(); } } @Override public boolean isTouchEvent() { return Events.isTouchEvent(mEvent); } @Override @Override public boolean isMouseEvent() { public boolean isMouseEvent() { return Events.isMouseEvent(mEvent); return Events.isMouseEvent(mEvent); Loading Loading @@ -249,6 +261,16 @@ public final class Events { return mEvent.getY(); return mEvent.getY(); } } @Override public float getRawX() { return mEvent.getRawX(); } @Override public float getRawY() { return mEvent.getRawY(); } @Override @Override public boolean isOverItem() { public boolean isOverItem() { return getItemPosition() != RecyclerView.NO_POSITION; return getItemPosition() != RecyclerView.NO_POSITION; Loading packages/DocumentsUI/src/com/android/documentsui/dirlist/BandController.java +5 −0 Original line number Original line Diff line number Diff line Loading @@ -178,6 +178,11 @@ public class BandController extends RecyclerView.OnScrollListener { } } private boolean handleEvent(MotionInputEvent e) { private boolean handleEvent(MotionInputEvent e) { // Don't start, or extend bands on right click. if (e.isSecondaryButtonPressed()) { return false; } if (!e.isMouseEvent() && isActive()) { if (!e.isMouseEvent() && isActive()) { // Weird things happen if we keep up band select // Weird things happen if we keep up band select // when touch events happen. // when touch events happen. Loading packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java +92 −82 Original line number Original line Diff line number Diff line Loading @@ -63,6 +63,7 @@ import android.view.LayoutInflater; import android.view.Menu; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.ImageView; Loading @@ -75,6 +76,7 @@ import com.android.documentsui.DirectoryResult; import com.android.documentsui.DocumentClipper; import com.android.documentsui.DocumentClipper; import com.android.documentsui.DocumentsActivity; import com.android.documentsui.DocumentsActivity; import com.android.documentsui.DocumentsApplication; import com.android.documentsui.DocumentsApplication; import com.android.documentsui.Events.InputEvent; import com.android.documentsui.Events.MotionInputEvent; import com.android.documentsui.Events.MotionInputEvent; import com.android.documentsui.ItemDragListener; import com.android.documentsui.ItemDragListener; import com.android.documentsui.MenuManager; import com.android.documentsui.MenuManager; Loading @@ -92,6 +94,7 @@ import com.android.documentsui.State; import com.android.documentsui.State.ViewMode; import com.android.documentsui.State.ViewMode; import com.android.documentsui.UrisSupplier; import com.android.documentsui.UrisSupplier; import com.android.documentsui.dirlist.MultiSelectManager.Selection; import com.android.documentsui.dirlist.MultiSelectManager.Selection; import com.android.documentsui.dirlist.UserInputHandler.DocumentDetails; import com.android.documentsui.model.DocumentInfo; import com.android.documentsui.model.DocumentInfo; import com.android.documentsui.model.RootInfo; import com.android.documentsui.model.RootInfo; import com.android.documentsui.services.FileOperation; import com.android.documentsui.services.FileOperation; Loading @@ -106,6 +109,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Collections; import java.util.List; import java.util.List; import java.util.Objects; import java.util.Objects; import java.util.function.Function; import javax.annotation.Nullable; import javax.annotation.Nullable; Loading Loading @@ -136,9 +140,9 @@ public class DirectoryFragment extends Fragment private static final int LOADER_ID = 42; private static final int LOADER_ID = 42; private Model mModel; private Model mModel; private MultiSelectManager mSelectionManager; private MultiSelectManager mSelectionMgr; private Model.UpdateListener mModelUpdateListener = new ModelUpdateListener(); private Model.UpdateListener mModelUpdateListener = new ModelUpdateListener(); private ItemEventListener mItemEventListener; private UserInputHandler mInputHandler; private SelectionModeListener mSelectionModeListener; private SelectionModeListener mSelectionModeListener; private FocusManager mFocusManager; private FocusManager mFocusManager; Loading Loading @@ -240,7 +244,7 @@ public class DirectoryFragment extends Fragment @Override @Override public void onDestroyView() { public void onDestroyView() { mSelectionManager.clearSelection(); mSelectionMgr.clearSelection(); // Cancel any outstanding thumbnail requests // Cancel any outstanding thumbnail requests final int count = mRecView.getChildCount(); final int count = mRecView.getChildCount(); Loading Loading @@ -296,46 +300,49 @@ public class DirectoryFragment extends Fragment // TODO: instead of inserting the view into the constructor, extract listener-creation code // 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 // and set the listener on the view after the fact. Then the view doesn't need to be passed // into the selection manager. // into the selection manager. mSelectionManager = new MultiSelectManager( mSelectionMgr = new MultiSelectManager( mAdapter, mAdapter, state.allowMultiple state.allowMultiple ? MultiSelectManager.MODE_MULTIPLE ? MultiSelectManager.MODE_MULTIPLE : MultiSelectManager.MODE_SINGLE); : MultiSelectManager.MODE_SINGLE); GestureListener gestureListener = new GestureListener( // Make sure this is done after the RecyclerView is set up. mSelectionManager, mFocusManager = new FocusManager(context, mRecView, mModel); mRecView, mInputHandler = new UserInputHandler( mSelectionMgr, mFocusManager, new Function<MotionEvent, InputEvent>() { @Override public InputEvent apply(MotionEvent t) { return MotionInputEvent.obtain(t, mRecView); } }, this::getTarget, this::getTarget, this::onDoubleTap, this::canSelect, this::onRightClick); this::onRightClick, this::onActivate, (DocumentDetails ignored) -> { return onDeleteSelectedDocuments(); }); mGestureDetector = mGestureDetector = new ListeningGestureDetector(this.getContext(), mDragHelper, gestureListener); new ListeningGestureDetector(this.getContext(), mDragHelper, mInputHandler); mRecView.addOnItemTouchListener(mGestureDetector); mRecView.addOnItemTouchListener(mGestureDetector); mEmptyView.setOnTouchListener(mGestureDetector); mEmptyView.setOnTouchListener(mGestureDetector); if (state.allowMultiple) { if (state.allowMultiple) { mBandController = new BandController(mRecView, mAdapter, mSelectionManager); mBandController = new BandController(mRecView, mAdapter, mSelectionMgr); } } mSelectionModeListener = new SelectionModeListener(); mSelectionModeListener = new SelectionModeListener(); mSelectionManager.addCallback(mSelectionModeListener); mSelectionMgr.addCallback(mSelectionModeListener); mModel = new Model(); mModel = new Model(); mModel.addUpdateListener(mAdapter); mModel.addUpdateListener(mAdapter); mModel.addUpdateListener(mModelUpdateListener); mModel.addUpdateListener(mModelUpdateListener); // Make sure this is done after the RecyclerView is set up. mFocusManager = new FocusManager(context, mRecView, mModel); mItemEventListener = new ItemEventListener( mSelectionManager, mFocusManager, this::handleViewItem, this::deleteDocuments, this::canSelect); final BaseActivity activity = getBaseActivity(); final BaseActivity activity = getBaseActivity(); mTuner = activity.createFragmentTuner(); mTuner = activity.createFragmentTuner(); mMenuManager = activity.getMenuManager(); mMenuManager = activity.getMenuManager(); Loading @@ -351,7 +358,7 @@ public class DirectoryFragment extends Fragment } } public void retainState(RetainedState state) { public void retainState(RetainedState state) { state.selection = mSelectionManager.getSelection(new Selection()); state.selection = mSelectionMgr.getSelection(new Selection()); } } @Override @Override Loading Loading @@ -419,49 +426,37 @@ public class DirectoryFragment extends Fragment FileOperations.start(getContext(), operation, mFileOpCallback); FileOperations.start(getContext(), operation, mFileOpCallback); } } protected boolean onDoubleTap(MotionInputEvent event) { protected boolean onRightClick(InputEvent e) { if (event.isMouseEvent()) { String id = getModelId(event); if (id != null) { return handleViewItem(id); } } return false; } protected boolean onRightClick(MotionInputEvent e) { if (e.getItemPosition() != RecyclerView.NO_POSITION) { if (e.getItemPosition() != RecyclerView.NO_POSITION) { final DocumentHolder holder = getTarget(e); final DocumentHolder doc = getTarget(e); String modelId = getModelId(holder.itemView); if (!mSelectionMgr.getSelection().contains(doc.modelId)) { if (!mSelectionManager.getSelection().contains(modelId)) { mSelectionMgr.replaceSelection(Collections.singleton(doc.modelId)); mSelectionManager.clearSelection(); // Set selection on the one single item List<String> ids = Collections.singletonList(modelId); mSelectionManager.setItemsSelected(ids, true); } } // We are registering for context menu here so long-press doesn't trigger this // We are registering for context menu here so long-press doesn't trigger this // floating context menu, and then quickly unregister right afterwards // floating context menu, and then quickly unregister right afterwards registerForContextMenu(holder.itemView); registerForContextMenu(doc.itemView); mRecView.showContextMenuForChild(holder.itemView, mRecView.showContextMenuForChild(doc.itemView, e.getX() - holder.itemView.getLeft(), e.getY() - holder.itemView.getTop()); e.getX() - doc.itemView.getLeft(), e.getY() - doc.itemView.getTop()); unregisterForContextMenu(holder.itemView); unregisterForContextMenu(doc.itemView); return true; } } // If there was no corresponding item pos, that means user right-clicked on the blank // If there was no corresponding item pos, that means user right-clicked on the blank // pane // pane // We would want to show different options then, and not select any item // We would want to show different options then, and not select any item // The blank pane could be the recyclerView or the emptyView, so we need to register // The blank pane could be the recyclerView or the emptyView, so we need to register // according to whichever one is visible // according to whichever one is visible else if (mEmptyView.getVisibility() == View.VISIBLE) { if (mEmptyView.getVisibility() == View.VISIBLE) { registerForContextMenu(mEmptyView); registerForContextMenu(mEmptyView); mEmptyView.showContextMenu(e.getX(), e.getY()); mEmptyView.showContextMenu(e.getX(), e.getY()); unregisterForContextMenu(mEmptyView); unregisterForContextMenu(mEmptyView); return true; return true; } else { } registerForContextMenu(mRecView); registerForContextMenu(mRecView); mRecView.showContextMenu(e.getX(), e.getY()); mRecView.showContextMenu(e.getX(), e.getY()); unregisterForContextMenu(mRecView); unregisterForContextMenu(mRecView); } return true; return true; } } Loading @@ -478,7 +473,7 @@ public class DirectoryFragment extends Fragment if (mTuner.isDocumentEnabled(docMimeType, docFlags)) { if (mTuner.isDocumentEnabled(docMimeType, docFlags)) { final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor); final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor); getBaseActivity().onDocumentPicked(doc, mModel); getBaseActivity().onDocumentPicked(doc, mModel); mSelectionManager.clearSelection(); mSelectionMgr.clearSelection(); return true; return true; } } return false; return false; Loading Loading @@ -643,7 +638,7 @@ public class DirectoryFragment extends Fragment @Override @Override public void onSelectionChanged() { public void onSelectionChanged() { mSelectionManager.getSelection(mSelected); mSelectionMgr.getSelection(mSelected); if (mSelected.size() > 0) { if (mSelected.size() > 0) { if (DEBUG) Log.d(TAG, "Maybe starting action mode."); if (DEBUG) Log.d(TAG, "Maybe starting action mode."); if (mActionMode == null) { if (mActionMode == null) { Loading Loading @@ -673,7 +668,7 @@ public class DirectoryFragment extends Fragment if (DEBUG) Log.d(TAG, "Handling action mode destroyed."); if (DEBUG) Log.d(TAG, "Handling action mode destroyed."); mActionMode = null; mActionMode = null; // clear selection // clear selection mSelectionManager.clearSelection(); mSelectionMgr.clearSelection(); mSelected.clear(); mSelected.clear(); mDirectoryCount = 0; mDirectoryCount = 0; Loading Loading @@ -704,7 +699,7 @@ public class DirectoryFragment extends Fragment mRecView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); mRecView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); } } int size = mSelectionManager.getSelection().size(); int size = mSelectionMgr.getSelection().size(); mode.getMenuInflater().inflate(R.menu.mode_directory, menu); mode.getMenuInflater().inflate(R.menu.mode_directory, menu); mode.setTitle(TextUtils.formatSelectedCount(size)); mode.setTitle(TextUtils.formatSelectedCount(size)); Loading Loading @@ -752,7 +747,7 @@ public class DirectoryFragment extends Fragment @Override @Override public boolean canRename() { public boolean canRename() { return mNoRenameCount == 0 && mSelectionManager.getSelection().size() == 1; return mNoRenameCount == 0 && mSelectionMgr.getSelection().size() == 1; } } private void updateActionMenu() { private void updateActionMenu() { Loading @@ -768,7 +763,7 @@ public class DirectoryFragment extends Fragment } } private boolean handleMenuItemClick(MenuItem item) { private boolean handleMenuItemClick(MenuItem item) { Selection selection = mSelectionManager.getSelection(new Selection()); Selection selection = mSelectionMgr.getSelection(new Selection()); switch (item.getItemId()) { switch (item.getItemId()) { case R.id.menu_open: case R.id.menu_open: Loading Loading @@ -835,9 +830,9 @@ public class DirectoryFragment extends Fragment } } public final boolean onBackPressed() { public final boolean onBackPressed() { if (mSelectionManager.hasSelection()) { if (mSelectionMgr.hasSelection()) { if (DEBUG) Log.d(TAG, "Clearing selection on selection manager."); if (DEBUG) Log.d(TAG, "Clearing selection on selection manager."); mSelectionManager.clearSelection(); mSelectionMgr.clearSelection(); return true; return true; } } return false; return false; Loading Loading @@ -949,6 +944,29 @@ public class DirectoryFragment extends Fragment return message; return message; } } private boolean onDeleteSelectedDocuments() { if (mSelectionMgr.hasSelection()) { deleteDocuments(mSelectionMgr.getSelection(new Selection())); } return false; } private boolean onActivate(DocumentDetails doc) { // Toggle selection if we're in selection mode, othewise, view item. if (mSelectionMgr.hasSelection()) { mSelectionMgr.toggleSelection(doc.getModelId()); } else { handleViewItem(doc.getModelId()); } return true; } // private boolean onSelect(DocumentDetails doc) { // mSelectionMgr.toggleSelection(doc.getModelId()); // mSelectionMgr.setSelectionRangeBegin(doc.getAdapterPosition()); // return true; // } private void deleteDocuments(final Selection selected) { private void deleteDocuments(final Selection selected) { Metrics.logUserAction(getContext(), Metrics.USER_ACTION_DELETE); Metrics.logUserAction(getContext(), Metrics.USER_ACTION_DELETE); Loading Loading @@ -1100,7 +1118,7 @@ public class DirectoryFragment extends Fragment @Override @Override public void initDocumentHolder(DocumentHolder holder) { public void initDocumentHolder(DocumentHolder holder) { holder.addEventListener(mItemEventListener); holder.addKeyEventListener(mInputHandler); holder.itemView.setOnFocusChangeListener(mFocusManager); holder.itemView.setOnFocusChangeListener(mFocusManager); } } Loading Loading @@ -1186,11 +1204,11 @@ public class DirectoryFragment extends Fragment public void copySelectedToClipboard() { public void copySelectedToClipboard() { Metrics.logUserAction(getContext(), Metrics.USER_ACTION_COPY_CLIPBOARD); Metrics.logUserAction(getContext(), Metrics.USER_ACTION_COPY_CLIPBOARD); Selection selection = mSelectionManager.getSelection(new Selection()); Selection selection = mSelectionMgr.getSelection(new Selection()); if (selection.isEmpty()) { if (selection.isEmpty()) { return; return; } } mSelectionManager.clearSelection(); mSelectionMgr.clearSelection(); mClipper.clipDocumentsForCopy(mModel::getItemUri, selection); mClipper.clipDocumentsForCopy(mModel::getItemUri, selection); Loading @@ -1200,11 +1218,11 @@ public class DirectoryFragment extends Fragment public void cutSelectedToClipboard() { public void cutSelectedToClipboard() { Metrics.logUserAction(getContext(), Metrics.USER_ACTION_CUT_CLIPBOARD); Metrics.logUserAction(getContext(), Metrics.USER_ACTION_CUT_CLIPBOARD); Selection selection = mSelectionManager.getSelection(new Selection()); Selection selection = mSelectionMgr.getSelection(new Selection()); if (selection.isEmpty()) { if (selection.isEmpty()) { return; return; } } mSelectionManager.clearSelection(); mSelectionMgr.clearSelection(); mClipper.clipDocumentsForCut(mModel::getItemUri, selection, getDisplayState().stack.peek()); mClipper.clipDocumentsForCut(mModel::getItemUri, selection, getDisplayState().stack.peek()); Loading Loading @@ -1239,7 +1257,7 @@ public class DirectoryFragment extends Fragment } } // Only select things currently visible in the adapter. // Only select things currently visible in the adapter. boolean changed = mSelectionManager.setItemsSelected(enabled, true); boolean changed = mSelectionMgr.setItemsSelected(enabled, true); if (changed) { if (changed) { updateDisplayState(); updateDisplayState(); } } Loading Loading @@ -1277,7 +1295,7 @@ public class DirectoryFragment extends Fragment void dragStopped(boolean result) { void dragStopped(boolean result) { if (result) { if (result) { mSelectionManager.clearSelection(); mSelectionMgr.clearSelection(); } } } } Loading Loading @@ -1363,19 +1381,7 @@ public class DirectoryFragment extends Fragment } } } } /** private @Nullable DocumentHolder getTarget(InputEvent e) { * Gets the model ID for a given motion event (using the event position) */ private String getModelId(MotionInputEvent e) { RecyclerView.ViewHolder vh = getTarget(e); if (vh instanceof DocumentHolder) { return ((DocumentHolder) vh).modelId; } else { return null; } } private @Nullable DocumentHolder getTarget(MotionInputEvent e) { View childView = mRecView.findChildViewUnder(e.getX(), e.getY()); View childView = mRecView.findChildViewUnder(e.getX(), e.getY()); if (childView != null) { if (childView != null) { return (DocumentHolder) mRecView.getChildViewHolder(childView); return (DocumentHolder) mRecView.getChildViewHolder(childView); Loading Loading @@ -1423,7 +1429,7 @@ public class DirectoryFragment extends Fragment @Override @Override public boolean isSelected(String modelId) { public boolean isSelected(String modelId) { return mSelectionManager.getSelection().contains(modelId); return mSelectionMgr.getSelection().contains(modelId); } } private final class ModelUpdateListener implements Model.UpdateListener { private final class ModelUpdateListener implements Model.UpdateListener { Loading Loading @@ -1480,7 +1486,7 @@ public class DirectoryFragment extends Fragment private DocumentInfo getSingleSelectedDocument(Selection selection) { private DocumentInfo getSingleSelectedDocument(Selection selection) { assert (selection.size() == 1); assert (selection.size() == 1); final List<DocumentInfo> docs = mModel.getDocuments(mSelectionManager.getSelection()); final List<DocumentInfo> docs = mModel.getDocuments(mSelectionMgr.getSelection()); assert (docs.size() == 1); assert (docs.size() == 1); return docs.get(0); return docs.get(0); } } Loading @@ -1489,7 +1495,7 @@ public class DirectoryFragment extends Fragment new DragStartHelper.OnDragStartListener() { new DragStartHelper.OnDragStartListener() { @Override @Override public boolean onDragStart(View v, DragStartHelper helper) { public boolean onDragStart(View v, DragStartHelper helper) { Selection selection = mSelectionManager.getSelection(); Selection selection = mSelectionMgr.getSelection(); if (v == null) { if (v == null) { Log.d(TAG, "Ignoring drag event, null view"); Log.d(TAG, "Ignoring drag event, null view"); Loading Loading @@ -1532,6 +1538,10 @@ public class DirectoryFragment extends Fragment } } }; }; private boolean canSelect(DocumentDetails doc) { return canSelect(doc.getModelId()); } private boolean canSelect(String modelId) { private boolean canSelect(String modelId) { // TODO: Combine this method with onBeforeItemStateChange, as both of them are almost // TODO: Combine this method with onBeforeItemStateChange, as both of them are almost Loading Loading @@ -1662,7 +1672,7 @@ public class DirectoryFragment extends Fragment updateLayout(state.derivedMode); updateLayout(state.derivedMode); if (mRestoredSelection != null) { if (mRestoredSelection != null) { mSelectionManager.restoreSelection(mRestoredSelection); mSelectionMgr.restoreSelection(mRestoredSelection); // Note, we'll take care of cleaning up retained selection // Note, we'll take care of cleaning up retained selection // in the selection handler where we already have some // in the selection handler where we already have some // specialized code to handle when selection was restored. // specialized code to handle when selection was restored. Loading packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java +39 −63 Original line number Original line Diff line number Diff line Loading @@ -24,28 +24,31 @@ import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView; import android.view.KeyEvent; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup; import com.android.documentsui.Events; import com.android.documentsui.Events.InputEvent; import com.android.documentsui.R; import com.android.documentsui.R; import com.android.documentsui.State; import com.android.documentsui.State; import com.android.documentsui.dirlist.UserInputHandler.DocumentDetails; public abstract class DocumentHolder public abstract class DocumentHolder extends RecyclerView.ViewHolder extends RecyclerView.ViewHolder implements View.OnKeyListener { implements View.OnKeyListener, DocumentDetails { static final float DISABLED_ALPHA = 0.3f; static final float DISABLED_ALPHA = 0.3f; @Deprecated // Public access is deprecated, use #getModelId. public @Nullable String modelId; public @Nullable String modelId; final Context mContext; final Context mContext; final @ColorInt int mDefaultBgColor; final @ColorInt int mDefaultBgColor; final @ColorInt int mSelectedBgColor; final @ColorInt int mSelectedBgColor; DocumentHolder.EventListener mEventListener; // See #addKeyEventListener for details on the need for this field. private View.OnKeyListener mKeyListener; KeyboardEventListener mKeyEventListener; private View mSelectionHotspot; private View mSelectionHotspot; Loading Loading @@ -74,6 +77,11 @@ public abstract class DocumentHolder */ */ public abstract void bind(Cursor cursor, String modelId, State state); public abstract void bind(Cursor cursor, String modelId, State state); @Override public String getModelId() { return modelId; } /** /** * Makes the associated item view appear selected. Note that this merely affects the appearance * Makes the associated item view appear selected. Note that this merely affects the appearance * of the view, it doesn't actually select the item. * of the view, it doesn't actually select the item. Loading Loading @@ -107,37 +115,25 @@ public abstract class DocumentHolder @Override @Override public boolean onKey(View v, int keyCode, KeyEvent event) { public boolean onKey(View v, int keyCode, KeyEvent event) { // Event listener should always be set. assert(mKeyEventListener != null); assert(mEventListener != null); return mKeyEventListener.onKey(this, keyCode, event); return mEventListener.onKey(this, keyCode, event); } public void addEventListener(DocumentHolder.EventListener listener) { // Just handle one for now; switch to a list if necessary. assert(mEventListener == null); mEventListener = listener; } } public void addOnKeyListener(View.OnKeyListener listener) { /** // Just handle one for now; switch to a list if necessary. * Installs a delegate to receive keyboard input events. This arrangement is necessitated assert(mKeyListener == null); * by the fact that a single listener cannot listen to all keyboard events mKeyListener = listener; * on RecyclerView (our parent view). Not sure why this is, but have been } * assured it is the case. * public boolean onSingleTapUp(MotionEvent event) { * <p>Ideally we'd not involve DocumentHolder in propagation of events like this. if (Events.isMouseEvent(event)) { */ // Mouse clicks select. public void addKeyEventListener(KeyboardEventListener listener) { // TODO: && input.isPrimaryButtonPressed(), but it is returning false. assert(mKeyEventListener == null); if (mEventListener != null) { mKeyEventListener = listener; return mEventListener.onSelect(this); } } else if (Events.isTouchEvent(event)) { // Touch events select if they occur in the selection hotspot, otherwise they activate. if (mEventListener == null) { return false; } } @Override public boolean isInSelectionHotspot(InputEvent event) { // Do everything in global coordinates - it makes things simpler. // Do everything in global coordinates - it makes things simpler. int[] coords = new int[2]; int[] coords = new int[2]; mSelectionHotspot.getLocationOnScreen(coords); mSelectionHotspot.getLocationOnScreen(coords); Loading @@ -145,13 +141,7 @@ public abstract class DocumentHolder coords[1] + mSelectionHotspot.getHeight()); coords[1] + mSelectionHotspot.getHeight()); // If the tap occurred within the icon rect, consider it a selection. // If the tap occurred within the icon rect, consider it a selection. if (rect.contains((int) event.getRawX(), (int) event.getRawY())) { return rect.contains((int) event.getRawX(), (int) event.getRawY()); return mEventListener.onSelect(this); } else { return mEventListener.onActivate(this); } } return false; } } static void setEnabledRecursive(View itemView, boolean enabled) { static void setEnabledRecursive(View itemView, boolean enabled) { Loading @@ -174,23 +164,9 @@ public abstract class DocumentHolder /** /** * Implement this in order to be able to respond to events coming from DocumentHolders. * Implement this in order to be able to respond to events coming from DocumentHolders. * TODO: Make this bubble up logic events rather than having imperative commands. */ */ interface EventListener { interface KeyboardEventListener { /** * Handles activation events on the document holder. * * @param doc The target DocumentHolder * @return Whether the event was handled. */ public boolean onActivate(DocumentHolder doc); /** * Handles selection events on the document holder. * * @param doc The target DocumentHolder * @return Whether the event was handled. */ public boolean onSelect(DocumentHolder doc); /** /** * Handles key events on the document holder. * Handles key events on the document holder. Loading packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusHandler.java 0 → 100644 +51 −0 Original line number Original line Diff line number Diff line /* * 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 class that handles navigation and focus within the DirectoryFragment. */ interface FocusHandler extends View.OnFocusChangeListener { /** * Handles navigation (setting focus, adjusting selection if needed) arising from incoming key * events. * * @param doc The DocumentHolder receiving the key event. * @param keyCode * @param event * @return Whether the event was handled. */ boolean handleKey(DocumentHolder doc, int keyCode, KeyEvent event); @Override void onFocusChange(View v, boolean hasFocus); /** * Requests focus on the item that last had focus. Scrolls to that item if necessary. */ void restoreLastFocus(); /** * @return The adapter position of the last focused item. */ int getFocusPosition(); } Loading
packages/DocumentsUI/src/com/android/documentsui/Events.java +24 −2 Original line number Original line Diff line number Diff line Loading @@ -115,7 +115,8 @@ public final class Events { * A facade over MotionEvent primarily designed to permit for unit testing * A facade over MotionEvent primarily designed to permit for unit testing * of related code. * of related code. */ */ public interface InputEvent { public interface InputEvent extends AutoCloseable { boolean isTouchEvent(); boolean isMouseEvent(); boolean isMouseEvent(); boolean isPrimaryButtonPressed(); boolean isPrimaryButtonPressed(); boolean isSecondaryButtonPressed(); boolean isSecondaryButtonPressed(); Loading @@ -127,9 +128,15 @@ public final class Events { /** Returns true if the action is the final release of a mouse or touch. */ /** Returns true if the action is the final release of a mouse or touch. */ boolean isActionUp(); boolean isActionUp(); // Eliminate the checked Exception from Autoclosable. @Override public void close(); Point getOrigin(); Point getOrigin(); float getX(); float getX(); float getY(); float getY(); float getRawX(); float getRawY(); /** Returns true if the there is an item under the finger/cursor. */ /** Returns true if the there is an item under the finger/cursor. */ boolean isOverItem(); boolean isOverItem(); Loading @@ -138,7 +145,7 @@ public final class Events { int getItemPosition(); int getItemPosition(); } } public static final class MotionInputEvent implements InputEvent, AutoCloseable { public static final class MotionInputEvent implements InputEvent { private static final String TAG = "MotionInputEvent"; private static final String TAG = "MotionInputEvent"; private static final Pools.SimplePool<MotionInputEvent> sPool = new Pools.SimplePool<>(1); private static final Pools.SimplePool<MotionInputEvent> sPool = new Pools.SimplePool<>(1); Loading Loading @@ -204,6 +211,11 @@ public final class Events { recycle(); recycle(); } } @Override public boolean isTouchEvent() { return Events.isTouchEvent(mEvent); } @Override @Override public boolean isMouseEvent() { public boolean isMouseEvent() { return Events.isMouseEvent(mEvent); return Events.isMouseEvent(mEvent); Loading Loading @@ -249,6 +261,16 @@ public final class Events { return mEvent.getY(); return mEvent.getY(); } } @Override public float getRawX() { return mEvent.getRawX(); } @Override public float getRawY() { return mEvent.getRawY(); } @Override @Override public boolean isOverItem() { public boolean isOverItem() { return getItemPosition() != RecyclerView.NO_POSITION; return getItemPosition() != RecyclerView.NO_POSITION; Loading
packages/DocumentsUI/src/com/android/documentsui/dirlist/BandController.java +5 −0 Original line number Original line Diff line number Diff line Loading @@ -178,6 +178,11 @@ public class BandController extends RecyclerView.OnScrollListener { } } private boolean handleEvent(MotionInputEvent e) { private boolean handleEvent(MotionInputEvent e) { // Don't start, or extend bands on right click. if (e.isSecondaryButtonPressed()) { return false; } if (!e.isMouseEvent() && isActive()) { if (!e.isMouseEvent() && isActive()) { // Weird things happen if we keep up band select // Weird things happen if we keep up band select // when touch events happen. // when touch events happen. Loading
packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java +92 −82 Original line number Original line Diff line number Diff line Loading @@ -63,6 +63,7 @@ import android.view.LayoutInflater; import android.view.Menu; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.ImageView; Loading @@ -75,6 +76,7 @@ import com.android.documentsui.DirectoryResult; import com.android.documentsui.DocumentClipper; import com.android.documentsui.DocumentClipper; import com.android.documentsui.DocumentsActivity; import com.android.documentsui.DocumentsActivity; import com.android.documentsui.DocumentsApplication; import com.android.documentsui.DocumentsApplication; import com.android.documentsui.Events.InputEvent; import com.android.documentsui.Events.MotionInputEvent; import com.android.documentsui.Events.MotionInputEvent; import com.android.documentsui.ItemDragListener; import com.android.documentsui.ItemDragListener; import com.android.documentsui.MenuManager; import com.android.documentsui.MenuManager; Loading @@ -92,6 +94,7 @@ import com.android.documentsui.State; import com.android.documentsui.State.ViewMode; import com.android.documentsui.State.ViewMode; import com.android.documentsui.UrisSupplier; import com.android.documentsui.UrisSupplier; import com.android.documentsui.dirlist.MultiSelectManager.Selection; import com.android.documentsui.dirlist.MultiSelectManager.Selection; import com.android.documentsui.dirlist.UserInputHandler.DocumentDetails; import com.android.documentsui.model.DocumentInfo; import com.android.documentsui.model.DocumentInfo; import com.android.documentsui.model.RootInfo; import com.android.documentsui.model.RootInfo; import com.android.documentsui.services.FileOperation; import com.android.documentsui.services.FileOperation; Loading @@ -106,6 +109,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Collections; import java.util.List; import java.util.List; import java.util.Objects; import java.util.Objects; import java.util.function.Function; import javax.annotation.Nullable; import javax.annotation.Nullable; Loading Loading @@ -136,9 +140,9 @@ public class DirectoryFragment extends Fragment private static final int LOADER_ID = 42; private static final int LOADER_ID = 42; private Model mModel; private Model mModel; private MultiSelectManager mSelectionManager; private MultiSelectManager mSelectionMgr; private Model.UpdateListener mModelUpdateListener = new ModelUpdateListener(); private Model.UpdateListener mModelUpdateListener = new ModelUpdateListener(); private ItemEventListener mItemEventListener; private UserInputHandler mInputHandler; private SelectionModeListener mSelectionModeListener; private SelectionModeListener mSelectionModeListener; private FocusManager mFocusManager; private FocusManager mFocusManager; Loading Loading @@ -240,7 +244,7 @@ public class DirectoryFragment extends Fragment @Override @Override public void onDestroyView() { public void onDestroyView() { mSelectionManager.clearSelection(); mSelectionMgr.clearSelection(); // Cancel any outstanding thumbnail requests // Cancel any outstanding thumbnail requests final int count = mRecView.getChildCount(); final int count = mRecView.getChildCount(); Loading Loading @@ -296,46 +300,49 @@ public class DirectoryFragment extends Fragment // TODO: instead of inserting the view into the constructor, extract listener-creation code // 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 // and set the listener on the view after the fact. Then the view doesn't need to be passed // into the selection manager. // into the selection manager. mSelectionManager = new MultiSelectManager( mSelectionMgr = new MultiSelectManager( mAdapter, mAdapter, state.allowMultiple state.allowMultiple ? MultiSelectManager.MODE_MULTIPLE ? MultiSelectManager.MODE_MULTIPLE : MultiSelectManager.MODE_SINGLE); : MultiSelectManager.MODE_SINGLE); GestureListener gestureListener = new GestureListener( // Make sure this is done after the RecyclerView is set up. mSelectionManager, mFocusManager = new FocusManager(context, mRecView, mModel); mRecView, mInputHandler = new UserInputHandler( mSelectionMgr, mFocusManager, new Function<MotionEvent, InputEvent>() { @Override public InputEvent apply(MotionEvent t) { return MotionInputEvent.obtain(t, mRecView); } }, this::getTarget, this::getTarget, this::onDoubleTap, this::canSelect, this::onRightClick); this::onRightClick, this::onActivate, (DocumentDetails ignored) -> { return onDeleteSelectedDocuments(); }); mGestureDetector = mGestureDetector = new ListeningGestureDetector(this.getContext(), mDragHelper, gestureListener); new ListeningGestureDetector(this.getContext(), mDragHelper, mInputHandler); mRecView.addOnItemTouchListener(mGestureDetector); mRecView.addOnItemTouchListener(mGestureDetector); mEmptyView.setOnTouchListener(mGestureDetector); mEmptyView.setOnTouchListener(mGestureDetector); if (state.allowMultiple) { if (state.allowMultiple) { mBandController = new BandController(mRecView, mAdapter, mSelectionManager); mBandController = new BandController(mRecView, mAdapter, mSelectionMgr); } } mSelectionModeListener = new SelectionModeListener(); mSelectionModeListener = new SelectionModeListener(); mSelectionManager.addCallback(mSelectionModeListener); mSelectionMgr.addCallback(mSelectionModeListener); mModel = new Model(); mModel = new Model(); mModel.addUpdateListener(mAdapter); mModel.addUpdateListener(mAdapter); mModel.addUpdateListener(mModelUpdateListener); mModel.addUpdateListener(mModelUpdateListener); // Make sure this is done after the RecyclerView is set up. mFocusManager = new FocusManager(context, mRecView, mModel); mItemEventListener = new ItemEventListener( mSelectionManager, mFocusManager, this::handleViewItem, this::deleteDocuments, this::canSelect); final BaseActivity activity = getBaseActivity(); final BaseActivity activity = getBaseActivity(); mTuner = activity.createFragmentTuner(); mTuner = activity.createFragmentTuner(); mMenuManager = activity.getMenuManager(); mMenuManager = activity.getMenuManager(); Loading @@ -351,7 +358,7 @@ public class DirectoryFragment extends Fragment } } public void retainState(RetainedState state) { public void retainState(RetainedState state) { state.selection = mSelectionManager.getSelection(new Selection()); state.selection = mSelectionMgr.getSelection(new Selection()); } } @Override @Override Loading Loading @@ -419,49 +426,37 @@ public class DirectoryFragment extends Fragment FileOperations.start(getContext(), operation, mFileOpCallback); FileOperations.start(getContext(), operation, mFileOpCallback); } } protected boolean onDoubleTap(MotionInputEvent event) { protected boolean onRightClick(InputEvent e) { if (event.isMouseEvent()) { String id = getModelId(event); if (id != null) { return handleViewItem(id); } } return false; } protected boolean onRightClick(MotionInputEvent e) { if (e.getItemPosition() != RecyclerView.NO_POSITION) { if (e.getItemPosition() != RecyclerView.NO_POSITION) { final DocumentHolder holder = getTarget(e); final DocumentHolder doc = getTarget(e); String modelId = getModelId(holder.itemView); if (!mSelectionMgr.getSelection().contains(doc.modelId)) { if (!mSelectionManager.getSelection().contains(modelId)) { mSelectionMgr.replaceSelection(Collections.singleton(doc.modelId)); mSelectionManager.clearSelection(); // Set selection on the one single item List<String> ids = Collections.singletonList(modelId); mSelectionManager.setItemsSelected(ids, true); } } // We are registering for context menu here so long-press doesn't trigger this // We are registering for context menu here so long-press doesn't trigger this // floating context menu, and then quickly unregister right afterwards // floating context menu, and then quickly unregister right afterwards registerForContextMenu(holder.itemView); registerForContextMenu(doc.itemView); mRecView.showContextMenuForChild(holder.itemView, mRecView.showContextMenuForChild(doc.itemView, e.getX() - holder.itemView.getLeft(), e.getY() - holder.itemView.getTop()); e.getX() - doc.itemView.getLeft(), e.getY() - doc.itemView.getTop()); unregisterForContextMenu(holder.itemView); unregisterForContextMenu(doc.itemView); return true; } } // If there was no corresponding item pos, that means user right-clicked on the blank // If there was no corresponding item pos, that means user right-clicked on the blank // pane // pane // We would want to show different options then, and not select any item // We would want to show different options then, and not select any item // The blank pane could be the recyclerView or the emptyView, so we need to register // The blank pane could be the recyclerView or the emptyView, so we need to register // according to whichever one is visible // according to whichever one is visible else if (mEmptyView.getVisibility() == View.VISIBLE) { if (mEmptyView.getVisibility() == View.VISIBLE) { registerForContextMenu(mEmptyView); registerForContextMenu(mEmptyView); mEmptyView.showContextMenu(e.getX(), e.getY()); mEmptyView.showContextMenu(e.getX(), e.getY()); unregisterForContextMenu(mEmptyView); unregisterForContextMenu(mEmptyView); return true; return true; } else { } registerForContextMenu(mRecView); registerForContextMenu(mRecView); mRecView.showContextMenu(e.getX(), e.getY()); mRecView.showContextMenu(e.getX(), e.getY()); unregisterForContextMenu(mRecView); unregisterForContextMenu(mRecView); } return true; return true; } } Loading @@ -478,7 +473,7 @@ public class DirectoryFragment extends Fragment if (mTuner.isDocumentEnabled(docMimeType, docFlags)) { if (mTuner.isDocumentEnabled(docMimeType, docFlags)) { final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor); final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor); getBaseActivity().onDocumentPicked(doc, mModel); getBaseActivity().onDocumentPicked(doc, mModel); mSelectionManager.clearSelection(); mSelectionMgr.clearSelection(); return true; return true; } } return false; return false; Loading Loading @@ -643,7 +638,7 @@ public class DirectoryFragment extends Fragment @Override @Override public void onSelectionChanged() { public void onSelectionChanged() { mSelectionManager.getSelection(mSelected); mSelectionMgr.getSelection(mSelected); if (mSelected.size() > 0) { if (mSelected.size() > 0) { if (DEBUG) Log.d(TAG, "Maybe starting action mode."); if (DEBUG) Log.d(TAG, "Maybe starting action mode."); if (mActionMode == null) { if (mActionMode == null) { Loading Loading @@ -673,7 +668,7 @@ public class DirectoryFragment extends Fragment if (DEBUG) Log.d(TAG, "Handling action mode destroyed."); if (DEBUG) Log.d(TAG, "Handling action mode destroyed."); mActionMode = null; mActionMode = null; // clear selection // clear selection mSelectionManager.clearSelection(); mSelectionMgr.clearSelection(); mSelected.clear(); mSelected.clear(); mDirectoryCount = 0; mDirectoryCount = 0; Loading Loading @@ -704,7 +699,7 @@ public class DirectoryFragment extends Fragment mRecView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); mRecView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); } } int size = mSelectionManager.getSelection().size(); int size = mSelectionMgr.getSelection().size(); mode.getMenuInflater().inflate(R.menu.mode_directory, menu); mode.getMenuInflater().inflate(R.menu.mode_directory, menu); mode.setTitle(TextUtils.formatSelectedCount(size)); mode.setTitle(TextUtils.formatSelectedCount(size)); Loading Loading @@ -752,7 +747,7 @@ public class DirectoryFragment extends Fragment @Override @Override public boolean canRename() { public boolean canRename() { return mNoRenameCount == 0 && mSelectionManager.getSelection().size() == 1; return mNoRenameCount == 0 && mSelectionMgr.getSelection().size() == 1; } } private void updateActionMenu() { private void updateActionMenu() { Loading @@ -768,7 +763,7 @@ public class DirectoryFragment extends Fragment } } private boolean handleMenuItemClick(MenuItem item) { private boolean handleMenuItemClick(MenuItem item) { Selection selection = mSelectionManager.getSelection(new Selection()); Selection selection = mSelectionMgr.getSelection(new Selection()); switch (item.getItemId()) { switch (item.getItemId()) { case R.id.menu_open: case R.id.menu_open: Loading Loading @@ -835,9 +830,9 @@ public class DirectoryFragment extends Fragment } } public final boolean onBackPressed() { public final boolean onBackPressed() { if (mSelectionManager.hasSelection()) { if (mSelectionMgr.hasSelection()) { if (DEBUG) Log.d(TAG, "Clearing selection on selection manager."); if (DEBUG) Log.d(TAG, "Clearing selection on selection manager."); mSelectionManager.clearSelection(); mSelectionMgr.clearSelection(); return true; return true; } } return false; return false; Loading Loading @@ -949,6 +944,29 @@ public class DirectoryFragment extends Fragment return message; return message; } } private boolean onDeleteSelectedDocuments() { if (mSelectionMgr.hasSelection()) { deleteDocuments(mSelectionMgr.getSelection(new Selection())); } return false; } private boolean onActivate(DocumentDetails doc) { // Toggle selection if we're in selection mode, othewise, view item. if (mSelectionMgr.hasSelection()) { mSelectionMgr.toggleSelection(doc.getModelId()); } else { handleViewItem(doc.getModelId()); } return true; } // private boolean onSelect(DocumentDetails doc) { // mSelectionMgr.toggleSelection(doc.getModelId()); // mSelectionMgr.setSelectionRangeBegin(doc.getAdapterPosition()); // return true; // } private void deleteDocuments(final Selection selected) { private void deleteDocuments(final Selection selected) { Metrics.logUserAction(getContext(), Metrics.USER_ACTION_DELETE); Metrics.logUserAction(getContext(), Metrics.USER_ACTION_DELETE); Loading Loading @@ -1100,7 +1118,7 @@ public class DirectoryFragment extends Fragment @Override @Override public void initDocumentHolder(DocumentHolder holder) { public void initDocumentHolder(DocumentHolder holder) { holder.addEventListener(mItemEventListener); holder.addKeyEventListener(mInputHandler); holder.itemView.setOnFocusChangeListener(mFocusManager); holder.itemView.setOnFocusChangeListener(mFocusManager); } } Loading Loading @@ -1186,11 +1204,11 @@ public class DirectoryFragment extends Fragment public void copySelectedToClipboard() { public void copySelectedToClipboard() { Metrics.logUserAction(getContext(), Metrics.USER_ACTION_COPY_CLIPBOARD); Metrics.logUserAction(getContext(), Metrics.USER_ACTION_COPY_CLIPBOARD); Selection selection = mSelectionManager.getSelection(new Selection()); Selection selection = mSelectionMgr.getSelection(new Selection()); if (selection.isEmpty()) { if (selection.isEmpty()) { return; return; } } mSelectionManager.clearSelection(); mSelectionMgr.clearSelection(); mClipper.clipDocumentsForCopy(mModel::getItemUri, selection); mClipper.clipDocumentsForCopy(mModel::getItemUri, selection); Loading @@ -1200,11 +1218,11 @@ public class DirectoryFragment extends Fragment public void cutSelectedToClipboard() { public void cutSelectedToClipboard() { Metrics.logUserAction(getContext(), Metrics.USER_ACTION_CUT_CLIPBOARD); Metrics.logUserAction(getContext(), Metrics.USER_ACTION_CUT_CLIPBOARD); Selection selection = mSelectionManager.getSelection(new Selection()); Selection selection = mSelectionMgr.getSelection(new Selection()); if (selection.isEmpty()) { if (selection.isEmpty()) { return; return; } } mSelectionManager.clearSelection(); mSelectionMgr.clearSelection(); mClipper.clipDocumentsForCut(mModel::getItemUri, selection, getDisplayState().stack.peek()); mClipper.clipDocumentsForCut(mModel::getItemUri, selection, getDisplayState().stack.peek()); Loading Loading @@ -1239,7 +1257,7 @@ public class DirectoryFragment extends Fragment } } // Only select things currently visible in the adapter. // Only select things currently visible in the adapter. boolean changed = mSelectionManager.setItemsSelected(enabled, true); boolean changed = mSelectionMgr.setItemsSelected(enabled, true); if (changed) { if (changed) { updateDisplayState(); updateDisplayState(); } } Loading Loading @@ -1277,7 +1295,7 @@ public class DirectoryFragment extends Fragment void dragStopped(boolean result) { void dragStopped(boolean result) { if (result) { if (result) { mSelectionManager.clearSelection(); mSelectionMgr.clearSelection(); } } } } Loading Loading @@ -1363,19 +1381,7 @@ public class DirectoryFragment extends Fragment } } } } /** private @Nullable DocumentHolder getTarget(InputEvent e) { * Gets the model ID for a given motion event (using the event position) */ private String getModelId(MotionInputEvent e) { RecyclerView.ViewHolder vh = getTarget(e); if (vh instanceof DocumentHolder) { return ((DocumentHolder) vh).modelId; } else { return null; } } private @Nullable DocumentHolder getTarget(MotionInputEvent e) { View childView = mRecView.findChildViewUnder(e.getX(), e.getY()); View childView = mRecView.findChildViewUnder(e.getX(), e.getY()); if (childView != null) { if (childView != null) { return (DocumentHolder) mRecView.getChildViewHolder(childView); return (DocumentHolder) mRecView.getChildViewHolder(childView); Loading Loading @@ -1423,7 +1429,7 @@ public class DirectoryFragment extends Fragment @Override @Override public boolean isSelected(String modelId) { public boolean isSelected(String modelId) { return mSelectionManager.getSelection().contains(modelId); return mSelectionMgr.getSelection().contains(modelId); } } private final class ModelUpdateListener implements Model.UpdateListener { private final class ModelUpdateListener implements Model.UpdateListener { Loading Loading @@ -1480,7 +1486,7 @@ public class DirectoryFragment extends Fragment private DocumentInfo getSingleSelectedDocument(Selection selection) { private DocumentInfo getSingleSelectedDocument(Selection selection) { assert (selection.size() == 1); assert (selection.size() == 1); final List<DocumentInfo> docs = mModel.getDocuments(mSelectionManager.getSelection()); final List<DocumentInfo> docs = mModel.getDocuments(mSelectionMgr.getSelection()); assert (docs.size() == 1); assert (docs.size() == 1); return docs.get(0); return docs.get(0); } } Loading @@ -1489,7 +1495,7 @@ public class DirectoryFragment extends Fragment new DragStartHelper.OnDragStartListener() { new DragStartHelper.OnDragStartListener() { @Override @Override public boolean onDragStart(View v, DragStartHelper helper) { public boolean onDragStart(View v, DragStartHelper helper) { Selection selection = mSelectionManager.getSelection(); Selection selection = mSelectionMgr.getSelection(); if (v == null) { if (v == null) { Log.d(TAG, "Ignoring drag event, null view"); Log.d(TAG, "Ignoring drag event, null view"); Loading Loading @@ -1532,6 +1538,10 @@ public class DirectoryFragment extends Fragment } } }; }; private boolean canSelect(DocumentDetails doc) { return canSelect(doc.getModelId()); } private boolean canSelect(String modelId) { private boolean canSelect(String modelId) { // TODO: Combine this method with onBeforeItemStateChange, as both of them are almost // TODO: Combine this method with onBeforeItemStateChange, as both of them are almost Loading Loading @@ -1662,7 +1672,7 @@ public class DirectoryFragment extends Fragment updateLayout(state.derivedMode); updateLayout(state.derivedMode); if (mRestoredSelection != null) { if (mRestoredSelection != null) { mSelectionManager.restoreSelection(mRestoredSelection); mSelectionMgr.restoreSelection(mRestoredSelection); // Note, we'll take care of cleaning up retained selection // Note, we'll take care of cleaning up retained selection // in the selection handler where we already have some // in the selection handler where we already have some // specialized code to handle when selection was restored. // specialized code to handle when selection was restored. Loading
packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java +39 −63 Original line number Original line Diff line number Diff line Loading @@ -24,28 +24,31 @@ import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView; import android.view.KeyEvent; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup; import com.android.documentsui.Events; import com.android.documentsui.Events.InputEvent; import com.android.documentsui.R; import com.android.documentsui.R; import com.android.documentsui.State; import com.android.documentsui.State; import com.android.documentsui.dirlist.UserInputHandler.DocumentDetails; public abstract class DocumentHolder public abstract class DocumentHolder extends RecyclerView.ViewHolder extends RecyclerView.ViewHolder implements View.OnKeyListener { implements View.OnKeyListener, DocumentDetails { static final float DISABLED_ALPHA = 0.3f; static final float DISABLED_ALPHA = 0.3f; @Deprecated // Public access is deprecated, use #getModelId. public @Nullable String modelId; public @Nullable String modelId; final Context mContext; final Context mContext; final @ColorInt int mDefaultBgColor; final @ColorInt int mDefaultBgColor; final @ColorInt int mSelectedBgColor; final @ColorInt int mSelectedBgColor; DocumentHolder.EventListener mEventListener; // See #addKeyEventListener for details on the need for this field. private View.OnKeyListener mKeyListener; KeyboardEventListener mKeyEventListener; private View mSelectionHotspot; private View mSelectionHotspot; Loading Loading @@ -74,6 +77,11 @@ public abstract class DocumentHolder */ */ public abstract void bind(Cursor cursor, String modelId, State state); public abstract void bind(Cursor cursor, String modelId, State state); @Override public String getModelId() { return modelId; } /** /** * Makes the associated item view appear selected. Note that this merely affects the appearance * Makes the associated item view appear selected. Note that this merely affects the appearance * of the view, it doesn't actually select the item. * of the view, it doesn't actually select the item. Loading Loading @@ -107,37 +115,25 @@ public abstract class DocumentHolder @Override @Override public boolean onKey(View v, int keyCode, KeyEvent event) { public boolean onKey(View v, int keyCode, KeyEvent event) { // Event listener should always be set. assert(mKeyEventListener != null); assert(mEventListener != null); return mKeyEventListener.onKey(this, keyCode, event); return mEventListener.onKey(this, keyCode, event); } public void addEventListener(DocumentHolder.EventListener listener) { // Just handle one for now; switch to a list if necessary. assert(mEventListener == null); mEventListener = listener; } } public void addOnKeyListener(View.OnKeyListener listener) { /** // Just handle one for now; switch to a list if necessary. * Installs a delegate to receive keyboard input events. This arrangement is necessitated assert(mKeyListener == null); * by the fact that a single listener cannot listen to all keyboard events mKeyListener = listener; * on RecyclerView (our parent view). Not sure why this is, but have been } * assured it is the case. * public boolean onSingleTapUp(MotionEvent event) { * <p>Ideally we'd not involve DocumentHolder in propagation of events like this. if (Events.isMouseEvent(event)) { */ // Mouse clicks select. public void addKeyEventListener(KeyboardEventListener listener) { // TODO: && input.isPrimaryButtonPressed(), but it is returning false. assert(mKeyEventListener == null); if (mEventListener != null) { mKeyEventListener = listener; return mEventListener.onSelect(this); } } else if (Events.isTouchEvent(event)) { // Touch events select if they occur in the selection hotspot, otherwise they activate. if (mEventListener == null) { return false; } } @Override public boolean isInSelectionHotspot(InputEvent event) { // Do everything in global coordinates - it makes things simpler. // Do everything in global coordinates - it makes things simpler. int[] coords = new int[2]; int[] coords = new int[2]; mSelectionHotspot.getLocationOnScreen(coords); mSelectionHotspot.getLocationOnScreen(coords); Loading @@ -145,13 +141,7 @@ public abstract class DocumentHolder coords[1] + mSelectionHotspot.getHeight()); coords[1] + mSelectionHotspot.getHeight()); // If the tap occurred within the icon rect, consider it a selection. // If the tap occurred within the icon rect, consider it a selection. if (rect.contains((int) event.getRawX(), (int) event.getRawY())) { return rect.contains((int) event.getRawX(), (int) event.getRawY()); return mEventListener.onSelect(this); } else { return mEventListener.onActivate(this); } } return false; } } static void setEnabledRecursive(View itemView, boolean enabled) { static void setEnabledRecursive(View itemView, boolean enabled) { Loading @@ -174,23 +164,9 @@ public abstract class DocumentHolder /** /** * Implement this in order to be able to respond to events coming from DocumentHolders. * Implement this in order to be able to respond to events coming from DocumentHolders. * TODO: Make this bubble up logic events rather than having imperative commands. */ */ interface EventListener { interface KeyboardEventListener { /** * Handles activation events on the document holder. * * @param doc The target DocumentHolder * @return Whether the event was handled. */ public boolean onActivate(DocumentHolder doc); /** * Handles selection events on the document holder. * * @param doc The target DocumentHolder * @return Whether the event was handled. */ public boolean onSelect(DocumentHolder doc); /** /** * Handles key events on the document holder. * Handles key events on the document holder. Loading
packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusHandler.java 0 → 100644 +51 −0 Original line number Original line Diff line number Diff line /* * 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 class that handles navigation and focus within the DirectoryFragment. */ interface FocusHandler extends View.OnFocusChangeListener { /** * Handles navigation (setting focus, adjusting selection if needed) arising from incoming key * events. * * @param doc The DocumentHolder receiving the key event. * @param keyCode * @param event * @return Whether the event was handled. */ boolean handleKey(DocumentHolder doc, int keyCode, KeyEvent event); @Override void onFocusChange(View v, boolean hasFocus); /** * Requests focus on the item that last had focus. Scrolls to that item if necessary. */ void restoreLastFocus(); /** * @return The adapter position of the last focused item. */ int getFocusPosition(); }