Loading src/com/android/documentsui/Events.java +9 −0 Original line number Original line Diff line number Diff line Loading @@ -106,6 +106,10 @@ public final class Events { /** Returns true if the action is the final release of a mouse or touch. */ /** Returns true if the action is the final release of a mouse or touch. */ boolean isActionUp(); boolean isActionUp(); /** Returns true if the action is neither the initial nor the final release of a mouse * or touch. */ boolean isActionMove(); // Eliminate the checked Exception from Autoclosable. // Eliminate the checked Exception from Autoclosable. @Override @Override public void close(); public void close(); Loading Loading @@ -219,6 +223,11 @@ public final class Events { return mEvent.getActionMasked() == MotionEvent.ACTION_UP; return mEvent.getActionMasked() == MotionEvent.ACTION_UP; } } @Override public boolean isActionMove() { return mEvent.getActionMasked() == MotionEvent.ACTION_MOVE; } @Override @Override public Point getOrigin() { public Point getOrigin() { return new Point((int) mEvent.getX(), (int) mEvent.getY()); return new Point((int) mEvent.getX(), (int) mEvent.getY()); Loading src/com/android/documentsui/dirlist/BandController.java +20 −42 Original line number Original line Diff line number Diff line Loading @@ -28,6 +28,7 @@ import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.support.annotation.VisibleForTesting; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.OnScrollListener; import android.util.Log; import android.util.Log; import android.util.SparseArray; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.SparseBooleanArray; Loading Loading @@ -55,7 +56,7 @@ import java.util.Set; * and {@link MultiSelectManager}. This class is responsible for rendering the band select * and {@link MultiSelectManager}. This class is responsible for rendering the band select * overlay and selecting overlaid items via MultiSelectManager. * overlay and selecting overlaid items via MultiSelectManager. */ */ public class BandController extends RecyclerView.OnScrollListener { public class BandController extends OnScrollListener { private static final String TAG = "BandController"; private static final String TAG = "BandController"; private static final int AUTOSCROLL_EDGE_HEIGHT = 1; private static final int AUTOSCROLL_EDGE_HEIGHT = 1; Loading @@ -79,26 +80,6 @@ public class BandController extends RecyclerView.OnScrollListener { DocumentsAdapter adapter, DocumentsAdapter adapter, MultiSelectManager selectionManager) { MultiSelectManager selectionManager) { this(new RuntimeSelectionEnvironment(view), adapter, selectionManager); this(new RuntimeSelectionEnvironment(view), adapter, selectionManager); view.addOnItemTouchListener( new RecyclerView.OnItemTouchListener() { @Override public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { try (InputEvent event = MotionInputEvent.obtain(e, view)) { return handleEvent(event); } } @Override public void onTouchEvent(RecyclerView rv, MotionEvent e) { if (Events.isMouseEvent(e)) { try (InputEvent event = MotionInputEvent.obtain(e, view)) { processInputEvent(event); } } } @Override public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {} }); } } private BandController( private BandController( Loading Loading @@ -199,26 +180,13 @@ public class BandController extends RecyclerView.OnScrollListener { mSelection = selection; mSelection = selection; } } private boolean handleEvent(InputEvent e) { boolean onInterceptTouchEvent(InputEvent e) { // Don't start, or extend bands on right click. if (e.isSecondaryButtonPressed()) { return false; } if (!e.isMouseEvent() && isActive()) { // Weird things happen if we keep up band select // when touch events happen. endBandSelect(); return false; } // b/23793622 notes the fact that we *never* receive ACTION_DOWN // b/23793622 notes the fact that we *never* receive ACTION_DOWN // events in onTouchEvent. Where it not for this issue, we'd // events in onTouchEvent. Where it not for this issue, we'd // push start handling down into handleInputEvent. // push start handling down into handleInputEvent. if (shouldStart(e)) { if (shouldStart(e)) { // endBandSelect is handled in handleInputEvent. startBandSelect(e.getOrigin()); startBandSelect(e.getOrigin()); } else if (isActive() && e.isActionUp()) { } else if (shouldStop(e)) { // Same issue here w b/23793622. The ACTION_UP event // Same issue here w b/23793622. The ACTION_UP event // is only evert dispatched to onTouchEvent when // is only evert dispatched to onTouchEvent when // there is some associated motion. If a user taps // there is some associated motion. If a user taps Loading @@ -226,9 +194,7 @@ public class BandController extends RecyclerView.OnScrollListener { // started BUT not ended. Causing phantom // started BUT not ended. Causing phantom // bands to appear when the user later clicks to start // bands to appear when the user later clicks to start // band select. // band select. if (e.isMouseEvent()) { endBandSelect(); processInputEvent(e); } } } return isActive(); return isActive(); Loading @@ -248,14 +214,26 @@ public class BandController extends RecyclerView.OnScrollListener { } } } } boolean shouldStart(InputEvent e) { public boolean shouldStart(InputEvent e) { // Don't start, or extend bands on right click. if (e.isSecondaryButtonPressed()) { return false; } if (!e.isMouseEvent() && isActive()) { // Weird things happen if we keep up band select // when touch events happen. endBandSelect(); return false; } return !isActive() return !isActive() && e.isActionDown() // the initial button press && e.isActionDown() // the initial button press && mAdapter.getItemCount() > 0 && mAdapter.getItemCount() > 0 && e.getItemPosition() == RecyclerView.NO_ID; // in empty space && e.getItemPosition() == RecyclerView.NO_ID; // in empty space } } boolean shouldStop(InputEvent input) { public boolean shouldStop(InputEvent input) { return isActive() return isActive() && input.isMouseEvent() && input.isMouseEvent() && input.isActionUp(); && input.isActionUp(); Loading @@ -265,7 +243,7 @@ public class BandController extends RecyclerView.OnScrollListener { * Processes a MotionEvent by starting, ending, or resizing the band select overlay. * Processes a MotionEvent by starting, ending, or resizing the band select overlay. * @param input * @param input */ */ private void processInputEvent(InputEvent input) { void onTouchEvent(InputEvent input) { assert(input.isMouseEvent()); assert(input.isMouseEvent()); if (shouldStop(input)) { if (shouldStop(input)) { Loading src/com/android/documentsui/dirlist/DirectoryFragment.java +32 −83 Original line number Original line Diff line number Diff line Loading @@ -45,7 +45,6 @@ import android.os.Handler; import android.os.Parcelable; import android.os.Parcelable; import android.provider.DocumentsContract; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Document; import android.support.v13.view.DragStartHelper; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.GridLayoutManager.SpanSizeLookup; import android.support.v7.widget.GridLayoutManager.SpanSizeLookup; Loading Loading @@ -191,7 +190,8 @@ public class DirectoryFragment extends Fragment private @Nullable BandController mBandController; private @Nullable BandController mBandController; private @Nullable ActionMode mActionMode; private @Nullable ActionMode mActionMode; private DragScrollListener mOnDragListener; private DragHoverListener mDragHoverListener; private DragStartListener mDragStartListener; private MenuManager mMenuManager; private MenuManager mMenuManager; private SortModel.UpdateListener mSortListener = (model, updateType) -> { private SortModel.UpdateListener mSortListener = (model, updateType) -> { Loading Loading @@ -227,12 +227,12 @@ public class DirectoryFragment extends Fragment mFileList = view.findViewById(R.id.file_list); mFileList = view.findViewById(R.id.file_list); final int edgeHeight = (int) getResources().getDimension(R.dimen.autoscroll_edge_height); final int edgeHeight = (int) getResources().getDimension(R.dimen.autoscroll_edge_height); mOnDragListener = DragScrollListener.create( mDragHoverListener = DragHoverListener.create( edgeHeight, new DirectoryDragListener(this), mRecView); edgeHeight, new DirectoryDragListener(this), mRecView); // Make the recycler and the empty views responsive to drop events. // Make the recycler and the empty views responsive to drop events. mRecView.setOnDragListener(mOnDragListener); mRecView.setOnDragListener(mDragHoverListener); mEmptyView.setOnDragListener(mOnDragListener); mEmptyView.setOnDragListener(mDragHoverListener); return view; return view; } } Loading Loading @@ -279,6 +279,7 @@ public class DirectoryFragment extends Fragment } } mIconHelper = new IconHelper(context, MODE_GRID); mIconHelper = new IconHelper(context, MODE_GRID); mClipper = DocumentsApplication.getDocumentClipper(getContext()); mAdapter = new SectionBreakDocumentsAdapterWrapper( mAdapter = new SectionBreakDocumentsAdapterWrapper( this, new ModelBackedDocumentsAdapter(this, mIconHelper)); this, new ModelBackedDocumentsAdapter(this, mIconHelper)); Loading Loading @@ -320,6 +321,10 @@ public class DirectoryFragment extends Fragment mSelectionMgr, mSelectionMgr, mRecView); mRecView); if (state.allowMultiple) { mBandController = new BandController(mRecView, mAdapter, mSelectionMgr); } mInputHandler = new UserInputHandler<>( mInputHandler = new UserInputHandler<>( mSelectionMgr, mSelectionMgr, mFocusManager, mFocusManager, Loading @@ -339,23 +344,32 @@ public class DirectoryFragment extends Fragment this::onDragAndDrop, this::onDragAndDrop, gestureSel::start); gestureSel::start); mGestureDetector = mDragStartListener = new DragStartListener( new ListeningGestureDetector(this.getContext(), mDragHelper, mIconHelper, mInputHandler, gestureSel); getContext(), mModel, mRecView.addOnItemTouchListener(mGestureDetector); mSelectionMgr, mEmptyView.setOnTouchListener(mGestureDetector); mClipper, getDisplayState(), if (state.allowMultiple) { this::getModelId, mBandController = new BandController(mRecView, mAdapter, mSelectionMgr); mRecView::findChildViewUnder, } getContext().getDrawable(com.android.internal.R.drawable.ic_doc_generic)); mGestureDetector = new ListeningGestureDetector( this.getContext(), mRecView, mEmptyView, mDragStartListener, gestureSel, mInputHandler, mBandController); mSelectionMgr.addCallback(mSelectionModeListener); mSelectionMgr.addCallback(mSelectionModeListener); final BaseActivity activity = getBaseActivity(); final BaseActivity activity = getBaseActivity(); mTuner = activity.createFragmentTuner(); mTuner = activity.createFragmentTuner(); mMenuManager = activity.getMenuManager(); mMenuManager = activity.getMenuManager(); mClipper = DocumentsApplication.getDocumentClipper(getContext()); final ActivityManager am = (ActivityManager) context.getSystemService( final ActivityManager am = (ActivityManager) context.getSystemService( Context.ACTIVITY_SERVICE); Context.ACTIVITY_SERVICE); Loading Loading @@ -1343,7 +1357,7 @@ public class DirectoryFragment extends Fragment if (Document.MIME_TYPE_DIR.equals(docMimeType)) { if (Document.MIME_TYPE_DIR.equals(docMimeType)) { // Make a directory item a drop target. Drop on non-directories and empty space // Make a directory item a drop target. Drop on non-directories and empty space // is handled at the list/grid view level. // is handled at the list/grid view level. view.setOnDragListener(mOnDragListener); view.setOnDragListener(mDragHoverListener); } } } } Loading Loading @@ -1531,75 +1545,10 @@ public class DirectoryFragment extends Fragment } } } } private Drawable getDragIcon(Selection selection) { if (selection.size() == 1) { DocumentInfo doc = getSingleSelectedDocument(selection); return mIconHelper.getDocumentIcon(getContext(), doc); } return getContext().getDrawable(com.android.internal.R.drawable.ic_doc_generic); } private String getDragTitle(Selection selection) { assert (!selection.isEmpty()); if (selection.size() == 1) { DocumentInfo doc = getSingleSelectedDocument(selection); return doc.displayName; } return Shared.getQuantityString(getContext(), R.plurals.elements_dragged, selection.size()); } private DocumentInfo getSingleSelectedDocument(Selection selection) { assert (selection.size() == 1); final List<DocumentInfo> docs = mModel.getDocuments(mSelectionMgr.getSelection()); assert (docs.size() == 1); return docs.get(0); } private DragStartHelper.OnDragStartListener mOnDragStartListener = new DragStartHelper.OnDragStartListener() { @Override public boolean onDragStart(View v, DragStartHelper helper) { Selection selection = mSelectionMgr.getSelection(); if (v == null) { Log.d(TAG, "Ignoring drag event, null view"); return false; } if (!isSelected(getModelId(v))) { Log.d(TAG, "Ignoring drag event, unselected view."); return false; } // NOTE: Preparation of the ClipData object can require a lot of time // and ideally should be done in the background. Unfortunately // the current code layout and framework assumptions don't support // this. So for now, we could end up doing a bunch of i/o on main thread. v.startDragAndDrop( mClipper.getClipDataForDocuments( mModel::getItemUri, selection, FileOperationService.OPERATION_COPY), new DragShadowBuilder( getActivity(), getDragTitle(selection), getDragIcon(selection)), getDisplayState().stack.peek(), View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ | View.DRAG_FLAG_GLOBAL_URI_WRITE); return true; } }; private DragStartHelper mDragHelper = new DragStartHelper(null, mOnDragStartListener); private boolean onDragAndDrop(InputEvent event) { private boolean onDragAndDrop(InputEvent event) { if (mTuner.dragAndDropEnabled()) { if (mTuner.dragAndDropEnabled()) { View childView = mRecView.findChildViewUnder(event.getX(), event.getY()); View childView = mRecView.findChildViewUnder(event.getX(), event.getY()); return mDragHelper.onLongClick(childView); return mDragStartListener.startDrag(childView); } } return false; return false; } } Loading src/com/android/documentsui/dirlist/DragScrollListener.java→src/com/android/documentsui/dirlist/DragHoverListener.java +5 −5 Original line number Original line Diff line number Diff line Loading @@ -37,7 +37,7 @@ import javax.annotation.Nullable; * This class acts as a middle-man handler for potential auto-scrolling before passing the dragEvent * This class acts as a middle-man handler for potential auto-scrolling before passing the dragEvent * onto {@link DirectoryDragListener}. * onto {@link DirectoryDragListener}. */ */ class DragScrollListener implements OnDragListener { class DragHoverListener implements OnDragListener { private final ItemDragListener<? extends DragHost> mDragHandler; private final ItemDragListener<? extends DragHost> mDragHandler; private final IntSupplier mHeight; private final IntSupplier mHeight; Loading @@ -49,7 +49,7 @@ class DragScrollListener implements OnDragListener { /** /** * Predicate to tests whether it's the scroll view ({@link DirectoryFragment#mRecView}) itself. * Predicate to tests whether it's the scroll view ({@link DirectoryFragment#mRecView}) itself. * * * {@link DragScrollListener} is used for both {@link DirectoryFragment#mRecView} and its * {@link DragHoverListener} is used for both {@link DirectoryFragment#mRecView} and its * children. When we decide whether it's in the scroll zone we need to obtain the coordinate * children. When we decide whether it's in the scroll zone we need to obtain the coordinate * relative to {@link DirectoryFragment#mRecView} so we need to transform the coordinate if the * relative to {@link DirectoryFragment#mRecView} so we need to transform the coordinate if the * view that gets drag and drop events is a child of {@link DirectoryFragment#mRecView}. * view that gets drag and drop events is a child of {@link DirectoryFragment#mRecView}. Loading @@ -60,7 +60,7 @@ class DragScrollListener implements OnDragListener { private @Nullable Point mCurrentPosition; private @Nullable Point mCurrentPosition; @VisibleForTesting @VisibleForTesting DragScrollListener( DragHoverListener( int autoScrollEdgeHeight, int autoScrollEdgeHeight, ItemDragListener<? extends DragHost> dragHandler, ItemDragListener<? extends DragHost> dragHandler, IntSupplier heightSupplier, IntSupplier heightSupplier, Loading Loading @@ -96,7 +96,7 @@ class DragScrollListener implements OnDragListener { mAutoScrollEdgeHeight, distanceDelegate, actionDelegate); mAutoScrollEdgeHeight, distanceDelegate, actionDelegate); } } static DragScrollListener create( static DragHoverListener create( int autoScrollEdgeHeight, int autoScrollEdgeHeight, ItemDragListener<? extends DragHost> dragHandler, ItemDragListener<? extends DragHost> dragHandler, View scrollView) { View scrollView) { Loading @@ -117,7 +117,7 @@ class DragScrollListener implements OnDragListener { scrollView.removeCallbacks(r); scrollView.removeCallbacks(r); } } }; }; DragScrollListener listener = new DragScrollListener( DragHoverListener listener = new DragHoverListener( autoScrollEdgeHeight, autoScrollEdgeHeight, dragHandler, dragHandler, scrollView::getHeight, scrollView::getHeight, Loading src/com/android/documentsui/dirlist/DragStartListener.java 0 → 100644 +158 −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.ClipData; import android.content.Context; import android.graphics.drawable.Drawable; import android.util.Log; import android.view.View; import com.android.documentsui.Events.InputEvent; import com.android.documentsui.R; import com.android.documentsui.Shared; import com.android.documentsui.State; import com.android.documentsui.clipping.DocumentClipper; import com.android.documentsui.dirlist.MultiSelectManager.Selection; import com.android.documentsui.model.DocumentInfo; import com.android.documentsui.services.FileOperationService; import java.util.List; import java.util.function.Function; import javax.annotation.Nullable; /** * Listens for potential "drag-like" events and kick-start dragging as needed. Also allows external * direct call to {@code #startDrag(RecyclerView, View)} if explicit start is needed, such as long- * pressing on an item via touch. (e.g. {@link UserInputHandler#onLongPress(InputEvent)} via touch.) */ class DragStartListener { private static String TAG = "DragStartListener"; private final IconHelper mIconHelper; private final Context mContext; private final Model mModel; private final MultiSelectManager mSelectionMgr; private final State mState; private final Drawable mDefaultDragIcon; private final DocumentClipper mClipper; private final Function<View, String> mIdFinder; private final ViewFinder mViewFinder; public DragStartListener( IconHelper iconHelper, Context context, Model model, MultiSelectManager selectionMgr, DocumentClipper clipper, State state, Function<View, String> idFinder, ViewFinder viewFinder, Drawable defaultDragIcon) { mIconHelper = iconHelper; mContext = context; mModel = model; mSelectionMgr = selectionMgr; mClipper = clipper; mState = state; mIdFinder = idFinder; mViewFinder = viewFinder; mDefaultDragIcon = defaultDragIcon; } boolean onInterceptTouchEvent(InputEvent event) { if (isDragEvent(event)) { View child = mViewFinder.findView(event.getX(), event.getY()); startDrag(child); return true; } return false; } boolean startDrag(View v) { if (v == null) { Log.d(TAG, "Ignoring drag event, null view"); return false; } final Selection selection = new Selection(); String modelId = mIdFinder.apply(v); if (modelId != null && !mSelectionMgr.getSelection().contains(modelId)) { selection.add(modelId); } else { mSelectionMgr.getSelection(selection); } DocumentInfo currentDir = mState.stack.peek(); ClipData clipData = mClipper.getClipDataForDocuments( mModel::getItemUri, selection, FileOperationService.OPERATION_COPY); // NOTE: Preparation of the ClipData object can require a lot of time // and ideally should be done in the background. Unfortunately // the current code layout and framework assumptions don't support // this. So for now, we could end up doing a bunch of i/o on main thread. v.startDragAndDrop( clipData, new DragShadowBuilder( mContext, getDragTitle(selection), getDragIcon(selection)), currentDir, View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ | View.DRAG_FLAG_GLOBAL_URI_WRITE); return true; } public boolean isDragEvent(InputEvent e) { return e.isOverItem() && e.isMouseEvent() && e.isActionMove() && e.isPrimaryButtonPressed(); } private Drawable getDragIcon(Selection selection) { if (selection.size() == 1) { DocumentInfo doc = getSingleSelectedDocument(selection); return mIconHelper.getDocumentIcon(mContext, doc); } return mDefaultDragIcon; } private String getDragTitle(Selection selection) { assert (!selection.isEmpty()); if (selection.size() == 1) { DocumentInfo doc = getSingleSelectedDocument(selection); return doc.displayName; } return Shared.getQuantityString(mContext, R.plurals.elements_dragged, selection.size()); } private DocumentInfo getSingleSelectedDocument(Selection selection) { assert (selection.size() == 1); final List<DocumentInfo> docs = mModel.getDocuments(selection); assert (docs.size() == 1); return docs.get(0); } @FunctionalInterface interface ViewFinder { @Nullable View findView(float x, float y); } } Loading
src/com/android/documentsui/Events.java +9 −0 Original line number Original line Diff line number Diff line Loading @@ -106,6 +106,10 @@ public final class Events { /** Returns true if the action is the final release of a mouse or touch. */ /** Returns true if the action is the final release of a mouse or touch. */ boolean isActionUp(); boolean isActionUp(); /** Returns true if the action is neither the initial nor the final release of a mouse * or touch. */ boolean isActionMove(); // Eliminate the checked Exception from Autoclosable. // Eliminate the checked Exception from Autoclosable. @Override @Override public void close(); public void close(); Loading Loading @@ -219,6 +223,11 @@ public final class Events { return mEvent.getActionMasked() == MotionEvent.ACTION_UP; return mEvent.getActionMasked() == MotionEvent.ACTION_UP; } } @Override public boolean isActionMove() { return mEvent.getActionMasked() == MotionEvent.ACTION_MOVE; } @Override @Override public Point getOrigin() { public Point getOrigin() { return new Point((int) mEvent.getX(), (int) mEvent.getY()); return new Point((int) mEvent.getX(), (int) mEvent.getY()); Loading
src/com/android/documentsui/dirlist/BandController.java +20 −42 Original line number Original line Diff line number Diff line Loading @@ -28,6 +28,7 @@ import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.support.annotation.VisibleForTesting; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.OnScrollListener; import android.util.Log; import android.util.Log; import android.util.SparseArray; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.SparseBooleanArray; Loading Loading @@ -55,7 +56,7 @@ import java.util.Set; * and {@link MultiSelectManager}. This class is responsible for rendering the band select * and {@link MultiSelectManager}. This class is responsible for rendering the band select * overlay and selecting overlaid items via MultiSelectManager. * overlay and selecting overlaid items via MultiSelectManager. */ */ public class BandController extends RecyclerView.OnScrollListener { public class BandController extends OnScrollListener { private static final String TAG = "BandController"; private static final String TAG = "BandController"; private static final int AUTOSCROLL_EDGE_HEIGHT = 1; private static final int AUTOSCROLL_EDGE_HEIGHT = 1; Loading @@ -79,26 +80,6 @@ public class BandController extends RecyclerView.OnScrollListener { DocumentsAdapter adapter, DocumentsAdapter adapter, MultiSelectManager selectionManager) { MultiSelectManager selectionManager) { this(new RuntimeSelectionEnvironment(view), adapter, selectionManager); this(new RuntimeSelectionEnvironment(view), adapter, selectionManager); view.addOnItemTouchListener( new RecyclerView.OnItemTouchListener() { @Override public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { try (InputEvent event = MotionInputEvent.obtain(e, view)) { return handleEvent(event); } } @Override public void onTouchEvent(RecyclerView rv, MotionEvent e) { if (Events.isMouseEvent(e)) { try (InputEvent event = MotionInputEvent.obtain(e, view)) { processInputEvent(event); } } } @Override public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {} }); } } private BandController( private BandController( Loading Loading @@ -199,26 +180,13 @@ public class BandController extends RecyclerView.OnScrollListener { mSelection = selection; mSelection = selection; } } private boolean handleEvent(InputEvent e) { boolean onInterceptTouchEvent(InputEvent e) { // Don't start, or extend bands on right click. if (e.isSecondaryButtonPressed()) { return false; } if (!e.isMouseEvent() && isActive()) { // Weird things happen if we keep up band select // when touch events happen. endBandSelect(); return false; } // b/23793622 notes the fact that we *never* receive ACTION_DOWN // b/23793622 notes the fact that we *never* receive ACTION_DOWN // events in onTouchEvent. Where it not for this issue, we'd // events in onTouchEvent. Where it not for this issue, we'd // push start handling down into handleInputEvent. // push start handling down into handleInputEvent. if (shouldStart(e)) { if (shouldStart(e)) { // endBandSelect is handled in handleInputEvent. startBandSelect(e.getOrigin()); startBandSelect(e.getOrigin()); } else if (isActive() && e.isActionUp()) { } else if (shouldStop(e)) { // Same issue here w b/23793622. The ACTION_UP event // Same issue here w b/23793622. The ACTION_UP event // is only evert dispatched to onTouchEvent when // is only evert dispatched to onTouchEvent when // there is some associated motion. If a user taps // there is some associated motion. If a user taps Loading @@ -226,9 +194,7 @@ public class BandController extends RecyclerView.OnScrollListener { // started BUT not ended. Causing phantom // started BUT not ended. Causing phantom // bands to appear when the user later clicks to start // bands to appear when the user later clicks to start // band select. // band select. if (e.isMouseEvent()) { endBandSelect(); processInputEvent(e); } } } return isActive(); return isActive(); Loading @@ -248,14 +214,26 @@ public class BandController extends RecyclerView.OnScrollListener { } } } } boolean shouldStart(InputEvent e) { public boolean shouldStart(InputEvent e) { // Don't start, or extend bands on right click. if (e.isSecondaryButtonPressed()) { return false; } if (!e.isMouseEvent() && isActive()) { // Weird things happen if we keep up band select // when touch events happen. endBandSelect(); return false; } return !isActive() return !isActive() && e.isActionDown() // the initial button press && e.isActionDown() // the initial button press && mAdapter.getItemCount() > 0 && mAdapter.getItemCount() > 0 && e.getItemPosition() == RecyclerView.NO_ID; // in empty space && e.getItemPosition() == RecyclerView.NO_ID; // in empty space } } boolean shouldStop(InputEvent input) { public boolean shouldStop(InputEvent input) { return isActive() return isActive() && input.isMouseEvent() && input.isMouseEvent() && input.isActionUp(); && input.isActionUp(); Loading @@ -265,7 +243,7 @@ public class BandController extends RecyclerView.OnScrollListener { * Processes a MotionEvent by starting, ending, or resizing the band select overlay. * Processes a MotionEvent by starting, ending, or resizing the band select overlay. * @param input * @param input */ */ private void processInputEvent(InputEvent input) { void onTouchEvent(InputEvent input) { assert(input.isMouseEvent()); assert(input.isMouseEvent()); if (shouldStop(input)) { if (shouldStop(input)) { Loading
src/com/android/documentsui/dirlist/DirectoryFragment.java +32 −83 Original line number Original line Diff line number Diff line Loading @@ -45,7 +45,6 @@ import android.os.Handler; import android.os.Parcelable; import android.os.Parcelable; import android.provider.DocumentsContract; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Document; import android.support.v13.view.DragStartHelper; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.GridLayoutManager.SpanSizeLookup; import android.support.v7.widget.GridLayoutManager.SpanSizeLookup; Loading Loading @@ -191,7 +190,8 @@ public class DirectoryFragment extends Fragment private @Nullable BandController mBandController; private @Nullable BandController mBandController; private @Nullable ActionMode mActionMode; private @Nullable ActionMode mActionMode; private DragScrollListener mOnDragListener; private DragHoverListener mDragHoverListener; private DragStartListener mDragStartListener; private MenuManager mMenuManager; private MenuManager mMenuManager; private SortModel.UpdateListener mSortListener = (model, updateType) -> { private SortModel.UpdateListener mSortListener = (model, updateType) -> { Loading Loading @@ -227,12 +227,12 @@ public class DirectoryFragment extends Fragment mFileList = view.findViewById(R.id.file_list); mFileList = view.findViewById(R.id.file_list); final int edgeHeight = (int) getResources().getDimension(R.dimen.autoscroll_edge_height); final int edgeHeight = (int) getResources().getDimension(R.dimen.autoscroll_edge_height); mOnDragListener = DragScrollListener.create( mDragHoverListener = DragHoverListener.create( edgeHeight, new DirectoryDragListener(this), mRecView); edgeHeight, new DirectoryDragListener(this), mRecView); // Make the recycler and the empty views responsive to drop events. // Make the recycler and the empty views responsive to drop events. mRecView.setOnDragListener(mOnDragListener); mRecView.setOnDragListener(mDragHoverListener); mEmptyView.setOnDragListener(mOnDragListener); mEmptyView.setOnDragListener(mDragHoverListener); return view; return view; } } Loading Loading @@ -279,6 +279,7 @@ public class DirectoryFragment extends Fragment } } mIconHelper = new IconHelper(context, MODE_GRID); mIconHelper = new IconHelper(context, MODE_GRID); mClipper = DocumentsApplication.getDocumentClipper(getContext()); mAdapter = new SectionBreakDocumentsAdapterWrapper( mAdapter = new SectionBreakDocumentsAdapterWrapper( this, new ModelBackedDocumentsAdapter(this, mIconHelper)); this, new ModelBackedDocumentsAdapter(this, mIconHelper)); Loading Loading @@ -320,6 +321,10 @@ public class DirectoryFragment extends Fragment mSelectionMgr, mSelectionMgr, mRecView); mRecView); if (state.allowMultiple) { mBandController = new BandController(mRecView, mAdapter, mSelectionMgr); } mInputHandler = new UserInputHandler<>( mInputHandler = new UserInputHandler<>( mSelectionMgr, mSelectionMgr, mFocusManager, mFocusManager, Loading @@ -339,23 +344,32 @@ public class DirectoryFragment extends Fragment this::onDragAndDrop, this::onDragAndDrop, gestureSel::start); gestureSel::start); mGestureDetector = mDragStartListener = new DragStartListener( new ListeningGestureDetector(this.getContext(), mDragHelper, mIconHelper, mInputHandler, gestureSel); getContext(), mModel, mRecView.addOnItemTouchListener(mGestureDetector); mSelectionMgr, mEmptyView.setOnTouchListener(mGestureDetector); mClipper, getDisplayState(), if (state.allowMultiple) { this::getModelId, mBandController = new BandController(mRecView, mAdapter, mSelectionMgr); mRecView::findChildViewUnder, } getContext().getDrawable(com.android.internal.R.drawable.ic_doc_generic)); mGestureDetector = new ListeningGestureDetector( this.getContext(), mRecView, mEmptyView, mDragStartListener, gestureSel, mInputHandler, mBandController); mSelectionMgr.addCallback(mSelectionModeListener); mSelectionMgr.addCallback(mSelectionModeListener); final BaseActivity activity = getBaseActivity(); final BaseActivity activity = getBaseActivity(); mTuner = activity.createFragmentTuner(); mTuner = activity.createFragmentTuner(); mMenuManager = activity.getMenuManager(); mMenuManager = activity.getMenuManager(); mClipper = DocumentsApplication.getDocumentClipper(getContext()); final ActivityManager am = (ActivityManager) context.getSystemService( final ActivityManager am = (ActivityManager) context.getSystemService( Context.ACTIVITY_SERVICE); Context.ACTIVITY_SERVICE); Loading Loading @@ -1343,7 +1357,7 @@ public class DirectoryFragment extends Fragment if (Document.MIME_TYPE_DIR.equals(docMimeType)) { if (Document.MIME_TYPE_DIR.equals(docMimeType)) { // Make a directory item a drop target. Drop on non-directories and empty space // Make a directory item a drop target. Drop on non-directories and empty space // is handled at the list/grid view level. // is handled at the list/grid view level. view.setOnDragListener(mOnDragListener); view.setOnDragListener(mDragHoverListener); } } } } Loading Loading @@ -1531,75 +1545,10 @@ public class DirectoryFragment extends Fragment } } } } private Drawable getDragIcon(Selection selection) { if (selection.size() == 1) { DocumentInfo doc = getSingleSelectedDocument(selection); return mIconHelper.getDocumentIcon(getContext(), doc); } return getContext().getDrawable(com.android.internal.R.drawable.ic_doc_generic); } private String getDragTitle(Selection selection) { assert (!selection.isEmpty()); if (selection.size() == 1) { DocumentInfo doc = getSingleSelectedDocument(selection); return doc.displayName; } return Shared.getQuantityString(getContext(), R.plurals.elements_dragged, selection.size()); } private DocumentInfo getSingleSelectedDocument(Selection selection) { assert (selection.size() == 1); final List<DocumentInfo> docs = mModel.getDocuments(mSelectionMgr.getSelection()); assert (docs.size() == 1); return docs.get(0); } private DragStartHelper.OnDragStartListener mOnDragStartListener = new DragStartHelper.OnDragStartListener() { @Override public boolean onDragStart(View v, DragStartHelper helper) { Selection selection = mSelectionMgr.getSelection(); if (v == null) { Log.d(TAG, "Ignoring drag event, null view"); return false; } if (!isSelected(getModelId(v))) { Log.d(TAG, "Ignoring drag event, unselected view."); return false; } // NOTE: Preparation of the ClipData object can require a lot of time // and ideally should be done in the background. Unfortunately // the current code layout and framework assumptions don't support // this. So for now, we could end up doing a bunch of i/o on main thread. v.startDragAndDrop( mClipper.getClipDataForDocuments( mModel::getItemUri, selection, FileOperationService.OPERATION_COPY), new DragShadowBuilder( getActivity(), getDragTitle(selection), getDragIcon(selection)), getDisplayState().stack.peek(), View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ | View.DRAG_FLAG_GLOBAL_URI_WRITE); return true; } }; private DragStartHelper mDragHelper = new DragStartHelper(null, mOnDragStartListener); private boolean onDragAndDrop(InputEvent event) { private boolean onDragAndDrop(InputEvent event) { if (mTuner.dragAndDropEnabled()) { if (mTuner.dragAndDropEnabled()) { View childView = mRecView.findChildViewUnder(event.getX(), event.getY()); View childView = mRecView.findChildViewUnder(event.getX(), event.getY()); return mDragHelper.onLongClick(childView); return mDragStartListener.startDrag(childView); } } return false; return false; } } Loading
src/com/android/documentsui/dirlist/DragScrollListener.java→src/com/android/documentsui/dirlist/DragHoverListener.java +5 −5 Original line number Original line Diff line number Diff line Loading @@ -37,7 +37,7 @@ import javax.annotation.Nullable; * This class acts as a middle-man handler for potential auto-scrolling before passing the dragEvent * This class acts as a middle-man handler for potential auto-scrolling before passing the dragEvent * onto {@link DirectoryDragListener}. * onto {@link DirectoryDragListener}. */ */ class DragScrollListener implements OnDragListener { class DragHoverListener implements OnDragListener { private final ItemDragListener<? extends DragHost> mDragHandler; private final ItemDragListener<? extends DragHost> mDragHandler; private final IntSupplier mHeight; private final IntSupplier mHeight; Loading @@ -49,7 +49,7 @@ class DragScrollListener implements OnDragListener { /** /** * Predicate to tests whether it's the scroll view ({@link DirectoryFragment#mRecView}) itself. * Predicate to tests whether it's the scroll view ({@link DirectoryFragment#mRecView}) itself. * * * {@link DragScrollListener} is used for both {@link DirectoryFragment#mRecView} and its * {@link DragHoverListener} is used for both {@link DirectoryFragment#mRecView} and its * children. When we decide whether it's in the scroll zone we need to obtain the coordinate * children. When we decide whether it's in the scroll zone we need to obtain the coordinate * relative to {@link DirectoryFragment#mRecView} so we need to transform the coordinate if the * relative to {@link DirectoryFragment#mRecView} so we need to transform the coordinate if the * view that gets drag and drop events is a child of {@link DirectoryFragment#mRecView}. * view that gets drag and drop events is a child of {@link DirectoryFragment#mRecView}. Loading @@ -60,7 +60,7 @@ class DragScrollListener implements OnDragListener { private @Nullable Point mCurrentPosition; private @Nullable Point mCurrentPosition; @VisibleForTesting @VisibleForTesting DragScrollListener( DragHoverListener( int autoScrollEdgeHeight, int autoScrollEdgeHeight, ItemDragListener<? extends DragHost> dragHandler, ItemDragListener<? extends DragHost> dragHandler, IntSupplier heightSupplier, IntSupplier heightSupplier, Loading Loading @@ -96,7 +96,7 @@ class DragScrollListener implements OnDragListener { mAutoScrollEdgeHeight, distanceDelegate, actionDelegate); mAutoScrollEdgeHeight, distanceDelegate, actionDelegate); } } static DragScrollListener create( static DragHoverListener create( int autoScrollEdgeHeight, int autoScrollEdgeHeight, ItemDragListener<? extends DragHost> dragHandler, ItemDragListener<? extends DragHost> dragHandler, View scrollView) { View scrollView) { Loading @@ -117,7 +117,7 @@ class DragScrollListener implements OnDragListener { scrollView.removeCallbacks(r); scrollView.removeCallbacks(r); } } }; }; DragScrollListener listener = new DragScrollListener( DragHoverListener listener = new DragHoverListener( autoScrollEdgeHeight, autoScrollEdgeHeight, dragHandler, dragHandler, scrollView::getHeight, scrollView::getHeight, Loading
src/com/android/documentsui/dirlist/DragStartListener.java 0 → 100644 +158 −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.ClipData; import android.content.Context; import android.graphics.drawable.Drawable; import android.util.Log; import android.view.View; import com.android.documentsui.Events.InputEvent; import com.android.documentsui.R; import com.android.documentsui.Shared; import com.android.documentsui.State; import com.android.documentsui.clipping.DocumentClipper; import com.android.documentsui.dirlist.MultiSelectManager.Selection; import com.android.documentsui.model.DocumentInfo; import com.android.documentsui.services.FileOperationService; import java.util.List; import java.util.function.Function; import javax.annotation.Nullable; /** * Listens for potential "drag-like" events and kick-start dragging as needed. Also allows external * direct call to {@code #startDrag(RecyclerView, View)} if explicit start is needed, such as long- * pressing on an item via touch. (e.g. {@link UserInputHandler#onLongPress(InputEvent)} via touch.) */ class DragStartListener { private static String TAG = "DragStartListener"; private final IconHelper mIconHelper; private final Context mContext; private final Model mModel; private final MultiSelectManager mSelectionMgr; private final State mState; private final Drawable mDefaultDragIcon; private final DocumentClipper mClipper; private final Function<View, String> mIdFinder; private final ViewFinder mViewFinder; public DragStartListener( IconHelper iconHelper, Context context, Model model, MultiSelectManager selectionMgr, DocumentClipper clipper, State state, Function<View, String> idFinder, ViewFinder viewFinder, Drawable defaultDragIcon) { mIconHelper = iconHelper; mContext = context; mModel = model; mSelectionMgr = selectionMgr; mClipper = clipper; mState = state; mIdFinder = idFinder; mViewFinder = viewFinder; mDefaultDragIcon = defaultDragIcon; } boolean onInterceptTouchEvent(InputEvent event) { if (isDragEvent(event)) { View child = mViewFinder.findView(event.getX(), event.getY()); startDrag(child); return true; } return false; } boolean startDrag(View v) { if (v == null) { Log.d(TAG, "Ignoring drag event, null view"); return false; } final Selection selection = new Selection(); String modelId = mIdFinder.apply(v); if (modelId != null && !mSelectionMgr.getSelection().contains(modelId)) { selection.add(modelId); } else { mSelectionMgr.getSelection(selection); } DocumentInfo currentDir = mState.stack.peek(); ClipData clipData = mClipper.getClipDataForDocuments( mModel::getItemUri, selection, FileOperationService.OPERATION_COPY); // NOTE: Preparation of the ClipData object can require a lot of time // and ideally should be done in the background. Unfortunately // the current code layout and framework assumptions don't support // this. So for now, we could end up doing a bunch of i/o on main thread. v.startDragAndDrop( clipData, new DragShadowBuilder( mContext, getDragTitle(selection), getDragIcon(selection)), currentDir, View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ | View.DRAG_FLAG_GLOBAL_URI_WRITE); return true; } public boolean isDragEvent(InputEvent e) { return e.isOverItem() && e.isMouseEvent() && e.isActionMove() && e.isPrimaryButtonPressed(); } private Drawable getDragIcon(Selection selection) { if (selection.size() == 1) { DocumentInfo doc = getSingleSelectedDocument(selection); return mIconHelper.getDocumentIcon(mContext, doc); } return mDefaultDragIcon; } private String getDragTitle(Selection selection) { assert (!selection.isEmpty()); if (selection.size() == 1) { DocumentInfo doc = getSingleSelectedDocument(selection); return doc.displayName; } return Shared.getQuantityString(mContext, R.plurals.elements_dragged, selection.size()); } private DocumentInfo getSingleSelectedDocument(Selection selection) { assert (selection.size() == 1); final List<DocumentInfo> docs = mModel.getDocuments(selection); assert (docs.size() == 1); return docs.get(0); } @FunctionalInterface interface ViewFinder { @Nullable View findView(float x, float y); } }