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

Commit 1597e94c authored by Steve McKay's avatar Steve McKay
Browse files

Right click selection fiddling into input handler.

Also, went down the rathole on TestEvent and TestInputEvent.
    Collapsed the two into one and updated TestEvent to use
    attrs from MotionEvent.

Change-Id: I6d652bc6a833bf7e24e92ecebb05d6b0f17dab47
parent cbe7db1f
Loading
Loading
Loading
Loading
+16 −1
Original line number Diff line number Diff line
@@ -142,9 +142,16 @@ public final class Events {
        float getRawX();
        float getRawY();

        /** Returns true if the there is an item under the finger/cursor. */
        /** Returns true if there is an item under the finger/cursor. */
        boolean isOverItem();

        /**
         * Returns true if there is a model backed item under the finger/cursor.
         * Resulting calls on the event instance should never return a null
         * DocumentDetails and DocumentDetails#hasModelId should always return true
         */
        boolean isOverModelItem();

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

@@ -273,6 +280,11 @@ public final class Events {
            return getItemPosition() != RecyclerView.NO_POSITION;
        }

        @Override
        public boolean isOverModelItem() {
            return isOverItem() && getDocumentDetails().hasModelId();
        }

        @Override
        public int getItemPosition() {
            if (mPosition == UNSET_POSITION) {
@@ -292,6 +304,9 @@ public final class Events {
                ? (DocumentHolder) mRecView.getChildViewHolder(childView)
                        : null;
            }
            if (isOverItem()) {
                assert(mDocDetails != null);
            }
            return mDocDetails;
        }

+10 −29
Original line number Diff line number Diff line
@@ -107,7 +107,6 @@ import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

@@ -472,37 +471,26 @@ 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.getModelId())) {
                mSelectionMgr.replaceSelection(Collections.singleton(doc.getModelId()));
                mSelectionMgr.setSelectionRangeBegin(doc.getAdapterPosition());
            }
        if (e.isOverModelItem()) {
            DocumentHolder doc = (DocumentHolder) e.getDocumentDetails();

            // We are registering for context menu here so long-press doesn't trigger this
            // floating context menu, and then quickly unregister right afterwards
            registerForContextMenu(doc.itemView);
            mRecView.showContextMenuForChild(doc.itemView,
            mRecView.showContextMenuForChild(
                    doc.itemView,
                    e.getX() - doc.itemView.getLeft(), e.getY() - doc.itemView.getTop());
            unregisterForContextMenu(doc.itemView);
            return true;
        }

        // If there was no corresponding item pos, that means user right-clicked on the blank
        // pane
        // 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
        // according to whichever one is visible
        if (mEmptyView.getVisibility() == View.VISIBLE) {
            registerForContextMenu(mEmptyView);
            mEmptyView.showContextMenu(e.getX(), e.getY());
            unregisterForContextMenu(mEmptyView);
            return true;
        }
        View v = (mEmptyView.getVisibility() == View.VISIBLE)
                ? mEmptyView : mRecView;

        registerForContextMenu(v);
        v.showContextMenu(e.getX(), e.getY());
        unregisterForContextMenu(v);

        registerForContextMenu(mRecView);
        mRecView.showContextMenu(e.getX(), e.getY());
        unregisterForContextMenu(mRecView);
        return true;
    }

@@ -1428,13 +1416,6 @@ public class DirectoryFragment extends Fragment
        }
    }

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

    /**
     * Gets the model ID for a given RecyclerView item.
     * @param view A View that is a document item view, or a child of a document item view.
+43 −34
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import com.android.documentsui.Events;
import com.android.documentsui.Events.EventHandler;
import com.android.documentsui.Events.InputEvent;

import java.util.Collections;
import java.util.function.Function;
import java.util.function.Predicate;

@@ -208,30 +209,29 @@ public final class UserInputHandler<T extends InputEvent>
                && !mSelectionMgr.getSelection().contains(doc.getModelId());
    }

    private static final String TTAG = "TouchInputDelegate";
    private final class TouchInputDelegate {

        boolean onDown(T event) {
            if (DEBUG) Log.v(TTAG, "Delegated onDown event.");
            return false;
        }

        // Don't consume so the RecyclerView will get the event and will get touch-based scrolling
        boolean onScroll(T event) {
            if (DEBUG) Log.v(TTAG, "Delegated onScroll event.");
            return false;
        }

        boolean onSingleTapUp(T event) {
            if (!event.isOverItem()) {
                if (DEBUG) Log.d(TAG, "Tap on non-item. Clearing selection.");
            if (DEBUG) Log.v(TTAG, "Delegated onSingleTapUp event.");
            if (!event.isOverModelItem()) {
                if (DEBUG) Log.d(TTAG, "Tap not associated w/ model 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;
            }

            DocumentDetails doc = event.getDocumentDetails();
            if (mSelectionMgr.hasSelection()) {
                if (isRangeExtension(event)) {
                    extendSelectionRange(doc);
@@ -249,25 +249,23 @@ public final class UserInputHandler<T extends InputEvent>
        }

        boolean onSingleTapConfirmed(T event) {
            if (DEBUG) Log.v(TTAG, "Delegated onSingleTapConfirmed event.");
            return false;
        }

        boolean onDoubleTap(T event) {
            if (DEBUG) Log.v(TTAG, "Delegated onDoubleTap event.");
            return false;
        }

        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.");
            if (DEBUG) Log.v(TTAG, "Delegated onLongPress event.");
            if (!event.isOverModelItem()) {
                if (DEBUG) Log.d(TTAG, "Ignoring LongPress on non-model-backed item.");
                return;
            }

            DocumentDetails doc = event.getDocumentDetails();
            if (isRangeExtension(event)) {
                extendSelectionRange(doc);
            } else {
@@ -283,6 +281,7 @@ public final class UserInputHandler<T extends InputEvent>
        }
    }

    private static final String MTAG = "MouseInputDelegate";
    private final class MouseInputDelegate {
        // The event has been handled in onSingleTapUp
        private boolean mHandledTapUp;
@@ -290,6 +289,7 @@ public final class UserInputHandler<T extends InputEvent>
        private boolean mHandledOnDown;

        boolean onDown(T event) {
            if (DEBUG) Log.v(MTAG, "Delegated onDown event.");
            if (event.isSecondaryButtonPressed()) {
                mHandledOnDown = true;
                return onRightClick(event);
@@ -300,31 +300,29 @@ public final class UserInputHandler<T extends InputEvent>

        // Don't scroll content window in response to mouse drag
        boolean onScroll(T event) {
            if (DEBUG) Log.v(MTAG, "Delegated onScroll event.");
            return true;
        }

        boolean onSingleTapUp(T event) {
            if (DEBUG) Log.v(MTAG, "Delegated onSingleTapUp event.");

            // See b/27377794. Since we don't get a button state back from UP events, we have to
            // explicitly save this state to know whether something was previously handled by
            // DOWN events or not.
            if (mHandledOnDown) {
                if (DEBUG) Log.v(MTAG, "Ignoring onSingleTapUp, previously handled in onDown.");
                mHandledOnDown = false;
                return false;
            }

            if (!event.isOverItem()) {
                if (DEBUG) Log.d(TAG, "Tap on non-item. Clearing selection.");
            if (!event.isOverModelItem()) {
                if (DEBUG) Log.d(MTAG, "Tap not associated w/ model 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;
            }

            DocumentDetails doc = event.getDocumentDetails();
            if (mSelectionMgr.hasSelection()) {
                if (isRangeExtension(event)) {
                    extendSelectionRange(doc);
@@ -342,7 +340,9 @@ public final class UserInputHandler<T extends InputEvent>
        }

        boolean onSingleTapConfirmed(T event) {
            if (DEBUG) Log.v(MTAG, "Delegated onSingleTapConfirmed event.");
            if (mHandledTapUp) {
                if (DEBUG) Log.v(MTAG, "Ignoring onSingleTapConfirmed, previously handled in onSingleTapUp.");
                mHandledTapUp = false;
                return false;
            }
@@ -352,13 +352,13 @@ public final class UserInputHandler<T extends InputEvent>
            }

            if (!event.isOverItem()) {
                if (DEBUG) Log.d(TAG, "Ignoring Confirmed Tap on non-item.");
                if (DEBUG) Log.d(MTAG, "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.");
                Log.w(MTAG, "Ignoring Confirmed Tap. No document details associated w/ event.");
                return false;
            }

@@ -366,29 +366,38 @@ public final class UserInputHandler<T extends InputEvent>
        }

        boolean onDoubleTap(T event) {
            if (DEBUG) Log.v(MTAG, "Delegated onDoubleTap event.");
            mHandledTapUp = false;

            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.");
            if (!event.isOverModelItem()) {
                if (DEBUG) Log.d(MTAG, "Ignoring DoubleTap on non-model-backed item.");
                return false;
            }

            DocumentDetails doc = event.getDocumentDetails();
            return mSelectionMgr.hasSelection()
                    ? selectDocument(doc)
                    : activateDocument(doc);
        }

        final void onLongPress(T event) {
            if (DEBUG) Log.v(MTAG, "Delegated onLongPress event.");
            return;
        }

        private boolean onRightClick(T event) {
            if (DEBUG) Log.v(MTAG, "Delegated onRightClick event.");
            if (event.isOverModelItem()) {
                DocumentDetails doc = event.getDocumentDetails();
                if (!mSelectionMgr.getSelection().contains(doc.getModelId())) {
                    mSelectionMgr.replaceSelection(Collections.singleton(doc.getModelId()));
                    mSelectionMgr.setSelectionRangeBegin(doc.getAdapterPosition());
                }
            }

            // We always delegate final handling of the event,
            // since the handler might want to show a context menu
            // in an empty area or some other weirdo view.
            return mRightClickHandler.apply(event);
        }
    }
+0 −141
Original line number Diff line number Diff line
package com.android.documentsui;

import android.graphics.Point;
import android.support.v7.widget.RecyclerView;

import com.android.documentsui.dirlist.DocumentDetails;

public class TestInputEvent implements Events.InputEvent {

    public boolean mouseEvent;
    public boolean primaryButtonPressed;
    public boolean secondaryButtonPressed;
    public boolean shiftKeyDow;
    public boolean ctrlKeyDow;
    public boolean actionDown;
    public boolean actionUp;
    public boolean actionMove;
    public Point location;
    public Point rawLocation;
    public int position = Integer.MIN_VALUE;
    public DocumentDetails details;

    public TestInputEvent() {}

    public TestInputEvent(int position) {
        this.position = position;
    }

    @Override
    public boolean isMouseEvent() {
        return mouseEvent;
    }

    @Override
    public boolean isPrimaryButtonPressed() {
        return primaryButtonPressed;
    }

    @Override
    public boolean isSecondaryButtonPressed() {
        return secondaryButtonPressed;
    }

    @Override
    public boolean isShiftKeyDown() {
        return shiftKeyDow;
    }

    @Override
    public boolean isCtrlKeyDown() {
        return ctrlKeyDow;
    }

    @Override
    public boolean isActionDown() {
        return actionDown;
    }

    @Override
    public boolean isActionUp() {
        return actionUp;
    }

    @Override
    public boolean isActionMove() {
        return actionMove;
    }

    @Override
    public Point getOrigin() {
        return location;
    }

    @Override
    public float getX() {
        return location.x;
    }

    @Override
    public float getY() {
        return location.y;
    }

    @Override
    public float getRawX() {
        return rawLocation.x;
    }

    @Override
    public float getRawY() {
        return rawLocation.y;
    }

    @Override
    public boolean isOverItem() {
        return position != Integer.MIN_VALUE && position != RecyclerView.NO_POSITION;
    }

    @Override
    public int getItemPosition() {
        return position;
    }

    @Override
    public DocumentDetails getDocumentDetails() {
        return details;
    }

    @Override
    public void close() {}

    public static TestInputEvent tap(int position) {
        return new TestInputEvent(position);
    }

    public static TestInputEvent shiftTap(int position) {
        TestInputEvent e = new TestInputEvent(position);
        e.shiftKeyDow = true;
        return e;
    }

    public static TestInputEvent click(int position) {
        TestInputEvent e = new TestInputEvent(position);
        e.mouseEvent = true;
        return e;
    }

    public static TestInputEvent rightClick(int position) {
        TestInputEvent e = new TestInputEvent(position);
        e.mouseEvent = true;
        e.secondaryButtonPressed = true;
        return e;
    }

    public static TestInputEvent shiftClick(int position) {
        TestInputEvent e = new TestInputEvent(position);
        e.mouseEvent = true;
        e.shiftKeyDow = true;
        return e;
    }
}
+32 −36
Original line number Diff line number Diff line
@@ -17,15 +17,15 @@
package com.android.documentsui.dirlist;

import android.content.ClipData;
import android.graphics.Point;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.MotionEvent;
import android.view.View;

import com.android.documentsui.Events.InputEvent;
import com.android.documentsui.State;
import com.android.documentsui.TestInputEvent;
import com.android.documentsui.dirlist.MultiSelectManager.Selection;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.testing.TestEvent;
import com.android.documentsui.testing.Views;

import java.util.ArrayList;
@@ -33,11 +33,11 @@ import java.util.ArrayList;
@SmallTest
public class DragStartListenerTest extends AndroidTestCase {

    private DragStartListener listener;
    private TestInputEvent event;
    private DragStartListener mListener;
    private TestEvent.Builder mEvent;
    private MultiSelectManager mMultiSelectManager;
    private String viewModelId;
    private boolean dragStarted;
    private String mViewModelId;
    private boolean mDragStarted;

    @Override
    public void setUp() throws Exception {
@@ -46,7 +46,7 @@ public class DragStartListenerTest extends AndroidTestCase {
                new TestDocumentsAdapter(new ArrayList<String>()),
                MultiSelectManager.MODE_MULTIPLE);

        listener = new DragStartListener.ActiveListener(
        mListener = new DragStartListener.ActiveListener(
                new State(),
                mMultiSelectManager,
                // view finder
@@ -55,7 +55,7 @@ public class DragStartListenerTest extends AndroidTestCase {
                },
                // model id finder
                (View view) -> {
                    return viewModelId;
                    return mViewModelId;
                },
                // docInfo Converter
                (Selection selection) -> {
@@ -78,57 +78,53 @@ public class DragStartListenerTest extends AndroidTestCase {
                    Object localState,
                    int flags) {

                dragStarted = true;
                mDragStarted = true;
            }
        };

        dragStarted = false;
        viewModelId = "1234";

        event = new TestInputEvent();
        event.mouseEvent = true;
        event.primaryButtonPressed = true;
        event.actionMove = true;
        event.position = 1;
        event.location = new Point();
        event.location.x = 0;
        event.location.y = 0;
        mDragStarted = false;
        mViewModelId = "1234";

        mEvent = TestEvent.builder()
                .action(MotionEvent.ACTION_MOVE)
                .mouse()
                .at(1)
                .primary();
    }

    public void testDragStarted_OnMouseMove() {
        assertTrue(listener.onMouseDragEvent(event));
        assertTrue(dragStarted);
        assertTrue(mListener.onMouseDragEvent(mEvent.build()));
        assertTrue(mDragStarted);
    }

    public void testDragNotStarted_NonModelBackedView() {
        viewModelId = null;
        assertFalse(listener.onMouseDragEvent(event));
        assertFalse(dragStarted);
        mViewModelId = null;
        assertFalse(mListener.onMouseDragEvent(mEvent.build()));
        assertFalse(mDragStarted);
    }

    public void testThrows_OnNonMouseMove() {
        event.mouseEvent = false;
        assertThrows(event);
        TestEvent e = TestEvent.builder()
                .at(1)
                .action(MotionEvent.ACTION_MOVE).build();
        assertThrows(e);
    }

    public void testThrows_OnNonPrimaryMove() {
        event.primaryButtonPressed = false;
        assertThrows(event);
        assertThrows(mEvent.pressButton(MotionEvent.BUTTON_PRIMARY).build());
    }

    public void testThrows_OnNonMove() {
        event.actionMove = false;
        assertThrows(event);
        assertThrows(mEvent.action(MotionEvent.ACTION_UP).build());
    }

    public void testThrows_WhenNotOnItem() {
        event.position = -1;
        assertThrows(event);
        assertThrows(mEvent.at(-1).build());
    }

    private void assertThrows(TestInputEvent e) {
    private void assertThrows(InputEvent e) {
        try {
            assertFalse(listener.onMouseDragEvent(e));
            assertFalse(mListener.onMouseDragEvent(e));
            fail();
        } catch (AssertionError expected) {}
    }
Loading