Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 21143951 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Consolidate user input handling in single class."

parents 8c61df67 74c28770
Loading
Loading
Loading
Loading
+24 −2
Original line number Original line Diff line number Diff line
@@ -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();
@@ -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();
@@ -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);
@@ -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);
@@ -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;
+5 −0
Original line number Original line Diff line number Diff line
@@ -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.
+92 −82
Original line number Original line Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;
@@ -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;


@@ -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;


@@ -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();
@@ -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();
@@ -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
@@ -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;
    }
    }


@@ -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;
@@ -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) {
@@ -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;
@@ -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));


@@ -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() {
@@ -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:
@@ -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;
@@ -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);


@@ -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);
    }
    }


@@ -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);


@@ -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());


@@ -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();
        }
        }
@@ -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();
        }
        }
    }
    }


@@ -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);
@@ -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 {
@@ -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);
    }
    }
@@ -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");
@@ -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
@@ -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.
+39 −63
Original line number Original line Diff line number Diff line
@@ -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;




@@ -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.
@@ -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);
@@ -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) {
@@ -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.
+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