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

Commit be007c26 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Move event handling code out of DirectoryFragment"

parents 98ecf022 10419c28
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
@@ -144,6 +144,10 @@ public final class Events {
        private static final Pools.SimplePool<MotionInputEvent> sPool = new Pools.SimplePool<>(1);

        private MotionEvent mEvent;
        interface PositionProvider {
            int get(MotionEvent e);
        }

        private int mPosition;

        private MotionInputEvent() {
@@ -167,6 +171,19 @@ public final class Events {
            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);

            return instance;
        }

        public void recycle() {
            Shared.checkMainLoop();

+18 −149
Original line number Diff line number Diff line
@@ -49,7 +49,6 @@ import android.support.v13.view.DragStartHelper;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.GridLayoutManager.SpanSizeLookup;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.OnItemTouchListener;
import android.support.v7.widget.RecyclerView.RecyclerListener;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.text.BidiFormatter;
@@ -61,14 +60,12 @@ import android.view.ContextMenu;
import android.view.DragEvent;
import android.view.GestureDetector;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
@@ -143,7 +140,7 @@ public class DirectoryFragment extends Fragment
    private Model mModel;
    private MultiSelectManager mSelectionManager;
    private Model.UpdateListener mModelUpdateListener = new ModelUpdateListener();
    private ItemEventListener mItemEventListener = new ItemEventListener();
    private ItemEventListener mItemEventListener;
    private SelectionModeListener mSelectionModeListener;
    private FocusManager mFocusManager;

@@ -322,6 +319,13 @@ public class DirectoryFragment extends Fragment
        // Make sure this is done after the RecyclerView is set up.
        mFocusManager = new FocusManager(context, mRecView, mModel);

        mItemEventListener = new ItemEventListener(
                mSelectionManager,
                mFocusManager,
                this::handleViewItem,
                this::deleteDocuments,
                this::canSelect);

        final BaseActivity activity = getBaseActivity();
        mTuner = activity.createFragmentTuner();
        mMenuManager = activity.getMenuManager();
@@ -1395,99 +1399,6 @@ public class DirectoryFragment extends Fragment
        return mSelectionManager.getSelection().contains(modelId);
    }

    private class ItemEventListener implements DocumentHolder.EventListener {
        @Override
        public boolean onActivate(DocumentHolder doc) {
            // Toggle selection if we're in selection mode, othewise, view item.
            if (mSelectionManager.hasSelection()) {
                mSelectionManager.toggleSelection(doc.modelId);
            } else {
                handleViewItem(doc.modelId);
            }
            return true;
        }

        @Override
        public boolean onSelect(DocumentHolder doc) {
            mSelectionManager.toggleSelection(doc.modelId);
            mSelectionManager.setSelectionRangeBegin(doc.getAdapterPosition());
            return true;
        }

        @Override
        public boolean onKey(DocumentHolder doc, int keyCode, KeyEvent event) {
            // Only handle key-down events. This is simpler, consistent with most other UIs, and
            // enables the handling of repeated key events from holding down a key.
            if (event.getAction() != KeyEvent.ACTION_DOWN) {
                return false;
            }

            // Ignore tab key events.  Those should be handled by the top-level key handler.
            if (keyCode == KeyEvent.KEYCODE_TAB) {
                return false;
            }

            if (mFocusManager.handleKey(doc, keyCode, event)) {
                // Handle range selection adjustments. Extending the selection will adjust the
                // bounds of the in-progress range selection. Each time an unshifted navigation
                // event is received, the range selection is restarted.
                if (shouldExtendSelection(doc, event)) {
                    if (!mSelectionManager.isRangeSelectionActive()) {
                        // Start a range selection if one isn't active
                        mSelectionManager.startRangeSelection(doc.getAdapterPosition());
                    }
                    mSelectionManager.snapRangeSelection(mFocusManager.getFocusPosition());
                } else {
                    mSelectionManager.endRangeSelection();
                }
                return true;
            }

            // Handle enter key events
            switch (keyCode) {
                case KeyEvent.KEYCODE_ENTER:
                    if (event.isShiftPressed()) {
                        return onSelect(doc);
                    }
                    // For non-shifted enter keypresses, fall through.
                case KeyEvent.KEYCODE_DPAD_CENTER:
                case KeyEvent.KEYCODE_BUTTON_A:
                    return onActivate(doc);
                case KeyEvent.KEYCODE_FORWARD_DEL:
                    // This has to be handled here instead of in a keyboard shortcut, because
                    // keyboard shortcuts all have to be modified with the 'Ctrl' key.
                    if (mSelectionManager.hasSelection()) {
                        Selection selection = mSelectionManager.getSelection(new Selection());
                        deleteDocuments(selection);
                    }
                    // Always handle the key, even if there was nothing to delete. This is a
                    // precaution to prevent other handlers from potentially picking up the event
                    // and triggering extra behaviours.
                    return true;
            }

            return false;
        }

        private boolean shouldExtendSelection(DocumentHolder doc, KeyEvent event) {
            if (!Events.isNavigationKeyCode(event.getKeyCode()) || !event.isShiftPressed()) {
                return false;
            }

            // TODO: Combine this method with onBeforeItemStateChange, as both of them are almost
            // the same, and responsible for the same thing (whether to select or not).
            final Cursor cursor = mModel.getItem(doc.modelId);
            if (cursor == null) {
                Log.w(TAG, "Couldn't obtain cursor for modelId: " + doc.modelId);
                return false;
            }

            final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
            final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
            return mTuner.canSelectType(docMimeType, docFlags);
        }
    }

    private final class ModelUpdateListener implements Model.UpdateListener {
        @Override
        public void onModelUpdate(Model model) {
@@ -1594,68 +1505,26 @@ public class DirectoryFragment extends Fragment
        }
    };

    // Previously we listened to events with one class, only to bounce them forward
    // to GestureDetector. We're still doing that here, but with a single class
    // that reduces overall complexity in our glue code.
    private static final class ListeningGestureDetector extends GestureDetector
            implements OnItemTouchListener, OnTouchListener {

        private DragStartHelper mDragHelper;
        private GestureListener mGestureListener;

        public ListeningGestureDetector(
                Context context, DragStartHelper dragHelper, GestureListener listener) {
            super(context, listener);
            mDragHelper = dragHelper;
            mGestureListener = listener;
            setOnDoubleTapListener(listener);
        }

        @Override
        public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
            if (e.getAction() == MotionEvent.ACTION_DOWN && Events.isMouseEvent(e)) {
                mGestureListener.setLastButtonState(e.getButtonState());
            }

            // Detect drag events. When a drag is detected, intercept the rest of the gesture.
            View itemView = rv.findChildViewUnder(e.getX(), e.getY());
            if (itemView != null && mDragHelper.onTouch(itemView,  e)) {
                return true;
            }
            // Forward unhandled events to the GestureDetector.
            onTouchEvent(e);
    private boolean canSelect(String modelId) {

        // TODO: Combine this method with onBeforeItemStateChange, as both of them are almost
        // the same, and responsible for the same thing (whether to select or not).
        final Cursor cursor = mModel.getItem(modelId);
        if (cursor == null) {
            Log.w(TAG, "Couldn't obtain cursor for modelId: " + modelId);
            return false;
        }

        @Override
        public void onTouchEvent(RecyclerView rv, MotionEvent e) {
            View itemView = rv.findChildViewUnder(e.getX(), e.getY());
            mDragHelper.onTouch(itemView,  e);
            // Note: even though this event is being handled as part of a drag gesture, continue
            // forwarding to the GestureDetector. The detector needs to see the entire cluster of
            // events in order to properly interpret gestures.
            onTouchEvent(e);
        }

        @Override
        public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}

        // For mEmptyView right-click context menu
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if (event.getButtonState() == MotionEvent.BUTTON_SECONDARY) {
                return mGestureListener.onRightClick(event);
            }
            return false;
        }
        final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
        final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
        return mTuner.canSelectType(docMimeType, docFlags);
    }

    /**
     * The gesture listener for items in the list/grid view. Interprets gestures and sends the
     * events to the target DocumentHolder, whence they are routed to the appropriate listener.
     */
    private class GestureListener extends GestureDetector.SimpleOnGestureListener {
    class GestureListener extends GestureDetector.SimpleOnGestureListener {
        // From the RecyclerView, we get two events sent to
        // ListeningGestureDetector#onInterceptTouchEvent on a mouse click; we first get an
        // ACTION_DOWN Event for clicking on the mouse, and then an ACTION_UP event from releasing
+132 −0
Original line number 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.view.KeyEvent;

import com.android.documentsui.Events;
import com.android.documentsui.dirlist.MultiSelectManager.Selection;

import java.util.function.Consumer;
import java.util.function.Predicate;

/**
 * Handles click/tap/key events on individual DocumentHolders.
 */
class ItemEventListener implements DocumentHolder.EventListener {
    private MultiSelectManager mSelectionManager;
    private FocusManager mFocusManager;

    private Consumer<String> mViewItemCallback;
    private Consumer<Selection> mDeleteDocumentsCallback;
    private Predicate<String> mCanSelectPredicate;

    public ItemEventListener(
            MultiSelectManager selectionManager,
            FocusManager focusManager,
            Consumer<String> viewItemCallback,
            Consumer<Selection> deleteDocumentsCallback,
            Predicate<String> canSelectPredicate) {

        mSelectionManager = selectionManager;
        mFocusManager = focusManager;
        mViewItemCallback = viewItemCallback;
        mDeleteDocumentsCallback = deleteDocumentsCallback;
        mCanSelectPredicate = canSelectPredicate;
    }

    @Override
    public boolean onActivate(DocumentHolder doc) {
        // Toggle selection if we're in selection mode, othewise, view item.
        if (mSelectionManager.hasSelection()) {
            mSelectionManager.toggleSelection(doc.modelId);
        } else {
            mViewItemCallback.accept(doc.modelId);
        }
        return true;
    }

    @Override
    public boolean onSelect(DocumentHolder doc) {
        mSelectionManager.toggleSelection(doc.modelId);
        mSelectionManager.setSelectionRangeBegin(doc.getAdapterPosition());
        return true;
    }

    @Override
    public boolean onKey(DocumentHolder doc, int keyCode, KeyEvent event) {
        // Only handle key-down events. This is simpler, consistent with most other UIs, and
        // enables the handling of repeated key events from holding down a key.
        if (event.getAction() != KeyEvent.ACTION_DOWN) {
            return false;
        }

        // Ignore tab key events.  Those should be handled by the top-level key handler.
        if (keyCode == KeyEvent.KEYCODE_TAB) {
            return false;
        }

        if (mFocusManager.handleKey(doc, keyCode, event)) {
            // Handle range selection adjustments. Extending the selection will adjust the
            // bounds of the in-progress range selection. Each time an unshifted navigation
            // event is received, the range selection is restarted.
            if (shouldExtendSelection(doc, event)) {
                if (!mSelectionManager.isRangeSelectionActive()) {
                    // Start a range selection if one isn't active
                    mSelectionManager.startRangeSelection(doc.getAdapterPosition());
                }
                mSelectionManager.snapRangeSelection(mFocusManager.getFocusPosition());
            } else {
                mSelectionManager.endRangeSelection();
            }
            return true;
        }

        // Handle enter key events
        switch (keyCode) {
            case KeyEvent.KEYCODE_ENTER:
                if (event.isShiftPressed()) {
                    return onSelect(doc);
                }
                // For non-shifted enter keypresses, fall through.
            case KeyEvent.KEYCODE_DPAD_CENTER:
            case KeyEvent.KEYCODE_BUTTON_A:
                return onActivate(doc);
            case KeyEvent.KEYCODE_FORWARD_DEL:
                // This has to be handled here instead of in a keyboard shortcut, because
                // keyboard shortcuts all have to be modified with the 'Ctrl' key.
                if (mSelectionManager.hasSelection()) {
                    Selection selection = mSelectionManager.getSelection(new Selection());
                    mDeleteDocumentsCallback.accept(selection);
                }
                // Always handle the key, even if there was nothing to delete. This is a
                // precaution to prevent other handlers from potentially picking up the event
                // and triggering extra behaviours.
                return true;
        }

        return false;
    }

    private boolean shouldExtendSelection(DocumentHolder doc, KeyEvent event) {
        if (!Events.isNavigationKeyCode(event.getKeyCode()) || !event.isShiftPressed()) {
            return false;
        }

        return mCanSelectPredicate.test(doc.modelId);
    }
}
+86 −0
Original line number 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.Context;
import android.support.v13.view.DragStartHelper;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.OnItemTouchListener;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;

import com.android.documentsui.Events;
import com.android.documentsui.dirlist.DirectoryFragment.GestureListener;

// Previously we listened to events with one class, only to bounce them forward
// to GestureDetector. We're still doing that here, but with a single class
// that reduces overall complexity in our glue code.
final class ListeningGestureDetector extends GestureDetector
        implements OnItemTouchListener, OnTouchListener {

    private DragStartHelper mDragHelper;
    private GestureListener mGestureListener;

    public ListeningGestureDetector(
            Context context, DragStartHelper dragHelper, GestureListener listener) {
        super(context, listener);
        mDragHelper = dragHelper;
        mGestureListener = listener;
        setOnDoubleTapListener(listener);
    }

    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        if (e.getAction() == MotionEvent.ACTION_DOWN && Events.isMouseEvent(e)) {
            mGestureListener.setLastButtonState(e.getButtonState());
        }

        // Detect drag events. When a drag is detected, intercept the rest of the gesture.
        View itemView = rv.findChildViewUnder(e.getX(), e.getY());
        if (itemView != null && mDragHelper.onTouch(itemView,  e)) {
            return true;
        }
        // Forward unhandled events to the GestureDetector.
        onTouchEvent(e);

        return false;
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {
        View itemView = rv.findChildViewUnder(e.getX(), e.getY());
        mDragHelper.onTouch(itemView,  e);
        // Note: even though this event is being handled as part of a drag gesture, continue
        // forwarding to the GestureDetector. The detector needs to see the entire cluster of
        // events in order to properly interpret gestures.
        onTouchEvent(e);
    }

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}

    // For mEmptyView right-click context menu
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (event.getButtonState() == MotionEvent.BUTTON_SECONDARY) {
            return mGestureListener.onRightClick(event);
        }
        return false;
    }
}
 No newline at end of file