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

Commit bde20e15 authored by Steve McKay's avatar Steve McKay
Browse files

UserInputHandler cleanup.

Obtain DocumentDetails directly from Event...fixing stack trace.
Don't try to do stuff in UserIntputHandler on events w/o details.
Don't call TouchDelegate#onLongPress for mouse events.
Don't clear selection when clicking in mime type icon (w/o CTRL).

Bug: 31350922,31381545

Change-Id: Ibc4e5335cdb41569f9863d87f82dafca58659974
parent 3bc0f3f6
Loading
Loading
Loading
Loading
+35 −24
Original line number Diff line number Diff line
@@ -26,6 +26,11 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;

import com.android.documentsui.dirlist.DocumentDetails;
import com.android.documentsui.dirlist.DocumentHolder;

import javax.annotation.Nullable;

/**
 * Utility code for dealing with MotionEvents.
 */
@@ -142,19 +147,23 @@ public final class Events {

        /** Returns the adapter position of the item under the finger/cursor. */
        int getItemPosition();

        /** Returns the DocumentDetails for the item under the event, or null. */
        @Nullable DocumentDetails getDocumentDetails();
    }

    public static final class MotionInputEvent implements InputEvent {
        private static final String TAG = "MotionInputEvent";

        private static final int UNSET_POSITION = RecyclerView.NO_POSITION - 1;

        private static final Pools.SimplePool<MotionInputEvent> sPool = new Pools.SimplePool<>(1);

        private MotionEvent mEvent;
        interface PositionProvider {
            int get(MotionEvent e);
        }
        private @Nullable RecyclerView mRecView;

        private int mPosition;
        private int mPosition = UNSET_POSITION;
        private @Nullable DocumentDetails mDocDetails;

        private MotionInputEvent() {
            if (DEBUG) Log.i(TAG, "Created a new instance.");
@@ -167,25 +176,7 @@ public final class Events {
            instance = (instance != null ? instance : new MotionInputEvent());

            instance.mEvent = event;

            // Consider determining position lazily as an optimization.
            View child = view.findChildViewUnder(event.getX(), event.getY());
            instance.mPosition = (child != null)
                    ? view.getChildAdapterPosition(child)
                    : RecyclerView.NO_POSITION;

            return instance;
        }

        public static MotionInputEvent obtain(
                MotionEvent event, PositionProvider positionProvider) {
            Shared.checkMainLoop();

            MotionInputEvent instance = sPool.acquire();
            instance = (instance != null ? instance : new MotionInputEvent());

            instance.mEvent = event;
            instance.mPosition = positionProvider.get(event);
            instance.mRecView = view;

            return instance;
        }
@@ -194,7 +185,9 @@ public final class Events {
            Shared.checkMainLoop();

            mEvent = null;
            mPosition = -1;
            mRecView = null;
            mPosition = UNSET_POSITION;
            mDocDetails = null;

            boolean released = sPool.release(this);
            // This assert is used to guarantee we won't generate too many instances that can't be
@@ -282,9 +275,26 @@ public final class Events {

        @Override
        public int getItemPosition() {
            if (mPosition == UNSET_POSITION) {
                View child = mRecView.findChildViewUnder(mEvent.getX(), mEvent.getY());
                mPosition = (child != null)
                        ? mRecView.getChildAdapterPosition(child)
                        : RecyclerView.NO_POSITION;
            }
            return mPosition;
        }

        @Override
        public @Nullable DocumentDetails getDocumentDetails() {
            if (mDocDetails == null) {
                View childView = mRecView.findChildViewUnder(mEvent.getX(), mEvent.getY());
                mDocDetails = (childView != null)
                ? (DocumentHolder) mRecView.getChildViewHolder(childView)
                        : null;
            }
            return mDocDetails;
        }

        @Override
        public String toString() {
            return new StringBuilder()
@@ -298,6 +308,7 @@ public final class Events {
                    .append(" getOrigin=").append(getOrigin())
                    .append(" isOverItem=").append(isOverItem())
                    .append(" getItemPosition=").append(getItemPosition())
                    .append(" getDocumentDetails=").append(getDocumentDetails())
                    .append("}")
                    .toString();
        }
+6 −9
Original line number Diff line number Diff line
@@ -340,7 +340,6 @@ public class DirectoryFragment extends Fragment
                mSelectionMgr,
                mFocusManager,
                (MotionEvent t) -> MotionInputEvent.obtain(t, mRecView),
                this::getTarget,
                this::canSelect,
                this::onRightClick,
                (DocumentDetails doc) -> handleViewItem(doc.getModelId()), // activate handler
@@ -475,8 +474,8 @@ public class DirectoryFragment extends Fragment
    protected boolean onRightClick(InputEvent e) {
        if (e.getItemPosition() != RecyclerView.NO_POSITION) {
            final DocumentHolder doc = getTarget(e);
            if (!mSelectionMgr.getSelection().contains(doc.modelId)) {
                mSelectionMgr.replaceSelection(Collections.singleton(doc.modelId));
            if (!mSelectionMgr.getSelection().contains(doc.getModelId())) {
                mSelectionMgr.replaceSelection(Collections.singleton(doc.getModelId()));
            }

            // We are registering for context menu here so long-press doesn't trigger this
@@ -1419,11 +1418,9 @@ public class DirectoryFragment extends Fragment

    private @Nullable DocumentHolder getTarget(InputEvent e) {
        View childView = mRecView.findChildViewUnder(e.getX(), e.getY());
        if (childView != null) {
            return (DocumentHolder) mRecView.getChildViewHolder(childView);
        } else {
            return null;
        }
        return (childView != null)
                ? (DocumentHolder) mRecView.getChildViewHolder(childView)
                : null;
    }

    /**
@@ -1437,7 +1434,7 @@ public class DirectoryFragment extends Fragment
        if (itemView != null) {
            RecyclerView.ViewHolder vh = mRecView.getChildViewHolder(itemView);
            if (vh instanceof DocumentHolder) {
                return ((DocumentHolder) vh).modelId;
                return ((DocumentHolder) vh).getModelId();
            }
        }
        return null;
+1 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import com.android.documentsui.Events.InputEvent;
 * Interface providing a loose coupling between DocumentHolder.
 */
public interface DocumentDetails {
    boolean hasModelId();
    String getModelId();
    int getAdapterPosition();
    boolean isInSelectionHotspot(InputEvent event);
+7 −2
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.database.Cursor;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
@@ -39,8 +40,7 @@ public abstract class DocumentHolder

    static final float DISABLED_ALPHA = 0.3f;

    @Deprecated  // Public access is deprecated, use #getModelId.
    public @Nullable String modelId;
    protected @Nullable String modelId;

    final Context mContext;
    final @ColorInt int mDefaultBgColor;
@@ -76,6 +76,11 @@ public abstract class DocumentHolder
     */
    public abstract void bind(Cursor cursor, String modelId, State state);

    @Override
    public boolean hasModelId() {
        return !TextUtils.isEmpty(modelId);
    }

    @Override
    public String getModelId() {
        return modelId;
+65 −56
Original line number Diff line number Diff line
@@ -31,6 +31,8 @@ import com.android.documentsui.Events.InputEvent;
import java.util.function.Function;
import java.util.function.Predicate;

import javax.annotation.Nullable;

/**
 * Grand unified-ish gesture/event listener for items in the directory list.
 */
@@ -43,7 +45,6 @@ public final class UserInputHandler<T extends InputEvent>
    private final MultiSelectManager mSelectionMgr;
    private final FocusHandler mFocusHandler;
    private final Function<MotionEvent, T> mEventConverter;
    private final Function<T, DocumentDetails> mDocFinder;
    private final Predicate<DocumentDetails> mSelectable;
    private final EventHandler mRightClickHandler;
    private final DocumentHandler mActivateHandler;
@@ -58,7 +59,6 @@ public final class UserInputHandler<T extends InputEvent>
            MultiSelectManager selectionMgr,
            FocusHandler focusHandler,
            Function<MotionEvent, T> eventConverter,
            Function<T, DocumentDetails> docFinder,
            Predicate<DocumentDetails> selectable,
            EventHandler rightClickHandler,
            DocumentHandler activateHandler,
@@ -69,7 +69,6 @@ public final class UserInputHandler<T extends InputEvent>
        mSelectionMgr = selectionMgr;
        mFocusHandler = focusHandler;
        mEventConverter = eventConverter;
        mDocFinder = docFinder;
        mSelectable = selectable;
        mRightClickHandler = rightClickHandler;
        mActivateHandler = activateHandler;
@@ -164,9 +163,10 @@ public final class UserInputHandler<T extends InputEvent>
    void onLongPress(T event) {
        if (event.isMouseEvent()) {
            mMouseDelegate.onLongPress(event);
        }
        } else {
            mTouchDelegate.onLongPress(event);
        }
    }

    // Only events from RecyclerView are fed into UserInputHandler#onDown.
    // ListeningGestureDetector#onTouch directly calls this method to support context menu in empty
@@ -188,21 +188,24 @@ public final class UserInputHandler<T extends InputEvent>

    private boolean selectDocument(DocumentDetails doc) {
        assert(doc != null);
        assert(doc.hasModelId());
        mSelectionMgr.toggleSelection(doc.getModelId());
        mSelectionMgr.setSelectionRangeBegin(doc.getAdapterPosition());
        return true;
    }

    boolean isRangeExtension(T event) {
        return event.isShiftKeyDown() && mSelectionMgr.isRangeSelectionActive();
    private void extendSelectionRange(DocumentDetails doc) {
        mSelectionMgr.snapRangeSelection(doc.getAdapterPosition());
    }

    private void extendSelectionRange(T event) {
        mSelectionMgr.snapRangeSelection(event.getItemPosition());
    boolean isRangeExtension(T event) {
        return event.isShiftKeyDown() && mSelectionMgr.isRangeSelectionActive();
    }

    private boolean shouldClearSelection(T event, DocumentDetails doc) {
        return !event.isCtrlKeyDown() && !mSelectionMgr.getSelection().contains(doc.getModelId());
        return !event.isCtrlKeyDown()
                && !doc.isInSelectionHotspot(event)
                && !mSelectionMgr.getSelection().contains(doc.getModelId());
    }

    private final class TouchInputDelegate {
@@ -223,18 +226,21 @@ public final class UserInputHandler<T extends InputEvent>
                return false;
            }

            @Nullable DocumentDetails doc = event.getDocumentDetails();
            if (doc == null || !doc.hasModelId()) {
                Log.w(TAG, "Ignoring Single Tap Up. No document details associated w/ event.");
                return false;
            }

            if (mSelectionMgr.hasSelection()) {
                if (isRangeExtension(event)) {
                    mSelectionMgr.snapRangeSelection(event.getItemPosition());
                    extendSelectionRange(doc);
                } else {
                    selectDocument(mDocFinder.apply(event));
                    selectDocument(doc);
                }
                return true;
            }

            // Give the DocumentHolder a crack at the event.
            DocumentDetails doc = mDocFinder.apply(event);
            if (doc != null) {
            // Touch events select if they occur in the selection hotspot,
            // otherwise they activate.
            return doc.isInSelectionHotspot(event)
@@ -242,9 +248,6 @@ public final class UserInputHandler<T extends InputEvent>
                    : activateDocument(doc);
        }

            return false;
        }

        boolean onSingleTapConfirmed(T event) {
            return false;
        }
@@ -255,15 +258,21 @@ public final class UserInputHandler<T extends InputEvent>

        final void onLongPress(T event) {
            if (!event.isOverItem()) {
                if (DEBUG) Log.d(TAG, "Ignoring Long Press on non-item.");
                return;
            }

            @Nullable DocumentDetails doc = event.getDocumentDetails();
            if (doc == null || !doc.hasModelId()) {
                Log.w(TAG, "Ignoring Long Press. No document details associated w/ event.");
                return;
            }

            if (isRangeExtension(event)) {
                extendSelectionRange(event);
                extendSelectionRange(doc);
            } else {
                DocumentDetails doc = mDocFinder.apply(event);
                if (!mSelectionMgr.getSelection().contains(doc.getModelId())) {
                    selectDocument(mDocFinder.apply(event));
                    selectDocument(doc);
                    mGestureSelectHandler.apply(event);
                } else {
                    // We only initiate drag and drop on long press for touch to allow regular
@@ -305,15 +314,21 @@ public final class UserInputHandler<T extends InputEvent>
            }

            if (!event.isOverItem()) {
                if (DEBUG) Log.d(TAG, "Tap on non-item. Clearing selection.");
                mSelectionMgr.clearSelection();
                return false;
            }

            @Nullable DocumentDetails doc = event.getDocumentDetails();
            if (doc == null || !doc.hasModelId()) {
                Log.w(TAG, "Ignoring Single Tap Up. No document details associated w/ event.");
                return false;
            }

            if (mSelectionMgr.hasSelection()) {
                if (isRangeExtension(event)) {
                    extendSelectionRange(event);
                    extendSelectionRange(doc);
                } else {
                    DocumentDetails doc = mDocFinder.apply(event);
                    if (shouldClearSelection(event, doc)) {
                        mSelectionMgr.clearSelection();
                    }
@@ -323,21 +338,9 @@ public final class UserInputHandler<T extends InputEvent>
                return true;
            }

            // We'll toggle selection in onSingleTapConfirmed
            // This avoids flickering on/off action mode when an item is double clicked.
            if (!mSelectionMgr.hasSelection()) {
                return false;
            }

            DocumentDetails doc = mDocFinder.apply(event);
            if (doc == null) {
            return false;
        }

            mHandledTapUp = true;
            return selectDocument(doc);
        }

        boolean onSingleTapConfirmed(T event) {
            if (mHandledTapUp) {
                mHandledTapUp = false;
@@ -348,8 +351,14 @@ public final class UserInputHandler<T extends InputEvent>
                return false;  // should have been handled by onSingleTapUp.
            }

            DocumentDetails doc = mDocFinder.apply(event);
            if (doc == null) {
            if (!event.isOverItem()) {
                if (DEBUG) Log.d(TAG, "Ignoring Confirmed Tap on non-item.");
                return false;
            }

            @Nullable DocumentDetails doc = event.getDocumentDetails();
            if (doc == null || !doc.hasModelId()) {
                Log.w(TAG, "Ignoring Confirmed Tap. No document details associated w/ event.");
                return false;
            }

@@ -358,27 +367,27 @@ public final class UserInputHandler<T extends InputEvent>

        boolean onDoubleTap(T event) {
            mHandledTapUp = false;
            DocumentDetails doc = mDocFinder.apply(event);
            if (doc != null) {

            if (!event.isOverItem()) {
                if (DEBUG) Log.d(TAG, "Ignoring DoubleTap on non-item.");
                return false;
            }

            @Nullable DocumentDetails doc = event.getDocumentDetails();
            if (doc == null || !doc.hasModelId()) {
                Log.w(TAG, "Ignoring Double Tap. No document details associated w/ event.");
                return false;
            }

            return mSelectionMgr.hasSelection()
                    ? selectDocument(doc)
                    : activateDocument(doc);
        }
            return false;
        }

        final void onLongPress(T event) {
            if (!event.isOverItem()) {
            return;
        }

            if (isRangeExtension(event)) {
                extendSelectionRange(event);
            } else {
                selectDocument(mDocFinder.apply(event));
            }
        }

        private boolean onRightClick(T event) {
            return mRightClickHandler.apply(event);
        }
Loading