Merge "Rework selection handling for items in the DirectoryFragment."
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Events.java b/packages/DocumentsUI/src/com/android/documentsui/Events.java
index 1b5b60de..10a78b9 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Events.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Events.java
@@ -111,17 +111,15 @@
public static final class MotionInputEvent implements InputEvent {
private final MotionEvent mEvent;
- private final RecyclerView mView;
private final int mPosition;
public MotionInputEvent(MotionEvent event, RecyclerView view) {
mEvent = event;
- mView = view;
// Consider determining position lazily as an optimization.
- View child = mView.findChildViewUnder(mEvent.getX(), mEvent.getY());
- mPosition = (child != null)
- ? mView.getChildAdapterPosition(child)
+ View child = view.findChildViewUnder(mEvent.getX(), mEvent.getY());
+ mPosition = (child!= null)
+ ? view.getChildAdapterPosition(child)
: RecyclerView.NO_POSITION;
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 580e2d8..053b618 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -84,6 +84,7 @@
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.Menus;
import com.android.documentsui.MessageBar;
import com.android.documentsui.MimePredicate;
@@ -138,7 +139,7 @@
private Model mModel;
private MultiSelectManager mSelectionManager;
private Model.UpdateListener mModelUpdateListener = new ModelUpdateListener();
- private ItemClickListener mItemClickListener = new ItemClickListener();
+ private ItemEventListener mItemEventListener = new ItemEventListener();
private IconHelper mIconHelper;
@@ -297,19 +298,7 @@
mRecView.setAdapter(mAdapter);
- GestureDetector.SimpleOnGestureListener listener =
- new GestureDetector.SimpleOnGestureListener() {
- @Override
- public boolean onSingleTapUp(MotionEvent e) {
- return DirectoryFragment.this.onSingleTapUp(e);
- }
- @Override
- public boolean onDoubleTap(MotionEvent e) {
- Log.d(TAG, "Handling double tap.");
- return DirectoryFragment.this.onDoubleTap(e);
- }
- };
-
+ GestureListener listener = new GestureListener();
final GestureDetector detector = new GestureDetector(this.getContext(), listener);
detector.setOnDoubleTapListener(listener);
@@ -466,22 +455,8 @@
operationType);
}
- private boolean onSingleTapUp(MotionEvent e) {
- // Only respond to touch events. Single-click mouse events are selection events and are
- // handled by the selection manager. Tap events that occur while the selection manager is
- // active are also selection events.
- if (Events.isTouchEvent(e) && !mSelectionManager.hasSelection()) {
- String id = getModelId(e);
- if (id != null) {
- return handleViewItem(id);
- }
- }
- return false;
- }
-
protected boolean onDoubleTap(MotionEvent e) {
if (Events.isMouseEvent(e)) {
- Log.d(TAG, "Handling double tap from mouse.");
String id = getModelId(e);
if (id != null) {
return handleViewItem(id);
@@ -926,7 +901,7 @@
@Override
public void initDocumentHolder(DocumentHolder holder) {
- holder.addClickListener(mItemClickListener);
+ holder.addEventListener(mItemEventListener);
holder.addOnKeyListener(mSelectionManager);
}
@@ -1330,15 +1305,18 @@
return mSelectionManager.getSelection().contains(modelId);
}
- private class ItemClickListener implements DocumentHolder.ClickListener {
+ private class ItemEventListener implements DocumentHolder.EventListener {
@Override
- public void onClick(DocumentHolder doc) {
- if (mSelectionManager.hasSelection()) {
- mSelectionManager.toggleSelection(doc.modelId);
- mSelectionManager.setSelectionRangeBegin(doc.getAdapterPosition());
- } else {
- handleViewItem(doc.modelId);
- }
+ public boolean onActivate(DocumentHolder doc) {
+ handleViewItem(doc.modelId);
+ return true;
+ }
+
+ @Override
+ public boolean onSelect(DocumentHolder doc) {
+ mSelectionManager.toggleSelection(doc.modelId);
+ mSelectionManager.setSelectionRangeBegin(doc.getAdapterPosition());
+ return true;
}
}
@@ -1366,4 +1344,54 @@
showErrorView();
}
}
+
+ /**
+ * 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 {
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ // Single tap logic:
+ // If the selection manager is active, it gets first whack at handling tap
+ // events. Otherwise, tap events are routed to the target DocumentHolder.
+ boolean handled = mSelectionManager.onSingleTapUp(
+ new MotionInputEvent(e, mRecView));
+
+ if (handled) {
+ return handled;
+ }
+
+ // Give the DocumentHolder a crack at the event.
+ DocumentHolder holder = getTarget(e);
+ 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.
+ mSelectionManager.onLongPress(new MotionInputEvent(e, mRecView));
+ }
+
+ @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.
+ return DirectoryFragment.this.onDoubleTap(e);
+ }
+
+ private @Nullable DocumentHolder getTarget(MotionEvent e) {
+ View childView = mRecView.findChildViewUnder(e.getX(), e.getY());
+ if (childView != null) {
+ return (DocumentHolder) mRecView.getChildViewHolder(childView);
+ } else {
+ return null;
+ }
+ }
+ }
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java
index 9ac9057..8acf1af 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java
@@ -16,17 +16,21 @@
package com.android.documentsui.dirlist;
+import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.internal.util.Preconditions.checkState;
import android.content.Context;
import android.database.Cursor;
+import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.view.KeyEvent;
import android.view.LayoutInflater;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import com.android.documentsui.Events;
import com.android.documentsui.R;
import com.android.documentsui.State;
@@ -41,8 +45,9 @@
final boolean mAlwaysShowSummary;
final Context mContext;
- private ListDocumentHolder.ClickListener mClickListener;
+ DocumentHolder.EventListener mEventListener;
private View.OnKeyListener mKeyListener;
+ private View mSelectionHotspot;
public DocumentHolder(Context context, ViewGroup parent, int layout) {
this(context, inflateLayout(context, parent, layout));
@@ -58,6 +63,8 @@
mDefaultItemColor = context.getColor(R.color.item_doc_background);
mSelectedItemColor = context.getColor(R.color.item_doc_background_selected);
mAlwaysShowSummary = context.getResources().getBoolean(R.bool.always_show_summary);
+
+ mSelectionHotspot = itemView.findViewById(R.id.icon_check);
}
/**
@@ -75,23 +82,21 @@
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
+ // Event listener should always be set.
+ checkNotNull(mEventListener);
// Intercept enter key-up events, and treat them as clicks. Forward other events.
- if (event.getAction() == KeyEvent.ACTION_UP &&
- keyCode == KeyEvent.KEYCODE_ENTER) {
- if (mClickListener != null) {
- mClickListener.onClick(this);
- }
- return true;
+ if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_ENTER) {
+ return mEventListener.onActivate(this);
} else if (mKeyListener != null) {
return mKeyListener.onKey(v, keyCode, event);
}
return false;
}
- public void addClickListener(ListDocumentHolder.ClickListener listener) {
+ public void addEventListener(DocumentHolder.EventListener listener) {
// Just handle one for now; switch to a list if necessary.
- checkState(mClickListener == null);
- mClickListener = listener;
+ checkState(mEventListener == null);
+ mEventListener = listener;
}
public void addOnKeyListener(View.OnKeyListener listener) {
@@ -104,6 +109,33 @@
setEnabledRecursive(itemView, enabled);
}
+ public boolean onSingleTapUp(MotionEvent event) {
+ if (Events.isMouseEvent(event)) {
+ // Mouse clicks select.
+ // TODO: && input.isPrimaryButtonPressed(), but it is returning false.
+ if (mEventListener != null) {
+ return mEventListener.onSelect(this);
+ }
+ } else if (Events.isTouchEvent(event)) {
+ // Touch events select if they occur in the selection hotspot, otherwise they activate.
+ if (mEventListener == null) {
+ return false;
+ }
+
+ // Do everything in global coordinates - it makes things simpler.
+ Rect rect = new Rect();
+ mSelectionHotspot.getGlobalVisibleRect(rect);
+
+ // If the tap occurred within the icon rect, consider it a selection.
+ if (rect.contains((int)event.getRawX(), (int)event.getRawY())) {
+ return mEventListener.onSelect(this);
+ } else {
+ return mEventListener.onActivate(this);
+ }
+ }
+ return false;
+ }
+
static void setEnabledRecursive(View itemView, boolean enabled) {
if (itemView == null) return;
if (itemView.isEnabled() == enabled) return;
@@ -122,7 +154,20 @@
return inflater.inflate(layout, parent, false);
}
- interface ClickListener {
- public void onClick(DocumentHolder doc);
+ /**
+ * Implement this in order to be able to respond to events coming from DocumentHolders.
+ */
+ interface EventListener {
+ /**
+ * @param doc The target DocumentHolder
+ * @return Whether the event was handled.
+ */
+ public boolean onActivate(DocumentHolder doc);
+
+ /**
+ * @param doc The target DocumentHolder
+ * @return Whether the event was handled.
+ */
+ public boolean onSelect(DocumentHolder doc);
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
index d868fb4..9cbcf8c 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
@@ -32,7 +32,6 @@
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
-import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
@@ -90,29 +89,10 @@
mBandManager = new BandController();
}
- GestureDetector.SimpleOnGestureListener listener =
- new GestureDetector.SimpleOnGestureListener() {
- @Override
- public boolean onSingleTapUp(MotionEvent e) {
- return MultiSelectManager.this.onSingleTapUp(
- new MotionInputEvent(e, recyclerView));
- }
- @Override
- public void onLongPress(MotionEvent e) {
- MultiSelectManager.this.onLongPress(
- new MotionInputEvent(e, recyclerView));
- }
- };
-
- final GestureDetector detector = new GestureDetector(recyclerView.getContext(), listener);
- detector.setOnDoubleTapListener(listener);
-
recyclerView.addOnItemTouchListener(
new RecyclerView.OnItemTouchListener() {
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
- detector.onTouchEvent(e);
-
if (mBandManager != null) {
return mBandManager.handleEvent(new MotionInputEvent(e, recyclerView));
}
@@ -287,13 +267,7 @@
boolean onSingleTapUp(InputEvent input) {
if (DEBUG) Log.d(TAG, "Processing tap event.");
if (!hasSelection()) {
- // if this is a mouse click on an item, start selection mode.
- // TODO: && input.isPrimaryButtonPressed(), but it is returning false.
- if (input.isOverItem() && input.isMouseEvent()) {
- int position = input.getItemPosition();
- toggleSelection(position);
- setSelectionRangeBegin(position);
- }
+ // No selection active - do nothing.
return false;
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DocumentHolderTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DocumentHolderTest.java
new file mode 100644
index 0000000..16efc6e
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DocumentHolderTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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.database.Cursor;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.MotionEvent.PointerCoords;
+import android.view.MotionEvent.PointerProperties;
+
+import com.android.documentsui.R;
+import com.android.documentsui.State;
+
+@SmallTest
+public class DocumentHolderTest extends AndroidTestCase {
+
+ DocumentHolder mHolder;
+ TestListener mListener;
+
+ public void setUp() throws Exception {
+ Context context = getContext();
+ LayoutInflater inflater = LayoutInflater.from(context);
+ mHolder = new DocumentHolder(getContext(), inflater.inflate(R.layout.item_doc_list, null)) {
+ @Override
+ public void bind(Cursor cursor, String modelId, State state) {}
+ };
+
+ mListener = new TestListener();
+ mHolder.addEventListener(mListener);
+
+ mHolder.itemView.requestLayout();
+ mHolder.itemView.invalidate();
+ }
+
+ public void testClickActivates() {
+ click();
+ mListener.assertSelected();
+ }
+
+ public void testTapActivates() {
+ tap();
+ mListener.assertActivated();
+ }
+
+ public void click() {
+ mHolder.onSingleTapUp(createEvent(MotionEvent.TOOL_TYPE_MOUSE));
+ }
+
+ public void tap() {
+ mHolder.onSingleTapUp(createEvent(MotionEvent.TOOL_TYPE_FINGER));
+ }
+
+ public MotionEvent createEvent(int tooltype) {
+ long time = SystemClock.uptimeMillis();
+
+ PointerProperties properties[] = new PointerProperties[] {
+ new PointerProperties()
+ };
+ properties[0].toolType = tooltype;
+
+ PointerCoords coords[] = new PointerCoords[] {
+ new PointerCoords()
+ };
+
+ Rect rect = new Rect();
+ mHolder.itemView.getHitRect(rect);
+ coords[0].x = rect.left;
+ coords[0].y = rect.top;
+
+ return MotionEvent.obtain(
+ time, // down time
+ time, // event time
+ MotionEvent.ACTION_UP, // action
+ 1, // pointer count
+ properties, // pointer properties
+ coords, // pointer coords
+ 0, // metastate
+ 0, // button state
+ 0, // xprecision
+ 0, // yprecision
+ 0, // deviceid
+ 0, // edgeflags
+ 0, // source
+ 0 // flags
+ );
+ }
+
+ private class TestListener implements DocumentHolder.EventListener {
+ private boolean mActivated = false;
+ private boolean mSelected = false;
+
+ public void assertActivated() {
+ assertTrue(mActivated);
+ assertFalse(mSelected);
+ }
+
+ public void assertSelected() {
+ assertTrue(mSelected);
+ assertFalse(mActivated);
+ }
+
+ @Override
+ public boolean onActivate(DocumentHolder doc) {
+ mActivated = true;
+ return true;
+ }
+
+ @Override
+ public boolean onSelect(DocumentHolder doc) {
+ mSelected = true;
+ return true;
+ }
+
+ }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
index 7a3b6d4..d3ef9aa 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
@@ -23,7 +23,6 @@
import com.android.documentsui.TestInputEvent;
import com.android.documentsui.dirlist.MultiSelectManager.Selection;
-
import com.google.common.collect.Lists;
import java.util.ArrayList;
@@ -55,13 +54,21 @@
mManager.addCallback(mCallback);
}
- public void testMouseClick_StartsSelectionMode() {
- click(7);
+ public void testSelection() {
+ // Check selection.
+ mManager.toggleSelection(items.get(7));
assertSelection(items.get(7));
+ // Check deselection.
+ mManager.toggleSelection(items.get(7));
+ assertSelectionSize(0);
}
- public void testMouseClick_NotifiesSelectionChanged() {
- click(7);
+ public void testSelection_NotifiesSelectionChanged() {
+ // Selection should notify.
+ mManager.toggleSelection(items.get(7));
+ mCallback.assertSelectionChanged();
+ // Deselection should notify.
+ mManager.toggleSelection(items.get(7));
mCallback.assertSelectionChanged();
}