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

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

Segregate Scale and Refresh support.

Scale and Refresh support aren't part of the mission of selection
support to RecyclerView.

This is a cleanup and separation CL. In a subsequent change
I'll merge remaining event handling from ListeningGestureDetector
into UserInputHandler.

Bug: 64847011
Test: Passing
Change-Id: Ie20a8b998dcf1761b2229e03fd55d6e813d6d6ad
parent 140b1b8d
Loading
Loading
Loading
Loading
+13 −6
Original line number Diff line number Diff line
@@ -417,16 +417,23 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On
                gestureHandler,
                () -> mRecView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS));

        new ListeningGestureDetector(
        if (Build.IS_DEBUGGABLE) {
            new ScaleHelper(this.getContext(), mInjector.features, this::scaleLayout)
                    .install(mRecView);
        }

        new RefreshHelper(mRefreshLayout::setEnabled)
                .install(mRecView);

        ListeningGestureDetector recListener = new ListeningGestureDetector(
                this.getContext(),
                mInjector.features,
                mRecView,
                mDetailsLookup,
                mDragStartListener::onMouseDragEvent,
                mRefreshLayout::setEnabled,
                gestureSel,
                mInputHandler,
                mBandSelector,
                this::scaleLayout);
                mBandSelector);

        recListener.listenTo(mRecView);

        mActionModeController = mInjector.getActionModeController(
                mSelectionMetadata,
+24 −76
Original line number Diff line number Diff line
@@ -16,118 +16,57 @@

package com.android.documentsui.dirlist;

import static com.android.documentsui.base.Shared.VERBOSE;

import android.annotation.Nullable;
import android.content.Context;
import android.os.Build;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.OnItemTouchListener;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;

import com.android.documentsui.base.BooleanConsumer;
import com.android.documentsui.base.EventDetailsLookup;
import com.android.documentsui.base.EventHandler;
import com.android.documentsui.base.Events;
import com.android.documentsui.base.Features;
import com.android.documentsui.selection.addons.BandSelectionHelper;
import com.android.documentsui.selection.addons.GestureSelectionHelper;

import java.util.function.Consumer;

//Receives event meant for both directory and empty view, and either pass them to
//{@link UserInputHandler} for simple gestures (Single Tap, Long-Press), or intercept them for
//other types of gestures (drag n' drop)
final class ListeningGestureDetector extends GestureDetector implements OnItemTouchListener {

    private static final String TAG = "ListeningGestureDetector";
final class ListeningGestureDetector extends GestureDetector {

    private final Features mFeatures;
    private final GestureSelectionHelper mGestureSelector;
    private final EventHandler<MotionEvent> mMouseDragListener;
    private final BooleanConsumer mRefreshLayoutEnabler;
    private final BandSelectionHelper mBandController;
    private final EventDetailsLookup mDocEventBinder;
    private final EventDetailsLookup mEventDetailsLookup;

    private final MouseDelegate mMouseDelegate = new MouseDelegate();
    private final TouchDelegate mTouchDelegate = new TouchDelegate();

    // Currently only initialized on IS_DEBUGGABLE builds.
    private final @Nullable ScaleGestureDetector mScaleDetector;


    public ListeningGestureDetector(
            Context context,
            Features features,
            RecyclerView recView,
            EventDetailsLookup eventDetailsLookup,
            EventHandler<MotionEvent> mouseDragListener,
            BooleanConsumer refreshLayoutEnabler,
            GestureSelectionHelper gestureSelector,
            UserInputHandler handler,
            @Nullable BandSelectionHelper bandController,
            Consumer<Float> scaleHandler) {
            @Nullable BandSelectionHelper bandController) {

        super(context, handler);

        mFeatures = features;
        mMouseDragListener = mouseDragListener;
        mRefreshLayoutEnabler = refreshLayoutEnabler;
        mGestureSelector = gestureSelector;
        mBandController = bandController;

        mDocEventBinder = new RuntimeEventDetailsLookup(recView);

        recView.addOnItemTouchListener(this);

        mScaleDetector = !Build.IS_DEBUGGABLE
                ? null
                : new ScaleGestureDetector(
                        context,
                        new ScaleGestureDetector.SimpleOnScaleGestureListener() {
                            @Override
                            public boolean onScale(ScaleGestureDetector detector) {
                                if (VERBOSE) Log.v(TAG,
                                        "Received scale event: " + detector.getScaleFactor());
                                scaleHandler.accept(detector.getScaleFactor());
                                return true;
                            }
                        });
        mEventDetailsLookup = eventDetailsLookup;
    }

    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
    private boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        boolean handled = false;

        // TODO: Re-wire event handling so that we're not dispatching
        //     events to to scaledetector's #onTouchEvent from this
        //     #onInterceptTouchEvent touch event.
        if (mFeatures.isGestureScaleEnabled()
                && mScaleDetector != null) {
            mScaleDetector.onTouchEvent(e);
        }

        if (Events.isMouseEvent(e)) {
            if (Events.isActionDown(e)) {
                mRefreshLayoutEnabler.accept(false);
            }
            handled |= mMouseDelegate.onInterceptTouchEvent(e);
        } else {
            // If user has started some gesture while RecyclerView is not at the top, disable
            // refresh
            if (Events.isActionDown(e) && rv.computeVerticalScrollOffset() != 0) {
                mRefreshLayoutEnabler.accept(false);
            }
            handled |= mTouchDelegate.onInterceptTouchEvent(e);
        }


        if (Events.isActionUp(e)) {
            mRefreshLayoutEnabler.accept(true);
        }

        // Forward all events to UserInputHandler.
        // This is necessary since UserInputHandler needs to always see the first DOWN event. Or
        // else all future UP events will be tossed.
@@ -136,18 +75,13 @@ final class ListeningGestureDetector extends GestureDetector implements OnItemTo
        return handled;
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {
    private void onTouchEvent(RecyclerView rv, MotionEvent e) {
        if (Events.isMouseEvent(e)) {
            mMouseDelegate.onTouchEvent(e);
        } else {
            mTouchDelegate.onTouchEvent(e);
        }

        if (Events.isActionUp(e)) {
            mRefreshLayoutEnabler.accept(true);
        }

        // Note: even though this event is being handled as part of gestures such as drag and band,
        // continue forwarding to the GestureDetector. The detector needs to see the entire cluster
        // of events in order to properly interpret other gestures, such as long press.
@@ -156,7 +90,7 @@ final class ListeningGestureDetector extends GestureDetector implements OnItemTo

    private class MouseDelegate {
        boolean onInterceptTouchEvent(MotionEvent e) {
            if (Events.isMouseDragEvent(e) && mDocEventBinder.inItemDragRegion(e)) {
            if (Events.isMouseDragEvent(e) && mEventDetailsLookup.inItemDragRegion(e)) {
                return mMouseDragListener.accept(e);
            } else if (mBandController != null
                    && (mBandController.shouldStart(e) || mBandController.shouldStop(e))) {
@@ -186,6 +120,20 @@ final class ListeningGestureDetector extends GestureDetector implements OnItemTo
        }
    }

    public void listenTo(RecyclerView view) {
        view.addOnItemTouchListener(
                new OnItemTouchListener() {
                    @Override
                    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
                        return ListeningGestureDetector.this.onInterceptTouchEvent(rv, e);
                    }
                    @Override
                    public void onTouchEvent(RecyclerView rv, MotionEvent e) {
                        ListeningGestureDetector.this.onTouchEvent(rv, e);
                    }
                    @Override
                    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}

                });
    }
}
+86 −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 android.support.v4.util.Preconditions.checkState;

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

import com.android.documentsui.base.BooleanConsumer;
import com.android.documentsui.base.Events;

/**
 * Class providing support for gluing gesture scaling of the ui into RecyclerView + DocsUI.
 */
final class RefreshHelper {

    private final BooleanConsumer mRefreshLayoutEnabler;

    private boolean mInstalled;

    public RefreshHelper(BooleanConsumer refreshLayoutEnabler) {
        mRefreshLayoutEnabler = refreshLayoutEnabler;
    }

    private boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        if (Events.isMouseEvent(e)) {
            if (Events.isActionDown(e)) {
                mRefreshLayoutEnabler.accept(false);
            }
        } else {
            // If user has started some gesture while RecyclerView is not at the top, disable
            // refresh
            if (Events.isActionDown(e) && rv.computeVerticalScrollOffset() != 0) {
                mRefreshLayoutEnabler.accept(false);
            }
        }

        if (Events.isActionUp(e)) {
            mRefreshLayoutEnabler.accept(true);
        }

        return false;
    }

    private void onTouchEvent(RecyclerView rv, MotionEvent e) {
        if (Events.isActionUp(e)) {
            mRefreshLayoutEnabler.accept(true);
        }
    }


    void install(RecyclerView view) {
        checkState(!mInstalled);

        view.addOnItemTouchListener(
                new OnItemTouchListener() {
                    @Override
                    public void onTouchEvent(RecyclerView rv, MotionEvent e) {
                        RefreshHelper.this.onTouchEvent(rv, e);
                    }

                    @Override
                    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
                        return RefreshHelper.this.onInterceptTouchEvent(rv, e);
                    }

                    @Override
                    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}
        });
    }
}
+93 −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 android.support.v4.util.Preconditions.checkState;
import static com.android.documentsui.base.Shared.VERBOSE;

import android.content.Context;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.OnItemTouchListener;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;

import com.android.documentsui.base.Features;

import java.util.function.Consumer;

/**
 * Class providing support for gluing gesture scaling of the ui into RecyclerView + DocsUI.
 */
final class ScaleHelper {

    private static final String TAG = "ScaleHelper";

    private final Context mContext;
    private final Features mFeatures;
    private final Consumer<Float> mScaleCallback;

    private @Nullable ScaleGestureDetector mScaleDetector;

    public ScaleHelper(Context context, Features features, Consumer<Float> scaleCallback) {
        mContext = context;
        mFeatures = features;
        mScaleCallback = scaleCallback;
    }

    private boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        // Checking feature is deferred to runtime so the feature can be enabled
        // after this class has been setup.
        if (mFeatures.isGestureScaleEnabled() && mScaleDetector != null) {
            mScaleDetector.onTouchEvent(e);
        }

        return false;
    }

    void install(RecyclerView view) {
        checkState(Build.IS_DEBUGGABLE);
        checkState(mScaleDetector == null);

        mScaleDetector = new ScaleGestureDetector(
            mContext,
            new ScaleGestureDetector.SimpleOnScaleGestureListener() {
                @Override
                public boolean onScale(ScaleGestureDetector detector) {
                    if (VERBOSE) Log.v(TAG,
                            "Received scale event: " + detector.getScaleFactor());
                    mScaleCallback.accept(detector.getScaleFactor());
                    return true;
                }
            });

        view.addOnItemTouchListener(
                new OnItemTouchListener() {
                    @Override
                    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
                        return ScaleHelper.this.onInterceptTouchEvent(rv, e);
                    }

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

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