Loading src/com/android/documentsui/Events.java +3 −5 Original line number Original line Diff line number Diff line Loading @@ -111,17 +111,15 @@ public final class Events { public static final class MotionInputEvent implements InputEvent { public static final class MotionInputEvent implements InputEvent { private final MotionEvent mEvent; private final MotionEvent mEvent; private final RecyclerView mView; private final int mPosition; private final int mPosition; public MotionInputEvent(MotionEvent event, RecyclerView view) { public MotionInputEvent(MotionEvent event, RecyclerView view) { mEvent = event; mEvent = event; mView = view; // Consider determining position lazily as an optimization. // Consider determining position lazily as an optimization. View child = mView.findChildViewUnder(mEvent.getX(), mEvent.getY()); View child = view.findChildViewUnder(mEvent.getX(), mEvent.getY()); mPosition = (child!= null) mPosition = (child!= null) ? mView.getChildAdapterPosition(child) ? view.getChildAdapterPosition(child) : RecyclerView.NO_POSITION; : RecyclerView.NO_POSITION; } } Loading src/com/android/documentsui/dirlist/DirectoryFragment.java +65 −37 Original line number Original line Diff line number Diff line Loading @@ -84,6 +84,7 @@ import com.android.documentsui.DocumentClipper; import com.android.documentsui.DocumentsActivity; import com.android.documentsui.DocumentsActivity; import com.android.documentsui.DocumentsApplication; import com.android.documentsui.DocumentsApplication; import com.android.documentsui.Events; import com.android.documentsui.Events; import com.android.documentsui.Events.MotionInputEvent; import com.android.documentsui.Menus; import com.android.documentsui.Menus; import com.android.documentsui.MessageBar; import com.android.documentsui.MessageBar; import com.android.documentsui.MimePredicate; import com.android.documentsui.MimePredicate; Loading Loading @@ -138,7 +139,7 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi private Model mModel; private Model mModel; private MultiSelectManager mSelectionManager; private MultiSelectManager mSelectionManager; private Model.UpdateListener mModelUpdateListener = new ModelUpdateListener(); private Model.UpdateListener mModelUpdateListener = new ModelUpdateListener(); private ItemClickListener mItemClickListener = new ItemClickListener(); private ItemEventListener mItemEventListener = new ItemEventListener(); private IconHelper mIconHelper; private IconHelper mIconHelper; Loading Loading @@ -297,19 +298,7 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi mRecView.setAdapter(mAdapter); mRecView.setAdapter(mAdapter); GestureDetector.SimpleOnGestureListener listener = GestureListener listener = new GestureListener(); 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); } }; final GestureDetector detector = new GestureDetector(this.getContext(), listener); final GestureDetector detector = new GestureDetector(this.getContext(), listener); detector.setOnDoubleTapListener(listener); detector.setOnDoubleTapListener(listener); Loading Loading @@ -466,22 +455,8 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi operationType); 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) { protected boolean onDoubleTap(MotionEvent e) { if (Events.isMouseEvent(e)) { if (Events.isMouseEvent(e)) { Log.d(TAG, "Handling double tap from mouse."); String id = getModelId(e); String id = getModelId(e); if (id != null) { if (id != null) { return handleViewItem(id); return handleViewItem(id); Loading Loading @@ -926,7 +901,7 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi @Override @Override public void initDocumentHolder(DocumentHolder holder) { public void initDocumentHolder(DocumentHolder holder) { holder.addClickListener(mItemClickListener); holder.addEventListener(mItemEventListener); holder.addOnKeyListener(mSelectionManager); holder.addOnKeyListener(mSelectionManager); } } Loading Loading @@ -1330,15 +1305,18 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi return mSelectionManager.getSelection().contains(modelId); return mSelectionManager.getSelection().contains(modelId); } } private class ItemClickListener implements DocumentHolder.ClickListener { private class ItemEventListener implements DocumentHolder.EventListener { @Override @Override public void onClick(DocumentHolder doc) { public boolean onActivate(DocumentHolder doc) { if (mSelectionManager.hasSelection()) { mSelectionManager.toggleSelection(doc.modelId); mSelectionManager.setSelectionRangeBegin(doc.getAdapterPosition()); } else { handleViewItem(doc.modelId); handleViewItem(doc.modelId); return true; } } @Override public boolean onSelect(DocumentHolder doc) { mSelectionManager.toggleSelection(doc.modelId); mSelectionManager.setSelectionRangeBegin(doc.getAdapterPosition()); return true; } } } } Loading Loading @@ -1366,4 +1344,54 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi showErrorView(); 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; } } } } } src/com/android/documentsui/dirlist/DocumentHolder.java +57 −12 Original line number Original line Diff line number Diff line Loading @@ -16,17 +16,21 @@ package com.android.documentsui.dirlist; package com.android.documentsui.dirlist; import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.internal.util.Preconditions.checkState; import static com.android.internal.util.Preconditions.checkState; import android.content.Context; import android.content.Context; import android.database.Cursor; import android.database.Cursor; import android.graphics.Rect; import android.support.annotation.Nullable; import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView; import android.view.KeyEvent; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup; import com.android.documentsui.Events; import com.android.documentsui.R; import com.android.documentsui.R; import com.android.documentsui.State; import com.android.documentsui.State; Loading @@ -41,8 +45,9 @@ public abstract class DocumentHolder final boolean mAlwaysShowSummary; final boolean mAlwaysShowSummary; final Context mContext; final Context mContext; private ListDocumentHolder.ClickListener mClickListener; DocumentHolder.EventListener mEventListener; private View.OnKeyListener mKeyListener; private View.OnKeyListener mKeyListener; private View mSelectionHotspot; public DocumentHolder(Context context, ViewGroup parent, int layout) { public DocumentHolder(Context context, ViewGroup parent, int layout) { this(context, inflateLayout(context, parent, layout)); this(context, inflateLayout(context, parent, layout)); Loading @@ -58,6 +63,8 @@ public abstract class DocumentHolder mDefaultItemColor = context.getColor(R.color.item_doc_background); mDefaultItemColor = context.getColor(R.color.item_doc_background); mSelectedItemColor = context.getColor(R.color.item_doc_background_selected); mSelectedItemColor = context.getColor(R.color.item_doc_background_selected); mAlwaysShowSummary = context.getResources().getBoolean(R.bool.always_show_summary); mAlwaysShowSummary = context.getResources().getBoolean(R.bool.always_show_summary); mSelectionHotspot = itemView.findViewById(R.id.icon_check); } } /** /** Loading @@ -75,23 +82,21 @@ public abstract class DocumentHolder @Override @Override public boolean onKey(View v, int keyCode, KeyEvent event) { 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. // Intercept enter key-up events, and treat them as clicks. Forward other events. if (event.getAction() == KeyEvent.ACTION_UP && if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_ENTER) { keyCode == KeyEvent.KEYCODE_ENTER) { return mEventListener.onActivate(this); if (mClickListener != null) { mClickListener.onClick(this); } return true; } else if (mKeyListener != null) { } else if (mKeyListener != null) { return mKeyListener.onKey(v, keyCode, event); return mKeyListener.onKey(v, keyCode, event); } } return false; 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. // Just handle one for now; switch to a list if necessary. checkState(mClickListener == null); checkState(mEventListener == null); mClickListener = listener; mEventListener = listener; } } public void addOnKeyListener(View.OnKeyListener listener) { public void addOnKeyListener(View.OnKeyListener listener) { Loading @@ -104,6 +109,33 @@ public abstract class DocumentHolder setEnabledRecursive(itemView, enabled); 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) { static void setEnabledRecursive(View itemView, boolean enabled) { if (itemView == null) return; if (itemView == null) return; if (itemView.isEnabled() == enabled) return; if (itemView.isEnabled() == enabled) return; Loading @@ -122,7 +154,20 @@ public abstract class DocumentHolder return inflater.inflate(layout, parent, false); 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); } } } } src/com/android/documentsui/dirlist/MultiSelectManager.java +1 −27 Original line number Original line Diff line number Diff line Loading @@ -32,7 +32,6 @@ import android.util.Log; import android.util.SparseArray; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.SparseBooleanArray; import android.util.SparseIntArray; import android.util.SparseIntArray; import android.view.GestureDetector; import android.view.KeyEvent; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.MotionEvent; import android.view.View; import android.view.View; Loading Loading @@ -90,29 +89,10 @@ public final class MultiSelectManager implements View.OnKeyListener { mBandManager = new BandController(); 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( recyclerView.addOnItemTouchListener( new RecyclerView.OnItemTouchListener() { new RecyclerView.OnItemTouchListener() { @Override @Override public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { detector.onTouchEvent(e); if (mBandManager != null) { if (mBandManager != null) { return mBandManager.handleEvent(new MotionInputEvent(e, recyclerView)); return mBandManager.handleEvent(new MotionInputEvent(e, recyclerView)); } } Loading Loading @@ -287,13 +267,7 @@ public final class MultiSelectManager implements View.OnKeyListener { boolean onSingleTapUp(InputEvent input) { boolean onSingleTapUp(InputEvent input) { if (DEBUG) Log.d(TAG, "Processing tap event."); if (DEBUG) Log.d(TAG, "Processing tap event."); if (!hasSelection()) { if (!hasSelection()) { // if this is a mouse click on an item, start selection mode. // No selection active - do nothing. // TODO: && input.isPrimaryButtonPressed(), but it is returning false. if (input.isOverItem() && input.isMouseEvent()) { int position = input.getItemPosition(); toggleSelection(position); setSelectionRangeBegin(position); } return false; return false; } } Loading tests/src/com/android/documentsui/dirlist/DocumentHolderTest.java 0 → 100644 +134 −0 Original line number Original line 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.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; } } } Loading
src/com/android/documentsui/Events.java +3 −5 Original line number Original line Diff line number Diff line Loading @@ -111,17 +111,15 @@ public final class Events { public static final class MotionInputEvent implements InputEvent { public static final class MotionInputEvent implements InputEvent { private final MotionEvent mEvent; private final MotionEvent mEvent; private final RecyclerView mView; private final int mPosition; private final int mPosition; public MotionInputEvent(MotionEvent event, RecyclerView view) { public MotionInputEvent(MotionEvent event, RecyclerView view) { mEvent = event; mEvent = event; mView = view; // Consider determining position lazily as an optimization. // Consider determining position lazily as an optimization. View child = mView.findChildViewUnder(mEvent.getX(), mEvent.getY()); View child = view.findChildViewUnder(mEvent.getX(), mEvent.getY()); mPosition = (child!= null) mPosition = (child!= null) ? mView.getChildAdapterPosition(child) ? view.getChildAdapterPosition(child) : RecyclerView.NO_POSITION; : RecyclerView.NO_POSITION; } } Loading
src/com/android/documentsui/dirlist/DirectoryFragment.java +65 −37 Original line number Original line Diff line number Diff line Loading @@ -84,6 +84,7 @@ import com.android.documentsui.DocumentClipper; import com.android.documentsui.DocumentsActivity; import com.android.documentsui.DocumentsActivity; import com.android.documentsui.DocumentsApplication; import com.android.documentsui.DocumentsApplication; import com.android.documentsui.Events; import com.android.documentsui.Events; import com.android.documentsui.Events.MotionInputEvent; import com.android.documentsui.Menus; import com.android.documentsui.Menus; import com.android.documentsui.MessageBar; import com.android.documentsui.MessageBar; import com.android.documentsui.MimePredicate; import com.android.documentsui.MimePredicate; Loading Loading @@ -138,7 +139,7 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi private Model mModel; private Model mModel; private MultiSelectManager mSelectionManager; private MultiSelectManager mSelectionManager; private Model.UpdateListener mModelUpdateListener = new ModelUpdateListener(); private Model.UpdateListener mModelUpdateListener = new ModelUpdateListener(); private ItemClickListener mItemClickListener = new ItemClickListener(); private ItemEventListener mItemEventListener = new ItemEventListener(); private IconHelper mIconHelper; private IconHelper mIconHelper; Loading Loading @@ -297,19 +298,7 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi mRecView.setAdapter(mAdapter); mRecView.setAdapter(mAdapter); GestureDetector.SimpleOnGestureListener listener = GestureListener listener = new GestureListener(); 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); } }; final GestureDetector detector = new GestureDetector(this.getContext(), listener); final GestureDetector detector = new GestureDetector(this.getContext(), listener); detector.setOnDoubleTapListener(listener); detector.setOnDoubleTapListener(listener); Loading Loading @@ -466,22 +455,8 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi operationType); 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) { protected boolean onDoubleTap(MotionEvent e) { if (Events.isMouseEvent(e)) { if (Events.isMouseEvent(e)) { Log.d(TAG, "Handling double tap from mouse."); String id = getModelId(e); String id = getModelId(e); if (id != null) { if (id != null) { return handleViewItem(id); return handleViewItem(id); Loading Loading @@ -926,7 +901,7 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi @Override @Override public void initDocumentHolder(DocumentHolder holder) { public void initDocumentHolder(DocumentHolder holder) { holder.addClickListener(mItemClickListener); holder.addEventListener(mItemEventListener); holder.addOnKeyListener(mSelectionManager); holder.addOnKeyListener(mSelectionManager); } } Loading Loading @@ -1330,15 +1305,18 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi return mSelectionManager.getSelection().contains(modelId); return mSelectionManager.getSelection().contains(modelId); } } private class ItemClickListener implements DocumentHolder.ClickListener { private class ItemEventListener implements DocumentHolder.EventListener { @Override @Override public void onClick(DocumentHolder doc) { public boolean onActivate(DocumentHolder doc) { if (mSelectionManager.hasSelection()) { mSelectionManager.toggleSelection(doc.modelId); mSelectionManager.setSelectionRangeBegin(doc.getAdapterPosition()); } else { handleViewItem(doc.modelId); handleViewItem(doc.modelId); return true; } } @Override public boolean onSelect(DocumentHolder doc) { mSelectionManager.toggleSelection(doc.modelId); mSelectionManager.setSelectionRangeBegin(doc.getAdapterPosition()); return true; } } } } Loading Loading @@ -1366,4 +1344,54 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi showErrorView(); 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; } } } } }
src/com/android/documentsui/dirlist/DocumentHolder.java +57 −12 Original line number Original line Diff line number Diff line Loading @@ -16,17 +16,21 @@ package com.android.documentsui.dirlist; package com.android.documentsui.dirlist; import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.internal.util.Preconditions.checkState; import static com.android.internal.util.Preconditions.checkState; import android.content.Context; import android.content.Context; import android.database.Cursor; import android.database.Cursor; import android.graphics.Rect; import android.support.annotation.Nullable; import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView; import android.view.KeyEvent; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup; import com.android.documentsui.Events; import com.android.documentsui.R; import com.android.documentsui.R; import com.android.documentsui.State; import com.android.documentsui.State; Loading @@ -41,8 +45,9 @@ public abstract class DocumentHolder final boolean mAlwaysShowSummary; final boolean mAlwaysShowSummary; final Context mContext; final Context mContext; private ListDocumentHolder.ClickListener mClickListener; DocumentHolder.EventListener mEventListener; private View.OnKeyListener mKeyListener; private View.OnKeyListener mKeyListener; private View mSelectionHotspot; public DocumentHolder(Context context, ViewGroup parent, int layout) { public DocumentHolder(Context context, ViewGroup parent, int layout) { this(context, inflateLayout(context, parent, layout)); this(context, inflateLayout(context, parent, layout)); Loading @@ -58,6 +63,8 @@ public abstract class DocumentHolder mDefaultItemColor = context.getColor(R.color.item_doc_background); mDefaultItemColor = context.getColor(R.color.item_doc_background); mSelectedItemColor = context.getColor(R.color.item_doc_background_selected); mSelectedItemColor = context.getColor(R.color.item_doc_background_selected); mAlwaysShowSummary = context.getResources().getBoolean(R.bool.always_show_summary); mAlwaysShowSummary = context.getResources().getBoolean(R.bool.always_show_summary); mSelectionHotspot = itemView.findViewById(R.id.icon_check); } } /** /** Loading @@ -75,23 +82,21 @@ public abstract class DocumentHolder @Override @Override public boolean onKey(View v, int keyCode, KeyEvent event) { 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. // Intercept enter key-up events, and treat them as clicks. Forward other events. if (event.getAction() == KeyEvent.ACTION_UP && if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_ENTER) { keyCode == KeyEvent.KEYCODE_ENTER) { return mEventListener.onActivate(this); if (mClickListener != null) { mClickListener.onClick(this); } return true; } else if (mKeyListener != null) { } else if (mKeyListener != null) { return mKeyListener.onKey(v, keyCode, event); return mKeyListener.onKey(v, keyCode, event); } } return false; 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. // Just handle one for now; switch to a list if necessary. checkState(mClickListener == null); checkState(mEventListener == null); mClickListener = listener; mEventListener = listener; } } public void addOnKeyListener(View.OnKeyListener listener) { public void addOnKeyListener(View.OnKeyListener listener) { Loading @@ -104,6 +109,33 @@ public abstract class DocumentHolder setEnabledRecursive(itemView, enabled); 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) { static void setEnabledRecursive(View itemView, boolean enabled) { if (itemView == null) return; if (itemView == null) return; if (itemView.isEnabled() == enabled) return; if (itemView.isEnabled() == enabled) return; Loading @@ -122,7 +154,20 @@ public abstract class DocumentHolder return inflater.inflate(layout, parent, false); 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); } } } }
src/com/android/documentsui/dirlist/MultiSelectManager.java +1 −27 Original line number Original line Diff line number Diff line Loading @@ -32,7 +32,6 @@ import android.util.Log; import android.util.SparseArray; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.SparseBooleanArray; import android.util.SparseIntArray; import android.util.SparseIntArray; import android.view.GestureDetector; import android.view.KeyEvent; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.MotionEvent; import android.view.View; import android.view.View; Loading Loading @@ -90,29 +89,10 @@ public final class MultiSelectManager implements View.OnKeyListener { mBandManager = new BandController(); 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( recyclerView.addOnItemTouchListener( new RecyclerView.OnItemTouchListener() { new RecyclerView.OnItemTouchListener() { @Override @Override public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { detector.onTouchEvent(e); if (mBandManager != null) { if (mBandManager != null) { return mBandManager.handleEvent(new MotionInputEvent(e, recyclerView)); return mBandManager.handleEvent(new MotionInputEvent(e, recyclerView)); } } Loading Loading @@ -287,13 +267,7 @@ public final class MultiSelectManager implements View.OnKeyListener { boolean onSingleTapUp(InputEvent input) { boolean onSingleTapUp(InputEvent input) { if (DEBUG) Log.d(TAG, "Processing tap event."); if (DEBUG) Log.d(TAG, "Processing tap event."); if (!hasSelection()) { if (!hasSelection()) { // if this is a mouse click on an item, start selection mode. // No selection active - do nothing. // TODO: && input.isPrimaryButtonPressed(), but it is returning false. if (input.isOverItem() && input.isMouseEvent()) { int position = input.getItemPosition(); toggleSelection(position); setSelectionRangeBegin(position); } return false; return false; } } Loading
tests/src/com/android/documentsui/dirlist/DocumentHolderTest.java 0 → 100644 +134 −0 Original line number Original line 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.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; } } }