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

Commit 444a9db8 authored by Ben Lin's avatar Ben Lin Committed by android-build-merger
Browse files

Refactor of BandController/DragStarter/GestureDetector.

am: 35f99e02

Change-Id: Ibd8600f5a24e949e51cf099d91467f93b37ee44f
parents af0d8923 35f99e02
Loading
Loading
Loading
Loading
+9 −0
Original line number Original line Diff line number Diff line
@@ -106,6 +106,10 @@ 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();


        /** Returns true if the action is neither the initial nor the final release of a mouse
         * or touch. */
        boolean isActionMove();

        // Eliminate the checked Exception from Autoclosable.
        // Eliminate the checked Exception from Autoclosable.
        @Override
        @Override
        public void close();
        public void close();
@@ -219,6 +223,11 @@ public final class Events {
            return mEvent.getActionMasked() == MotionEvent.ACTION_UP;
            return mEvent.getActionMasked() == MotionEvent.ACTION_UP;
        }
        }


        @Override
        public boolean isActionMove() {
            return mEvent.getActionMasked() == MotionEvent.ACTION_MOVE;
        }

        @Override
        @Override
        public Point getOrigin() {
        public Point getOrigin() {
            return new Point((int) mEvent.getX(), (int) mEvent.getY());
            return new Point((int) mEvent.getX(), (int) mEvent.getY());
+20 −42
Original line number Original line Diff line number Diff line
@@ -28,6 +28,7 @@ import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.annotation.VisibleForTesting;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.OnScrollListener;
import android.util.Log;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseBooleanArray;
@@ -55,7 +56,7 @@ import java.util.Set;
 * and {@link MultiSelectManager}. This class is responsible for rendering the band select
 * and {@link MultiSelectManager}. This class is responsible for rendering the band select
 * overlay and selecting overlaid items via MultiSelectManager.
 * overlay and selecting overlaid items via MultiSelectManager.
 */
 */
public class BandController extends RecyclerView.OnScrollListener {
public class BandController extends OnScrollListener {


    private static final String TAG = "BandController";
    private static final String TAG = "BandController";
    private static final int AUTOSCROLL_EDGE_HEIGHT = 1;
    private static final int AUTOSCROLL_EDGE_HEIGHT = 1;
@@ -79,26 +80,6 @@ public class BandController extends RecyclerView.OnScrollListener {
            DocumentsAdapter adapter,
            DocumentsAdapter adapter,
            MultiSelectManager selectionManager) {
            MultiSelectManager selectionManager) {
        this(new RuntimeSelectionEnvironment(view), adapter, selectionManager);
        this(new RuntimeSelectionEnvironment(view), adapter, selectionManager);

        view.addOnItemTouchListener(
                new RecyclerView.OnItemTouchListener() {
                    @Override
                    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
                        try (InputEvent event = MotionInputEvent.obtain(e, view)) {
                            return handleEvent(event);
                        }
                    }
                    @Override
                    public void onTouchEvent(RecyclerView rv, MotionEvent e) {
                        if (Events.isMouseEvent(e)) {
                            try (InputEvent event = MotionInputEvent.obtain(e, view)) {
                                processInputEvent(event);
                            }
                        }
                    }
                    @Override
                    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}
                });
    }
    }


    private BandController(
    private BandController(
@@ -199,26 +180,13 @@ public class BandController extends RecyclerView.OnScrollListener {
        mSelection = selection;
        mSelection = selection;
    }
    }


    private boolean handleEvent(InputEvent e) {
    boolean onInterceptTouchEvent(InputEvent e) {
        // Don't start, or extend bands on right click.
        if (e.isSecondaryButtonPressed()) {
            return false;
        }

        if (!e.isMouseEvent() && isActive()) {
            // Weird things happen if we keep up band select
            // when touch events happen.
            endBandSelect();
            return false;
        }

        // b/23793622 notes the fact that we *never* receive ACTION_DOWN
        // b/23793622 notes the fact that we *never* receive ACTION_DOWN
        // events in onTouchEvent. Where it not for this issue, we'd
        // events in onTouchEvent. Where it not for this issue, we'd
        // push start handling down into handleInputEvent.
        // push start handling down into handleInputEvent.
        if (shouldStart(e)) {
        if (shouldStart(e)) {
            // endBandSelect is handled in handleInputEvent.
            startBandSelect(e.getOrigin());
            startBandSelect(e.getOrigin());
        } else if (isActive() && e.isActionUp()) {
        } else if (shouldStop(e)) {
            // Same issue here w b/23793622. The ACTION_UP event
            // Same issue here w b/23793622. The ACTION_UP event
            // is only evert dispatched to onTouchEvent when
            // is only evert dispatched to onTouchEvent when
            // there is some associated motion. If a user taps
            // there is some associated motion. If a user taps
@@ -226,9 +194,7 @@ public class BandController extends RecyclerView.OnScrollListener {
            // started BUT not ended. Causing phantom
            // started BUT not ended. Causing phantom
            // bands to appear when the user later clicks to start
            // bands to appear when the user later clicks to start
            // band select.
            // band select.
            if (e.isMouseEvent()) {
            endBandSelect();
                processInputEvent(e);
            }
        }
        }


        return isActive();
        return isActive();
@@ -248,14 +214,26 @@ public class BandController extends RecyclerView.OnScrollListener {
        }
        }
    }
    }


    boolean shouldStart(InputEvent e) {
    public boolean shouldStart(InputEvent e) {
        // Don't start, or extend bands on right click.
        if (e.isSecondaryButtonPressed()) {
            return false;
        }

        if (!e.isMouseEvent() && isActive()) {
            // Weird things happen if we keep up band select
            // when touch events happen.
            endBandSelect();
            return false;
        }

        return !isActive()
        return !isActive()
                && e.isActionDown()  // the initial button press
                && e.isActionDown()  // the initial button press
                && mAdapter.getItemCount() > 0
                && mAdapter.getItemCount() > 0
                && e.getItemPosition() == RecyclerView.NO_ID;  // in empty space
                && e.getItemPosition() == RecyclerView.NO_ID;  // in empty space
    }
    }


    boolean shouldStop(InputEvent input) {
    public boolean shouldStop(InputEvent input) {
        return isActive()
        return isActive()
                && input.isMouseEvent()
                && input.isMouseEvent()
                && input.isActionUp();
                && input.isActionUp();
@@ -265,7 +243,7 @@ public class BandController extends RecyclerView.OnScrollListener {
     * Processes a MotionEvent by starting, ending, or resizing the band select overlay.
     * Processes a MotionEvent by starting, ending, or resizing the band select overlay.
     * @param input
     * @param input
     */
     */
    private void processInputEvent(InputEvent input) {
    void onTouchEvent(InputEvent input) {
        assert(input.isMouseEvent());
        assert(input.isMouseEvent());


        if (shouldStop(input)) {
        if (shouldStop(input)) {
+32 −83
Original line number Original line Diff line number Diff line
@@ -45,7 +45,6 @@ import android.os.Handler;
import android.os.Parcelable;
import android.os.Parcelable;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Document;
import android.support.v13.view.DragStartHelper;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.GridLayoutManager.SpanSizeLookup;
import android.support.v7.widget.GridLayoutManager.SpanSizeLookup;
@@ -191,7 +190,8 @@ public class DirectoryFragment extends Fragment
    private @Nullable BandController mBandController;
    private @Nullable BandController mBandController;
    private @Nullable ActionMode mActionMode;
    private @Nullable ActionMode mActionMode;


    private DragScrollListener mOnDragListener;
    private DragHoverListener mDragHoverListener;
    private DragStartListener mDragStartListener;
    private MenuManager mMenuManager;
    private MenuManager mMenuManager;


    private SortModel.UpdateListener mSortListener = (model, updateType) -> {
    private SortModel.UpdateListener mSortListener = (model, updateType) -> {
@@ -227,12 +227,12 @@ public class DirectoryFragment extends Fragment
        mFileList = view.findViewById(R.id.file_list);
        mFileList = view.findViewById(R.id.file_list);


        final int edgeHeight = (int) getResources().getDimension(R.dimen.autoscroll_edge_height);
        final int edgeHeight = (int) getResources().getDimension(R.dimen.autoscroll_edge_height);
        mOnDragListener = DragScrollListener.create(
        mDragHoverListener = DragHoverListener.create(
                edgeHeight, new DirectoryDragListener(this), mRecView);
                edgeHeight, new DirectoryDragListener(this), mRecView);


        // Make the recycler and the empty views responsive to drop events.
        // Make the recycler and the empty views responsive to drop events.
        mRecView.setOnDragListener(mOnDragListener);
        mRecView.setOnDragListener(mDragHoverListener);
        mEmptyView.setOnDragListener(mOnDragListener);
        mEmptyView.setOnDragListener(mDragHoverListener);


        return view;
        return view;
    }
    }
@@ -279,6 +279,7 @@ public class DirectoryFragment extends Fragment
        }
        }


        mIconHelper = new IconHelper(context, MODE_GRID);
        mIconHelper = new IconHelper(context, MODE_GRID);
        mClipper = DocumentsApplication.getDocumentClipper(getContext());


        mAdapter = new SectionBreakDocumentsAdapterWrapper(
        mAdapter = new SectionBreakDocumentsAdapterWrapper(
                this, new ModelBackedDocumentsAdapter(this, mIconHelper));
                this, new ModelBackedDocumentsAdapter(this, mIconHelper));
@@ -320,6 +321,10 @@ public class DirectoryFragment extends Fragment
                mSelectionMgr,
                mSelectionMgr,
                mRecView);
                mRecView);


        if (state.allowMultiple) {
            mBandController = new BandController(mRecView, mAdapter, mSelectionMgr);
        }

        mInputHandler = new UserInputHandler<>(
        mInputHandler = new UserInputHandler<>(
                mSelectionMgr,
                mSelectionMgr,
                mFocusManager,
                mFocusManager,
@@ -339,23 +344,32 @@ public class DirectoryFragment extends Fragment
                this::onDragAndDrop,
                this::onDragAndDrop,
                gestureSel::start);
                gestureSel::start);


        mGestureDetector =
        mDragStartListener = new DragStartListener(
                new ListeningGestureDetector(this.getContext(), mDragHelper,
                mIconHelper,
                        mInputHandler, gestureSel);
                getContext(),

                mModel,
        mRecView.addOnItemTouchListener(mGestureDetector);
                mSelectionMgr,
        mEmptyView.setOnTouchListener(mGestureDetector);
                mClipper,

                getDisplayState(),
        if (state.allowMultiple) {
                this::getModelId,
            mBandController = new BandController(mRecView, mAdapter, mSelectionMgr);
                mRecView::findChildViewUnder,
        }
                getContext().getDrawable(com.android.internal.R.drawable.ic_doc_generic));


        mGestureDetector = new ListeningGestureDetector(
                this.getContext(),
                mRecView,
                mEmptyView,
                mDragStartListener,
                gestureSel,
                mInputHandler,
                mBandController);


        mSelectionMgr.addCallback(mSelectionModeListener);
        mSelectionMgr.addCallback(mSelectionModeListener);


        final BaseActivity activity = getBaseActivity();
        final BaseActivity activity = getBaseActivity();
        mTuner = activity.createFragmentTuner();
        mTuner = activity.createFragmentTuner();
        mMenuManager = activity.getMenuManager();
        mMenuManager = activity.getMenuManager();
        mClipper = DocumentsApplication.getDocumentClipper(getContext());


        final ActivityManager am = (ActivityManager) context.getSystemService(
        final ActivityManager am = (ActivityManager) context.getSystemService(
                Context.ACTIVITY_SERVICE);
                Context.ACTIVITY_SERVICE);
@@ -1343,7 +1357,7 @@ public class DirectoryFragment extends Fragment
        if (Document.MIME_TYPE_DIR.equals(docMimeType)) {
        if (Document.MIME_TYPE_DIR.equals(docMimeType)) {
            // Make a directory item a drop target. Drop on non-directories and empty space
            // Make a directory item a drop target. Drop on non-directories and empty space
            // is handled at the list/grid view level.
            // is handled at the list/grid view level.
            view.setOnDragListener(mOnDragListener);
            view.setOnDragListener(mDragHoverListener);
        }
        }
    }
    }


@@ -1531,75 +1545,10 @@ public class DirectoryFragment extends Fragment
        }
        }
    }
    }


    private Drawable getDragIcon(Selection selection) {
        if (selection.size() == 1) {
            DocumentInfo doc = getSingleSelectedDocument(selection);
            return mIconHelper.getDocumentIcon(getContext(), doc);
        }
        return getContext().getDrawable(com.android.internal.R.drawable.ic_doc_generic);
    }

    private String getDragTitle(Selection selection) {
        assert (!selection.isEmpty());
        if (selection.size() == 1) {
            DocumentInfo doc = getSingleSelectedDocument(selection);
            return doc.displayName;
        }

        return Shared.getQuantityString(getContext(), R.plurals.elements_dragged, selection.size());
    }

    private DocumentInfo getSingleSelectedDocument(Selection selection) {
        assert (selection.size() == 1);
        final List<DocumentInfo> docs = mModel.getDocuments(mSelectionMgr.getSelection());
        assert (docs.size() == 1);
        return docs.get(0);
    }

    private DragStartHelper.OnDragStartListener mOnDragStartListener =
            new DragStartHelper.OnDragStartListener() {
                @Override
                public boolean onDragStart(View v, DragStartHelper helper) {
                    Selection selection = mSelectionMgr.getSelection();

                    if (v == null) {
                        Log.d(TAG, "Ignoring drag event, null view");
                        return false;
                    }
                    if (!isSelected(getModelId(v))) {
                        Log.d(TAG, "Ignoring drag event, unselected view.");
                        return false;
                    }

                    // NOTE: Preparation of the ClipData object can require a lot of time
                    // and ideally should be done in the background. Unfortunately
                    // the current code layout and framework assumptions don't support
                    // this. So for now, we could end up doing a bunch of i/o on main thread.
                    v.startDragAndDrop(
                            mClipper.getClipDataForDocuments(
                                    mModel::getItemUri,
                                    selection,
                                    FileOperationService.OPERATION_COPY),
                            new DragShadowBuilder(
                                    getActivity(),
                                    getDragTitle(selection),
                                    getDragIcon(selection)),
                            getDisplayState().stack.peek(),
                            View.DRAG_FLAG_GLOBAL
                                    | View.DRAG_FLAG_GLOBAL_URI_READ
                                    | View.DRAG_FLAG_GLOBAL_URI_WRITE);

                    return true;
                }
            };


    private DragStartHelper mDragHelper = new DragStartHelper(null, mOnDragStartListener);

    private boolean onDragAndDrop(InputEvent event) {
    private boolean onDragAndDrop(InputEvent event) {
        if (mTuner.dragAndDropEnabled()) {
        if (mTuner.dragAndDropEnabled()) {
            View childView = mRecView.findChildViewUnder(event.getX(), event.getY());
            View childView = mRecView.findChildViewUnder(event.getX(), event.getY());
            return mDragHelper.onLongClick(childView);
            return mDragStartListener.startDrag(childView);
        }
        }
        return false;
        return false;
    }
    }
+5 −5
Original line number Original line Diff line number Diff line
@@ -37,7 +37,7 @@ import javax.annotation.Nullable;
 * This class acts as a middle-man handler for potential auto-scrolling before passing the dragEvent
 * This class acts as a middle-man handler for potential auto-scrolling before passing the dragEvent
 * onto {@link DirectoryDragListener}.
 * onto {@link DirectoryDragListener}.
 */
 */
class DragScrollListener implements OnDragListener {
class DragHoverListener implements OnDragListener {


    private final ItemDragListener<? extends DragHost> mDragHandler;
    private final ItemDragListener<? extends DragHost> mDragHandler;
    private final IntSupplier mHeight;
    private final IntSupplier mHeight;
@@ -49,7 +49,7 @@ class DragScrollListener implements OnDragListener {
    /**
    /**
     * Predicate to tests whether it's the scroll view ({@link DirectoryFragment#mRecView}) itself.
     * Predicate to tests whether it's the scroll view ({@link DirectoryFragment#mRecView}) itself.
     *
     *
     * {@link DragScrollListener} is used for both {@link DirectoryFragment#mRecView} and its
     * {@link DragHoverListener} is used for both {@link DirectoryFragment#mRecView} and its
     * children. When we decide whether it's in the scroll zone we need to obtain the coordinate
     * children. When we decide whether it's in the scroll zone we need to obtain the coordinate
     * relative to {@link DirectoryFragment#mRecView} so we need to transform the coordinate if the
     * relative to {@link DirectoryFragment#mRecView} so we need to transform the coordinate if the
     * view that gets drag and drop events is a child of {@link DirectoryFragment#mRecView}.
     * view that gets drag and drop events is a child of {@link DirectoryFragment#mRecView}.
@@ -60,7 +60,7 @@ class DragScrollListener implements OnDragListener {
    private @Nullable Point mCurrentPosition;
    private @Nullable Point mCurrentPosition;


    @VisibleForTesting
    @VisibleForTesting
    DragScrollListener(
    DragHoverListener(
            int autoScrollEdgeHeight,
            int autoScrollEdgeHeight,
            ItemDragListener<? extends DragHost> dragHandler,
            ItemDragListener<? extends DragHost> dragHandler,
            IntSupplier heightSupplier,
            IntSupplier heightSupplier,
@@ -96,7 +96,7 @@ class DragScrollListener implements OnDragListener {
                mAutoScrollEdgeHeight, distanceDelegate, actionDelegate);
                mAutoScrollEdgeHeight, distanceDelegate, actionDelegate);
    }
    }


    static DragScrollListener create(
    static DragHoverListener create(
            int autoScrollEdgeHeight,
            int autoScrollEdgeHeight,
            ItemDragListener<? extends DragHost> dragHandler,
            ItemDragListener<? extends DragHost> dragHandler,
            View scrollView) {
            View scrollView) {
@@ -117,7 +117,7 @@ class DragScrollListener implements OnDragListener {
                scrollView.removeCallbacks(r);
                scrollView.removeCallbacks(r);
            }
            }
        };
        };
        DragScrollListener listener = new DragScrollListener(
        DragHoverListener listener = new DragHoverListener(
                autoScrollEdgeHeight,
                autoScrollEdgeHeight,
                dragHandler,
                dragHandler,
                scrollView::getHeight,
                scrollView::getHeight,
+158 −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.content.ClipData;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.View;

import com.android.documentsui.Events.InputEvent;
import com.android.documentsui.R;
import com.android.documentsui.Shared;
import com.android.documentsui.State;
import com.android.documentsui.clipping.DocumentClipper;
import com.android.documentsui.dirlist.MultiSelectManager.Selection;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.services.FileOperationService;

import java.util.List;
import java.util.function.Function;

import javax.annotation.Nullable;

/**
 * Listens for potential "drag-like" events and kick-start dragging as needed. Also allows external
 * direct call to {@code #startDrag(RecyclerView, View)} if explicit start is needed, such as long-
 * pressing on an item via touch. (e.g. {@link UserInputHandler#onLongPress(InputEvent)} via touch.)
 */
class DragStartListener {

    private static String TAG = "DragStartListener";

    private final IconHelper mIconHelper;
    private final Context mContext;
    private final Model mModel;
    private final MultiSelectManager mSelectionMgr;
    private final State mState;
    private final Drawable mDefaultDragIcon;
    private final DocumentClipper mClipper;
    private final Function<View, String> mIdFinder;
    private final ViewFinder mViewFinder;

    public DragStartListener(
            IconHelper iconHelper,
            Context context,
            Model model,
            MultiSelectManager selectionMgr,
            DocumentClipper clipper,
            State state,
            Function<View, String> idFinder,
            ViewFinder viewFinder,
            Drawable defaultDragIcon) {
        mIconHelper = iconHelper;
        mContext = context;
        mModel = model;
        mSelectionMgr = selectionMgr;
        mClipper = clipper;
        mState = state;
        mIdFinder = idFinder;
        mViewFinder = viewFinder;
        mDefaultDragIcon = defaultDragIcon;
    }

    boolean onInterceptTouchEvent(InputEvent event) {
        if (isDragEvent(event)) {
            View child = mViewFinder.findView(event.getX(), event.getY());
            startDrag(child);
            return true;
        }
        return false;
    }

    boolean startDrag(View v) {

        if (v == null) {
            Log.d(TAG, "Ignoring drag event, null view");
            return false;
        }

        final Selection selection = new Selection();
        String modelId = mIdFinder.apply(v);
        if (modelId != null && !mSelectionMgr.getSelection().contains(modelId)) {
            selection.add(modelId);
        } else {
            mSelectionMgr.getSelection(selection);
        }

        DocumentInfo currentDir = mState.stack.peek();
        ClipData clipData = mClipper.getClipDataForDocuments(
                mModel::getItemUri,
                selection,
                FileOperationService.OPERATION_COPY);
        // NOTE: Preparation of the ClipData object can require a lot of time
        // and ideally should be done in the background. Unfortunately
        // the current code layout and framework assumptions don't support
        // this. So for now, we could end up doing a bunch of i/o on main thread.
        v.startDragAndDrop(
                clipData,
                new DragShadowBuilder(
                        mContext,
                        getDragTitle(selection),
                        getDragIcon(selection)),
                currentDir,
                View.DRAG_FLAG_GLOBAL
                        | View.DRAG_FLAG_GLOBAL_URI_READ
                        | View.DRAG_FLAG_GLOBAL_URI_WRITE);

        return true;
    }

    public boolean isDragEvent(InputEvent e) {
        return e.isOverItem() && e.isMouseEvent() && e.isActionMove() && e.isPrimaryButtonPressed();
    }

    private Drawable getDragIcon(Selection selection) {
        if (selection.size() == 1) {
            DocumentInfo doc = getSingleSelectedDocument(selection);
            return mIconHelper.getDocumentIcon(mContext, doc);
        }
        return mDefaultDragIcon;
    }

    private String getDragTitle(Selection selection) {
        assert (!selection.isEmpty());
        if (selection.size() == 1) {
            DocumentInfo doc = getSingleSelectedDocument(selection);
            return doc.displayName;
        }
        return Shared.getQuantityString(mContext, R.plurals.elements_dragged, selection.size());
    }

    private DocumentInfo getSingleSelectedDocument(Selection selection) {
        assert (selection.size() == 1);
        final List<DocumentInfo> docs = mModel.getDocuments(selection);
        assert (docs.size() == 1);
        return docs.get(0);
    }

    @FunctionalInterface
    interface ViewFinder {
        @Nullable View findView(float x, float y);
    }
}
Loading