Move event handling code out of DirectoryFragment

Bug:29575607
Change-Id: Ieae3bcee030973bf83551a74f722236a24832730
This commit is contained in:
Steve McKay
2016-06-23 11:08:19 -07:00
parent c2fdeb42da
commit 10419c2882
4 changed files with 253 additions and 149 deletions

View File

@@ -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();

View File

@@ -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

View File

@@ -0,0 +1,132 @@
/*
* 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);
}
}

View File

@@ -0,0 +1,86 @@
/*
* 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;
}
}