• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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