| /* |
| * 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 static com.android.documentsui.base.SharedMinimal.DEBUG; |
| |
| import android.net.Uri; |
| import android.util.Log; |
| import android.view.MotionEvent; |
| import android.view.View; |
| |
| import androidx.annotation.VisibleForTesting; |
| import androidx.recyclerview.selection.MutableSelection; |
| import androidx.recyclerview.selection.Selection; |
| import androidx.recyclerview.selection.SelectionTracker; |
| |
| import com.android.documentsui.DragAndDropManager; |
| import com.android.documentsui.MenuManager.SelectionDetails; |
| import com.android.documentsui.Model; |
| import com.android.documentsui.base.DocumentInfo; |
| import com.android.documentsui.base.Events; |
| import com.android.documentsui.base.State; |
| |
| import java.util.ArrayList; |
| 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. InputEventDispatcher#onLongPress(MotionEvent)} via touch. |
| */ |
| interface DragStartListener { |
| |
| DragStartListener STUB = new DragStartListener() { |
| @Override |
| public boolean onDragEvent(MotionEvent event) { |
| return false; |
| } |
| }; |
| |
| boolean onDragEvent(MotionEvent event); |
| |
| @VisibleForTesting |
| class RuntimeDragStartListener implements DragStartListener { |
| |
| private static String TAG = "DragStartListener"; |
| |
| private final IconHelper mIconHelper; |
| private final State mState; |
| private final SelectionTracker<String> mSelectionMgr; |
| private final SelectionDetails mSelectionDetails; |
| private final ViewFinder mViewFinder; |
| private final Function<View, String> mIdFinder; |
| private final Function<Selection<String>, List<DocumentInfo>> mDocsConverter; |
| private final DragAndDropManager mDragAndDropManager; |
| |
| |
| // use DragStartListener.create |
| @VisibleForTesting |
| public RuntimeDragStartListener( |
| IconHelper iconHelper, |
| State state, |
| SelectionTracker<String> selectionMgr, |
| SelectionDetails selectionDetails, |
| ViewFinder viewFinder, |
| Function<View, String> idFinder, |
| Function<Selection<String>, List<DocumentInfo>> docsConverter, |
| DragAndDropManager dragAndDropManager) { |
| |
| mIconHelper = iconHelper; |
| mState = state; |
| mSelectionMgr = selectionMgr; |
| mSelectionDetails = selectionDetails; |
| mViewFinder = viewFinder; |
| mIdFinder = idFinder; |
| mDocsConverter = docsConverter; |
| mDragAndDropManager = dragAndDropManager; |
| } |
| |
| @Override |
| public final boolean onDragEvent(MotionEvent event) { |
| return startDrag(mViewFinder.findView(event.getX(), event.getY()), event); |
| } |
| |
| /** |
| * May be called externally when drag is initiated from other event handling code. |
| */ |
| private boolean startDrag(@Nullable View view, MotionEvent event) { |
| |
| if (view == null) { |
| if (DEBUG) { |
| Log.d(TAG, "Ignoring drag event, null view."); |
| } |
| return false; |
| } |
| |
| @Nullable String modelId = mIdFinder.apply(view); |
| if (modelId == null) { |
| if (DEBUG) { |
| Log.d(TAG, "Ignoring drag on view not represented in model."); |
| } |
| return false; |
| } |
| |
| Selection<String> selection = getSelectionToBeCopied(modelId, event); |
| |
| final List<DocumentInfo> srcs = mDocsConverter.apply(selection); |
| |
| final List<Uri> invalidDest = new ArrayList<>(srcs.size() + 1); |
| for (DocumentInfo doc : srcs) { |
| invalidDest.add(doc.derivedUri); |
| } |
| |
| final DocumentInfo parent = mState.stack.peek(); |
| // parent is null when we're in Recents |
| if (parent != null) { |
| invalidDest.add(parent.derivedUri); |
| } |
| |
| mDragAndDropManager.startDrag(view, srcs, mState.stack.getRoot(), invalidDest, |
| mSelectionDetails, mIconHelper, parent); |
| |
| return true; |
| } |
| |
| /** |
| * Given the MotionEvent (for CTRL case) and modelId of the view associated with the |
| * coordinates of the event, return a valid selection for drag and drop operation |
| */ |
| @VisibleForTesting |
| MutableSelection<String> getSelectionToBeCopied(String modelId, MotionEvent event) { |
| MutableSelection<String> selection = new MutableSelection<>(); |
| // If CTRL-key is held down and there's other existing selection, add item to |
| // selection (if not already selected) |
| if (Events.isCtrlKeyPressed(event) |
| && mSelectionMgr.hasSelection() |
| && !mSelectionMgr.isSelected(modelId)) { |
| mSelectionMgr.select(modelId); |
| } |
| |
| if (mSelectionMgr.isSelected(modelId)) { |
| mSelectionMgr.copySelection(selection); |
| } else { |
| selection.add(modelId); |
| mSelectionMgr.clearSelection(); |
| } |
| return selection; |
| } |
| } |
| |
| static DragStartListener create( |
| IconHelper iconHelper, |
| Model model, |
| SelectionTracker<String> selectionMgr, |
| SelectionDetails selectionDetails, |
| State state, |
| Function<View, String> idFinder, |
| ViewFinder viewFinder, |
| DragAndDropManager dragAndDropManager) { |
| |
| return new RuntimeDragStartListener( |
| iconHelper, |
| state, |
| selectionMgr, |
| selectionDetails, |
| viewFinder, |
| idFinder, |
| model::getDocuments, |
| dragAndDropManager); |
| } |
| |
| @FunctionalInterface |
| interface ViewFinder { |
| @Nullable View findView(float x, float y); |
| } |
| } |