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

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

Move event handling code out of DirectoryFragment

Bug:29575607
Change-Id: Ieae3bcee030973bf83551a74f722236a24832730
parent 95966b8f
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