Move GestureListener to own file.

And:
- make MotionInputEvent AutoCloseable (and update obtainers accordingly).
- remove unused constructor args and overloading from MultiSelectManager.

Change-Id: I335a95c3d05ab10bdcbfebab8dc69f0b2f681e3a
This commit is contained in:
Steve McKay
2016-06-30 12:09:34 -07:00
parent fd9f5e60f1
commit 2499e8deb2
8 changed files with 182 additions and 174 deletions

View File

@@ -138,7 +138,7 @@ public final class Events {
int getItemPosition();
}
public static final class MotionInputEvent implements InputEvent {
public static final class MotionInputEvent implements InputEvent, AutoCloseable {
private static final String TAG = "MotionInputEvent";
private static final Pools.SimplePool<MotionInputEvent> sPool = new Pools.SimplePool<>(1);
@@ -199,6 +199,11 @@ public final class Events {
assert(released);
}
@Override
public void close() {
recycle();
}
@Override
public boolean isMouseEvent() {
return Events.isMouseEvent(mEvent);

View File

@@ -52,13 +52,13 @@ import com.android.documentsui.model.DocumentStack;
import com.android.documentsui.model.DurableUtils;
import com.android.documentsui.model.RootInfo;
import libcore.io.IoUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import libcore.io.IoUtils;
/**
* Display directories where recent creates took place.
*/
@@ -141,17 +141,13 @@ public class RecentsCreateFragment extends Fragment {
new RecyclerView.OnItemTouchListener() {
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
final MotionInputEvent event = MotionInputEvent.obtain(e, mRecView);
try {
try (MotionInputEvent event = MotionInputEvent.obtain(e, mRecView)) {
if (event.isOverItem() && event.isActionUp()) {
final DocumentStack stack = mAdapter.getItem(event.getItemPosition());
((BaseActivity) getActivity()).onStackPicked(stack);
return true;
}
return false;
} finally {
event.recycle();
}
}
@@ -247,7 +243,7 @@ public class RecentsCreateFragment extends Fragment {
final LayoutInflater inflater = LayoutInflater.from(context);
return new StackHolder(
(View) inflater.inflate(R.layout.item_doc_list, parent, false));
inflater.inflate(R.layout.item_doc_list, parent, false));
}
@Override

View File

@@ -85,21 +85,15 @@ public class BandController extends RecyclerView.OnScrollListener {
new RecyclerView.OnItemTouchListener() {
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
final MotionInputEvent event = MotionInputEvent.obtain(e, view);
try {
try (MotionInputEvent event = MotionInputEvent.obtain(e, view)) {
return handleEvent(event);
} finally {
event.recycle();
}
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
if (Events.isMouseEvent(e)) {
final MotionInputEvent event = MotionInputEvent.obtain(e, view);
try {
try (MotionInputEvent event = MotionInputEvent.obtain(e, view)) {
processInputEvent(event);
} finally {
event.recycle();
}
}
}

View File

@@ -58,13 +58,11 @@ import android.util.SparseArray;
import android.view.ActionMode;
import android.view.ContextMenu;
import android.view.DragEvent;
import android.view.GestureDetector;
import android.view.HapticFeedbackConstants;
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.ViewGroup;
import android.widget.ImageView;
@@ -74,11 +72,9 @@ import android.widget.Toolbar;
import com.android.documentsui.BaseActivity;
import com.android.documentsui.DirectoryLoader;
import com.android.documentsui.DirectoryResult;
import com.android.documentsui.UrisSupplier;
import com.android.documentsui.DocumentClipper;
import com.android.documentsui.DocumentsActivity;
import com.android.documentsui.DocumentsApplication;
import com.android.documentsui.Events;
import com.android.documentsui.Events.MotionInputEvent;
import com.android.documentsui.ItemDragListener;
import com.android.documentsui.MenuManager;
@@ -94,6 +90,7 @@ import com.android.documentsui.Shared;
import com.android.documentsui.Snackbars;
import com.android.documentsui.State;
import com.android.documentsui.State.ViewMode;
import com.android.documentsui.UrisSupplier;
import com.android.documentsui.dirlist.MultiSelectManager.Selection;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.RootInfo;
@@ -295,22 +292,27 @@ public class DirectoryFragment extends Fragment
}
mRecView.setLayoutManager(mLayout);
mGestureDetector =
new ListeningGestureDetector(this.getContext(), mDragHelper, new GestureListener());
mRecView.addOnItemTouchListener(mGestureDetector);
mEmptyView.setOnTouchListener(mGestureDetector);
// TODO: instead of inserting the view into the constructor, extract listener-creation code
// and set the listener on the view after the fact. Then the view doesn't need to be passed
// into the selection manager.
mSelectionManager = new MultiSelectManager(
mRecView,
mAdapter,
state.allowMultiple
? MultiSelectManager.MODE_MULTIPLE
: MultiSelectManager.MODE_SINGLE,
null);
: MultiSelectManager.MODE_SINGLE);
GestureListener gestureListener = new GestureListener(
mSelectionManager,
mRecView,
this::getTarget,
this::onDoubleTap,
this::onRightClick);
mGestureDetector =
new ListeningGestureDetector(this.getContext(), mDragHelper, gestureListener);
mRecView.addOnItemTouchListener(mGestureDetector);
mEmptyView.setOnTouchListener(mGestureDetector);
if (state.allowMultiple) {
mBandController = new BandController(mRecView, mAdapter, mSelectionManager);
@@ -426,44 +428,38 @@ public class DirectoryFragment extends Fragment
return false;
}
protected boolean onRightClick(MotionEvent e) {
// First get target to see if it's a blank window or a file/doc
final MotionInputEvent event = MotionInputEvent.obtain(e, mRecView);
try {
if (event.getItemPosition() != RecyclerView.NO_POSITION) {
final DocumentHolder holder = getTarget(event);
String modelId = getModelId(holder.itemView);
if (!mSelectionManager.getSelection().contains(modelId)) {
mSelectionManager.clearSelection();
// Set selection on the one single item
List<String> ids = Collections.singletonList(modelId);
mSelectionManager.setItemsSelected(ids, true);
}
protected boolean onRightClick(MotionInputEvent e) {
if (e.getItemPosition() != RecyclerView.NO_POSITION) {
final DocumentHolder holder = getTarget(e);
String modelId = getModelId(holder.itemView);
if (!mSelectionManager.getSelection().contains(modelId)) {
mSelectionManager.clearSelection();
// Set selection on the one single item
List<String> ids = Collections.singletonList(modelId);
mSelectionManager.setItemsSelected(ids, true);
}
// We are registering for context menu here so long-press doesn't trigger this
// floating context menu, and then quickly unregister right afterwards
registerForContextMenu(holder.itemView);
mRecView.showContextMenuForChild(holder.itemView,
e.getX() - holder.itemView.getLeft(), e.getY() - holder.itemView.getTop());
unregisterForContextMenu(holder.itemView);
}
// If there was no corresponding item pos, that means user right-clicked on the blank
// pane
// We would want to show different options then, and not select any item
// The blank pane could be the recyclerView or the emptyView, so we need to register
// according to whichever one is visible
else if (mEmptyView.getVisibility() == View.VISIBLE) {
registerForContextMenu(mEmptyView);
mEmptyView.showContextMenu(e.getX(), e.getY());
unregisterForContextMenu(mEmptyView);
return true;
} else {
registerForContextMenu(mRecView);
mRecView.showContextMenu(e.getX(), e.getY());
unregisterForContextMenu(mRecView);
}
} finally {
event.recycle();
// We are registering for context menu here so long-press doesn't trigger this
// floating context menu, and then quickly unregister right afterwards
registerForContextMenu(holder.itemView);
mRecView.showContextMenuForChild(holder.itemView,
e.getX() - holder.itemView.getLeft(), e.getY() - holder.itemView.getTop());
unregisterForContextMenu(holder.itemView);
}
// If there was no corresponding item pos, that means user right-clicked on the blank
// pane
// We would want to show different options then, and not select any item
// The blank pane could be the recyclerView or the emptyView, so we need to register
// according to whichever one is visible
else if (mEmptyView.getVisibility() == View.VISIBLE) {
registerForContextMenu(mEmptyView);
mEmptyView.showContextMenu(e.getX(), e.getY());
unregisterForContextMenu(mEmptyView);
return true;
} else {
registerForContextMenu(mRecView);
mRecView.showContextMenu(e.getX(), e.getY());
unregisterForContextMenu(mRecView);
}
return true;
}
@@ -1554,81 +1550,6 @@ public class DirectoryFragment extends Fragment
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.
*/
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
// the mouse click. ACTION_UP event doesn't have information regarding the button (primary
// vs. secondary), so we have to save that somewhere first from ACTION_DOWN, and then reuse
// it later. The ACTION_DOWN event doesn't get forwarded to GestureListener, so we have open
// up a public set method to set it.
private int mLastButtonState = -1;
public void setLastButtonState(int state) {
mLastButtonState = state;
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
// Single tap logic:
// We first see if it's a mouse event, and if it was right click by checking on
// @{code ListeningGestureDetector#mLastButtonState}
// If the selection manager is active, it gets first whack at handling tap
// events. Otherwise, tap events are routed to the target DocumentHolder.
if (Events.isMouseEvent(e) && mLastButtonState == MotionEvent.BUTTON_SECONDARY) {
mLastButtonState = -1;
return onRightClick(e);
}
final MotionInputEvent event = MotionInputEvent.obtain(e, mRecView);
try {
boolean handled = mSelectionManager.onSingleTapUp(event);
if (handled) {
return handled;
}
// Give the DocumentHolder a crack at the event.
DocumentHolder holder = DirectoryFragment.this.getTarget(event);
if (holder != null) {
handled = holder.onSingleTapUp(e);
}
return handled;
} finally {
event.recycle();
}
}
@Override
public void onLongPress(MotionEvent e) {
// Long-press events get routed directly to the selection manager. They can be
// changed to route through the DocumentHolder if necessary.
final MotionInputEvent event = MotionInputEvent.obtain(e, mRecView);
try {
mSelectionManager.onLongPress(event);
} finally {
event.recycle();
}
}
@Override
public boolean onDoubleTap(MotionEvent e) {
// Double-tap events are handled directly by the DirectoryFragment. They can be changed
// to route through the DocumentHolder if necessary.
final MotionInputEvent event = MotionInputEvent.obtain(e, mRecView);
return DirectoryFragment.this.onDoubleTap(event);
}
public boolean onRightClick(MotionEvent e) {
return DirectoryFragment.this.onRightClick(e);
}
}
public static void showDirectory(
FragmentManager fm, RootInfo root, DocumentInfo doc, int anim) {
create(fm, TYPE_NORMAL, root, doc, null, anim);

View File

@@ -0,0 +1,118 @@
/*
* 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.support.v7.widget.RecyclerView;
import android.view.GestureDetector;
import android.view.MotionEvent;
import com.android.documentsui.Events;
import com.android.documentsui.Events.MotionInputEvent;
import java.util.function.Function;
import java.util.function.Predicate;
/**
* The gesture listener for items in the directly list, interprets gestures, and sends the
* events to the target DocumentHolder, whence they are routed to the appropriate listener.
*/
final 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
// the mouse click. ACTION_UP event doesn't have information regarding the button (primary
// vs. secondary), so we have to save that somewhere first from ACTION_DOWN, and then reuse
// it later. The ACTION_DOWN event doesn't get forwarded to GestureListener, so we have open
// up a public set method to set it.
private int mLastButtonState = -1;
private MultiSelectManager mSelectionMgr;
private RecyclerView mRecView;
private Function<MotionInputEvent, DocumentHolder> mDocFinder;
private Predicate<MotionInputEvent> mDoubleTapHandler;
private Predicate<MotionInputEvent> mRightClickHandler;
public GestureListener(
MultiSelectManager selectionMgr,
RecyclerView recView,
Function<MotionInputEvent, DocumentHolder> docFinder,
Predicate<MotionInputEvent> doubleTapHandler,
Predicate<MotionInputEvent> rightClickHandler) {
mSelectionMgr = selectionMgr;
mRecView = recView;
mDocFinder = docFinder;
mDoubleTapHandler = doubleTapHandler;
mRightClickHandler = rightClickHandler;
}
public void setLastButtonState(int state) {
mLastButtonState = state;
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
// Single tap logic:
// We first see if it's a mouse event, and if it was right click by checking on
// @{code ListeningGestureDetector#mLastButtonState}
// If the selection manager is active, it gets first whack at handling tap
// events. Otherwise, tap events are routed to the target DocumentHolder.
if (Events.isMouseEvent(e) && mLastButtonState == MotionEvent.BUTTON_SECONDARY) {
mLastButtonState = -1;
return onRightClick(e);
}
try (MotionInputEvent event = MotionInputEvent.obtain(e, mRecView)) {
boolean handled = mSelectionMgr.onSingleTapUp(event);
if (handled) {
return handled;
}
// Give the DocumentHolder a crack at the event.
DocumentHolder holder = mDocFinder.apply(event);
if (holder != null) {
handled = holder.onSingleTapUp(e);
}
return handled;
}
}
@Override
public void onLongPress(MotionEvent e) {
// Long-press events get routed directly to the selection manager. They can be
// changed to route through the DocumentHolder if necessary.
try (MotionInputEvent event = MotionInputEvent.obtain(e, mRecView)) {
mSelectionMgr.onLongPress(event);
}
}
@Override
public boolean onDoubleTap(MotionEvent e) {
// Double-tap events are handled directly by the DirectoryFragment. They can be changed
// to route through the DocumentHolder if necessary.
try (MotionInputEvent event = MotionInputEvent.obtain(e, mRecView)) {
return mDoubleTapHandler.test(event);
}
}
public boolean onRightClick(MotionEvent e) {
try (MotionInputEvent event = MotionInputEvent.obtain(e, mRecView)) {
return mRightClickHandler.test(event);
}
}
}

View File

@@ -26,7 +26,6 @@ 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

View File

@@ -65,39 +65,13 @@ public final class MultiSelectManager {
private Range mRanger;
private boolean mSingleSelect;
/**
* @param mode Selection single or multiple selection mode.
* @param initialSelection selection state probably preserved in external state.
*/
public MultiSelectManager(
final RecyclerView recyclerView,
DocumentsAdapter adapter,
@SelectionMode int mode,
@Nullable Selection initialSelection) {
this(adapter, mode, initialSelection);
}
/**
* Constructs a new instance with {@code adapter} and {@code helper}.
* @param runtimeSelectionEnvironment
* @hide
*/
@VisibleForTesting
MultiSelectManager(
DocumentsAdapter adapter,
@SelectionMode int mode,
@Nullable Selection initialSelection) {
public MultiSelectManager(DocumentsAdapter adapter, @SelectionMode int mode) {
assert(adapter != null);
mAdapter = adapter;
mSingleSelect = mode == MODE_SINGLE;
if (initialSelection != null) {
mSelection.copyFrom(initialSelection);
}
mAdapter.registerAdapterDataObserver(
new RecyclerView.AdapterDataObserver() {

View File

@@ -46,10 +46,11 @@ public class MultiSelectManagerTest extends AndroidTestCase {
private TestCallback mCallback;
private TestDocumentsAdapter mAdapter;
@Override
public void setUp() throws Exception {
mCallback = new TestCallback();
mAdapter = new TestDocumentsAdapter(items);
mManager = new MultiSelectManager(mAdapter, MultiSelectManager.MODE_MULTIPLE, null);
mManager = new MultiSelectManager(mAdapter, MultiSelectManager.MODE_MULTIPLE);
mManager.addCallback(mCallback);
}
@@ -173,7 +174,7 @@ public class MultiSelectManagerTest extends AndroidTestCase {
}
public void testSingleSelectMode() {
mManager = new MultiSelectManager(mAdapter, MultiSelectManager.MODE_SINGLE, null);
mManager = new MultiSelectManager(mAdapter, MultiSelectManager.MODE_SINGLE);
mManager.addCallback(mCallback);
longPress(20);
tap(13);
@@ -181,7 +182,7 @@ public class MultiSelectManagerTest extends AndroidTestCase {
}
public void testSingleSelectMode_ShiftTap() {
mManager = new MultiSelectManager(mAdapter, MultiSelectManager.MODE_SINGLE, null);
mManager = new MultiSelectManager(mAdapter, MultiSelectManager.MODE_SINGLE);
mManager.addCallback(mCallback);
longPress(13);
shiftTap(20);
@@ -228,7 +229,7 @@ public class MultiSelectManagerTest extends AndroidTestCase {
}
public void testRangeSelection_singleSelect() {
mManager = new MultiSelectManager(mAdapter, MultiSelectManager.MODE_SINGLE, null);
mManager = new MultiSelectManager(mAdapter, MultiSelectManager.MODE_SINGLE);
mManager.addCallback(mCallback);
mManager.startRangeSelection(11);
mManager.snapRangeSelection(19);