1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.documentsui.dirlist; 18 19 import static com.android.documentsui.base.SharedMinimal.DEBUG; 20 21 import android.net.Uri; 22 import android.util.Log; 23 import android.view.MotionEvent; 24 import android.view.View; 25 26 import androidx.annotation.VisibleForTesting; 27 import androidx.recyclerview.selection.MutableSelection; 28 import androidx.recyclerview.selection.Selection; 29 import androidx.recyclerview.selection.SelectionTracker; 30 31 import com.android.documentsui.DragAndDropManager; 32 import com.android.documentsui.MenuManager.SelectionDetails; 33 import com.android.documentsui.Model; 34 import com.android.documentsui.base.DocumentInfo; 35 import com.android.documentsui.base.Events; 36 import com.android.documentsui.base.State; 37 38 import java.util.ArrayList; 39 import java.util.List; 40 import java.util.function.Function; 41 42 import javax.annotation.Nullable; 43 44 /** 45 * Listens for potential "drag-like" events and kick-start dragging as needed. Also allows external 46 * direct call to {@code #startDrag(RecyclerView, View)} if explicit start is needed, such as long- 47 * pressing on an item via touch. (e.g. InputEventDispatcher#onLongPress(MotionEvent)} via touch. 48 */ 49 interface DragStartListener { 50 51 DragStartListener STUB = new DragStartListener() { 52 @Override 53 public boolean onDragEvent(MotionEvent event) { 54 return false; 55 } 56 }; 57 onDragEvent(MotionEvent event)58 boolean onDragEvent(MotionEvent event); 59 60 @VisibleForTesting 61 class RuntimeDragStartListener implements DragStartListener { 62 63 private static String TAG = "DragStartListener"; 64 65 private final IconHelper mIconHelper; 66 private final State mState; 67 private final SelectionTracker<String> mSelectionMgr; 68 private final SelectionDetails mSelectionDetails; 69 private final ViewFinder mViewFinder; 70 private final Function<View, String> mIdFinder; 71 private final Function<Selection<String>, List<DocumentInfo>> mDocsConverter; 72 private final DragAndDropManager mDragAndDropManager; 73 74 75 // use DragStartListener.create 76 @VisibleForTesting RuntimeDragStartListener( IconHelper iconHelper, State state, SelectionTracker<String> selectionMgr, SelectionDetails selectionDetails, ViewFinder viewFinder, Function<View, String> idFinder, Function<Selection<String>, List<DocumentInfo>> docsConverter, DragAndDropManager dragAndDropManager)77 public RuntimeDragStartListener( 78 IconHelper iconHelper, 79 State state, 80 SelectionTracker<String> selectionMgr, 81 SelectionDetails selectionDetails, 82 ViewFinder viewFinder, 83 Function<View, String> idFinder, 84 Function<Selection<String>, List<DocumentInfo>> docsConverter, 85 DragAndDropManager dragAndDropManager) { 86 87 mIconHelper = iconHelper; 88 mState = state; 89 mSelectionMgr = selectionMgr; 90 mSelectionDetails = selectionDetails; 91 mViewFinder = viewFinder; 92 mIdFinder = idFinder; 93 mDocsConverter = docsConverter; 94 mDragAndDropManager = dragAndDropManager; 95 } 96 97 @Override onDragEvent(MotionEvent event)98 public final boolean onDragEvent(MotionEvent event) { 99 return startDrag(mViewFinder.findView(event.getX(), event.getY()), event); 100 } 101 102 /** 103 * May be called externally when drag is initiated from other event handling code. 104 */ startDrag(@ullable View view, MotionEvent event)105 private boolean startDrag(@Nullable View view, MotionEvent event) { 106 107 if (view == null) { 108 if (DEBUG) { 109 Log.d(TAG, "Ignoring drag event, null view."); 110 } 111 return false; 112 } 113 114 @Nullable String modelId = mIdFinder.apply(view); 115 if (modelId == null) { 116 if (DEBUG) { 117 Log.d(TAG, "Ignoring drag on view not represented in model."); 118 } 119 return false; 120 } 121 122 Selection<String> selection = getSelectionToBeCopied(modelId, event); 123 124 final List<DocumentInfo> srcs = mDocsConverter.apply(selection); 125 126 final List<Uri> invalidDest = new ArrayList<>(srcs.size() + 1); 127 for (DocumentInfo doc : srcs) { 128 invalidDest.add(doc.derivedUri); 129 } 130 131 final DocumentInfo parent = mState.stack.peek(); 132 // parent is null when we're in Recents 133 if (parent != null) { 134 invalidDest.add(parent.derivedUri); 135 } 136 137 mDragAndDropManager.startDrag(view, srcs, mState.stack.getRoot(), invalidDest, 138 mSelectionDetails, mIconHelper, parent); 139 140 return true; 141 } 142 143 /** 144 * Given the MotionEvent (for CTRL case) and modelId of the view associated with the 145 * coordinates of the event, return a valid selection for drag and drop operation 146 */ 147 @VisibleForTesting getSelectionToBeCopied(String modelId, MotionEvent event)148 MutableSelection<String> getSelectionToBeCopied(String modelId, MotionEvent event) { 149 MutableSelection<String> selection = new MutableSelection<>(); 150 // If CTRL-key is held down and there's other existing selection, add item to 151 // selection (if not already selected) 152 if (Events.isCtrlKeyPressed(event) 153 && mSelectionMgr.hasSelection() 154 && !mSelectionMgr.isSelected(modelId)) { 155 mSelectionMgr.select(modelId); 156 } 157 158 if (mSelectionMgr.isSelected(modelId)) { 159 mSelectionMgr.copySelection(selection); 160 } else { 161 selection.add(modelId); 162 mSelectionMgr.clearSelection(); 163 } 164 return selection; 165 } 166 } 167 create( IconHelper iconHelper, Model model, SelectionTracker<String> selectionMgr, SelectionDetails selectionDetails, State state, Function<View, String> idFinder, ViewFinder viewFinder, DragAndDropManager dragAndDropManager)168 static DragStartListener create( 169 IconHelper iconHelper, 170 Model model, 171 SelectionTracker<String> selectionMgr, 172 SelectionDetails selectionDetails, 173 State state, 174 Function<View, String> idFinder, 175 ViewFinder viewFinder, 176 DragAndDropManager dragAndDropManager) { 177 178 return new RuntimeDragStartListener( 179 iconHelper, 180 state, 181 selectionMgr, 182 selectionDetails, 183 viewFinder, 184 idFinder, 185 model::getDocuments, 186 dragAndDropManager); 187 } 188 189 @FunctionalInterface 190 interface ViewFinder { findView(float x, float y)191 @Nullable View findView(float x, float y); 192 } 193 } 194