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

Commit 07be125b authored by Steve McKay's avatar Steve McKay
Browse files

Decompose ListeningGestureDetector.

The class was a GestureDetector subclass responsible for
- receiving MotionEvents from Recycler view
- passing them off to mouse/touch specific code (that in turn
    where relaying events to band, gesture, and drag helpers)
- then finally delivering events to parent gesture detector.

This change decomposes this arrangement into:

A dedicated TouchEventRouter that is reponsible for:
- delivery of events to injected mouse/touch deligates.
- delivery of events to an injected gesture detector.

Migrate Band/Gesture specific logic into the respective helper classes.

Renamed InputEventDispatcher to GestureRouter to make role
more immediately obvious.
I can't seem to convince git that it's a rename, though.

Bug: 64847011
Test: Passing.
Change-Id: Icf671cb4ca44a0aff11bb3547342de0063b2f403
parent 1fdd34b1
Loading
Loading
Loading
Loading
+23 −18
Original line number Diff line number Diff line
@@ -50,6 +50,7 @@ import android.support.v7.widget.RecyclerView.ViewHolder;
import android.util.Log;
import android.util.SparseArray;
import android.view.ContextMenu;
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.MenuItem;
@@ -93,11 +94,13 @@ import com.android.documentsui.selection.addons.BandSelectionHelper;
import com.android.documentsui.selection.addons.ContentLock;
import com.android.documentsui.selection.addons.DefaultBandHost;
import com.android.documentsui.selection.addons.DefaultBandPredicate;
import com.android.documentsui.selection.addons.GestureRouter;
import com.android.documentsui.selection.addons.GestureSelectionHelper;
import com.android.documentsui.selection.addons.InputEventDispatcher;
import com.android.documentsui.selection.addons.ItemDetailsLookup;
import com.android.documentsui.selection.addons.KeyInputHandler;
import com.android.documentsui.selection.addons.MotionInputHandler;
import com.android.documentsui.selection.addons.MouseInputHandler;
import com.android.documentsui.selection.addons.TouchEventRouter;
import com.android.documentsui.selection.addons.TouchInputHandler;
import com.android.documentsui.services.FileOperation;
import com.android.documentsui.services.FileOperationService;
@@ -161,7 +164,7 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On

    private ItemDetailsLookup mDetailsLookup;
    private SelectionMetadata mSelectionMetadata;
    private InputEventDispatcher mInputHandler;
    private GestureRouter<MotionInputHandler> mGestureRouter;
    private KeyInputHandler mKeyListener;
    private @Nullable BandSelectionHelper mBandSelector;
    private @Nullable DragHoverListener mDragHoverListener;
@@ -378,14 +381,13 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On
                mRecView,
                mState);

        TouchInputHandler touchDelegate =
                handlers.createTouchHandler(gestureSel, dragStartListener);

        MouseInputHandler mouseDelegate = handlers.createMouseHandler(this::onContextMenuClick);

        mInputHandler = new InputEventDispatcher(touchDelegate);  // default handler.
        mInputHandler.register(MotionEvent.TOOL_TYPE_MOUSE, mouseDelegate);
        MouseInputHandler mouseHandler = handlers.createMouseHandler(this::onContextMenuClick);
        TouchInputHandler touchHandler = handlers.createTouchHandler(gestureSel, dragStartListener);
        mGestureRouter = new GestureRouter<>(touchHandler);
        mGestureRouter.register(MotionEvent.TOOL_TYPE_MOUSE, mouseHandler);

        // This little guy gets added to each Holder, so that we can be notified of key events
        // on RecyclerView items.
        mKeyListener = handlers.createKeyHandler();

        if (Build.IS_DEBUGGABLE) {
@@ -396,15 +398,19 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On
        new RefreshHelper(mRefreshLayout::setEnabled)
                .attach(mRecView);

        ListeningGestureDetector recListener = new ListeningGestureDetector(
                this.getContext(),
        GestureDetector gestureDetector = new GestureDetector(getContext(), mGestureRouter);

        TouchEventRouter eventRouter =
                new TouchEventRouter(gestureDetector, gestureSel.getTouchListener());

        eventRouter.register(
                MotionEvent.TOOL_TYPE_MOUSE,
                new MouseDragEventInterceptor(
                        mDetailsLookup,
                        dragStartListener::onMouseDragEvent,
                gestureSel,
                mInputHandler,
                mBandSelector);
                        mBandSelector != null ? mBandSelector.getTouchListener() : null));

        recListener.attach(mRecView);
        mRecView.addOnItemTouchListener(eventRouter);

        mActionModeController = mInjector.getActionModeController(
                mSelectionMetadata,
@@ -431,7 +437,6 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On
        mActions.loadDocumentsForCurrentStack();
    }


    @Override
    public void onStart() {
        super.onStart();
+80 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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 static com.google.common.base.Preconditions.checkArgument;

import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.OnItemTouchListener;
import android.view.MotionEvent;

import com.android.documentsui.base.EventHandler;
import com.android.documentsui.base.Events;
import com.android.documentsui.selection.addons.BandSelectionHelper;
import com.android.documentsui.selection.addons.ItemDetailsLookup;
import com.android.documentsui.selection.addons.TouchEventRouter;

/**
 * OnItemTouchListener that helps enable support for drag/drop functionality
 * in DirectoryFragment.
 *
 * <p>This class takes an OnItemTouchListener that is presumably the
 * one provided by {@link BandSelectionHelper#getListener()}, and
 * is used in conjunction with the {@link TouchEventRouter}.
 *
 * <p>See DirectoryFragment for more details on how this is glued
 * into DocumetnsUI event handling to make the magic happen.
 */
class MouseDragEventInterceptor implements OnItemTouchListener {

    private final ItemDetailsLookup mEventDetailsLookup;
    private final EventHandler<MotionEvent> mMouseDragListener;
    private final @Nullable OnItemTouchListener mDelegate;

    public MouseDragEventInterceptor(
            ItemDetailsLookup eventDetailsLookup,
            EventHandler<MotionEvent> mouseDragListener,
            @Nullable OnItemTouchListener delegate) {

        checkArgument(eventDetailsLookup != null);
        checkArgument(mouseDragListener != null);

        mEventDetailsLookup = eventDetailsLookup;
        mMouseDragListener = mouseDragListener;
        mDelegate = delegate;
    }

    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        if (Events.isMouseDragEvent(e) && mEventDetailsLookup.inItemDragRegion(e)) {
            return mMouseDragListener.accept(e);
        } else if (mDelegate != null) {
            return mDelegate.onInterceptTouchEvent(rv, e);
        }
        return false;
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {
        if (mDelegate != null) {
            mDelegate.onTouchEvent(rv, e);
        }
    }

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}
}
 No newline at end of file
+34 −2
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.OnItemTouchListener;
import android.support.v7.widget.RecyclerView.OnScrollListener;
import android.util.Log;
import android.view.MotionEvent;
@@ -177,7 +178,7 @@ public class BandSelectionHelper {
        return mModel != null;
    }

    public boolean onInterceptTouchEvent(MotionEvent e) {
    private boolean onInterceptTouchEvent(MotionEvent e) {
        if (shouldStart(e)) {
            if (!MotionEvents.isCtrlKeyPressed(e)) {
                mSelectionHelper.clearSelection();
@@ -254,7 +255,7 @@ public class BandSelectionHelper {
     * Processes a MotionEvent by starting, ending, or resizing the band select overlay.
     * @param input
     */
    public void onTouchEvent(MotionEvent e) {
    private void onTouchEvent(MotionEvent e) {
        assert MotionEvents.isMouseEvent(e);

        if (shouldStop(e)) {
@@ -355,6 +356,37 @@ public class BandSelectionHelper {
        resizeBandSelectRectangle();
    }

    public OnItemTouchListener getTouchListener() {
        return new EventPreprocessor(this);
    }

    private static class EventPreprocessor implements OnItemTouchListener {

        private final BandSelectionHelper mBandController;

        public EventPreprocessor(BandSelectionHelper bandController) {
            checkArgument(bandController != null);

            mBandController = bandController;
        }

        @Override
        public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
            if (mBandController.shouldStart(e) || mBandController.shouldStop(e)) {
                return mBandController.onInterceptTouchEvent(e);
            }
            return false;
        }

        @Override
        public void onTouchEvent(RecyclerView rv, MotionEvent e) {
            mBandController.onTouchEvent(e);
        }

        @Override
        public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}
    }

    /**
     * Provides functionality for BandController. Exists primarily to tests that are
     * fully isolated from RecyclerView.
+94 −0
Original line number Diff line number Diff line
@@ -16,8 +16,6 @@

package com.android.documentsui.selection.addons;

import static com.google.common.base.Preconditions.checkArgument;

import android.support.annotation.Nullable;
import android.view.GestureDetector.OnDoubleTapListener;
import android.view.GestureDetector.OnGestureListener;
@@ -28,85 +26,69 @@ import android.view.MotionEvent;
 * Gesture event dispatcher. Dispatches gesture events to respective
 * input type specific handlers.
 */
public final class InputEventDispatcher implements OnGestureListener, OnDoubleTapListener {

    // Currently there are four known input types. ERASER is the last one, so has the
    // highest value. UNKNOWN is zero, so we add one. This allows delegates to be
    // registered by type, and avoid the auto-boxing that would be necessary were we
    // to store delegates in a Map<Integer, Delegate>.
    private static final int sNumInputTypes = MotionEvent.TOOL_TYPE_ERASER + 1;
    private final Delegate[] mDelegates = new Delegate[sNumInputTypes];
public final class GestureRouter<T extends OnGestureListener & OnDoubleTapListener>
        implements OnGestureListener, OnDoubleTapListener {

    private final Delegate mDefaultDelegate;
    private final ToolHandlerRegistry<T> mDelegates;

    public InputEventDispatcher(Delegate defaultDelegate) {
        checkArgument(defaultDelegate != null);
        mDefaultDelegate = defaultDelegate;
    public GestureRouter(T defaultDelegate) {
        mDelegates = new ToolHandlerRegistry<>(defaultDelegate);
    }

    public InputEventDispatcher() {
        mDefaultDelegate = new DummyDelegate();
    public GestureRouter() {
        this((T) new SimpleOnGestureListener());
    }

    /**
     * @param toolType
     * @param delegate the delegate, or null to unregister.
     */
    public void register(int toolType, @Nullable Delegate delegate) {
        checkArgument(toolType >= 0 && toolType <= MotionEvent.TOOL_TYPE_ERASER);
        mDelegates[toolType] = delegate;
    }

    private Delegate getDelegate(MotionEvent e) {
        Delegate d = mDelegates[e.getToolType(0)];
        return d != null ? d : mDefaultDelegate;
    public void register(int toolType, @Nullable T delegate) {
        mDelegates.set(toolType, delegate);
    }

    @Override
    public boolean onSingleTapConfirmed(MotionEvent e) {
        return getDelegate(e).onSingleTapConfirmed(e);
        return mDelegates.get(e).onSingleTapConfirmed(e);
    }

    @Override
    public boolean onDoubleTap(MotionEvent e) {
        return getDelegate(e).onDoubleTap(e);
        return mDelegates.get(e).onDoubleTap(e);
    }

    @Override
    public boolean onDoubleTapEvent(MotionEvent e) {
        return getDelegate(e).onDoubleTapEvent(e);
        return mDelegates.get(e).onDoubleTapEvent(e);
    }

    @Override
    public boolean onDown(MotionEvent e) {
        return getDelegate(e).onDown(e);
        return mDelegates.get(e).onDown(e);
    }

    @Override
    public void onShowPress(MotionEvent e) {
        getDelegate(e).onShowPress(e);
        mDelegates.get(e).onShowPress(e);
    }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        return getDelegate(e).onSingleTapUp(e);
        return mDelegates.get(e).onSingleTapUp(e);
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        return getDelegate(e2).onScroll(e1, e2, distanceX, distanceY);
        return mDelegates.get(e2).onScroll(e1, e2, distanceX, distanceY);
    }

    @Override
    public void onLongPress(MotionEvent e) {
        getDelegate(e).onLongPress(e);
        mDelegates.get(e).onLongPress(e);
    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        return getDelegate(e2).onFling(e1, e2, velocityX, velocityY);
        return mDelegates.get(e2).onFling(e1, e2, velocityX, velocityY);
    }

    public static interface Delegate extends OnGestureListener, OnDoubleTapListener {}
    public static class DummyDelegate extends SimpleOnGestureListener implements Delegate {}
}
+32 −2
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import static com.android.documentsui.selection.Shared.DEBUG;
import android.graphics.Point;
import android.support.annotation.VisibleForTesting;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.OnItemTouchListener;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
@@ -85,7 +86,7 @@ public final class GestureSelectionHelper extends ScrollHost {
        return true;
    }

    public boolean onInterceptTouchEvent(MotionEvent e) {
    private boolean onInterceptTouchEvent(MotionEvent e) {
        if (MotionEvents.isMouseEvent(e)) {
            return false;
        }
@@ -103,7 +104,7 @@ public final class GestureSelectionHelper extends ScrollHost {
        return handled;
    }

    public void onTouchEvent(MotionEvent e) {
    private void onTouchEvent(MotionEvent e) {
        if (!mStarted) {
            return;
        }
@@ -314,4 +315,33 @@ public final class GestureSelectionHelper extends ScrollHost {
            mView.removeCallbacks(r);
        }
    }

    public OnItemTouchListener getTouchListener() {
        return new EventPreprocessor(this);
    }

    private static final class EventPreprocessor implements OnItemTouchListener {

        private final GestureSelectionHelper mGestureSelector;

        private EventPreprocessor(GestureSelectionHelper gestureSelector) {
            mGestureSelector = gestureSelector;
        }

        @Override
        public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
            // Gesture Selector needs to be constantly fed events, so that when a long press does
            // happen, we would have the last DOWN event that occurred to keep track of our anchor
            // point
            return mGestureSelector.onInterceptTouchEvent(e);
        }

        @Override
        public void onTouchEvent(RecyclerView rv, MotionEvent e) {
            mGestureSelector.onTouchEvent(e);
        }

        @Override
        public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}
    }
}
Loading