135f99e02f3fa8af21139be216fc57c123779808dBen Lin/* 235f99e02f3fa8af21139be216fc57c123779808dBen Lin * Copyright (C) 2016 The Android Open Source Project 335f99e02f3fa8af21139be216fc57c123779808dBen Lin * 435f99e02f3fa8af21139be216fc57c123779808dBen Lin * Licensed under the Apache License, Version 2.0 (the "License"); 535f99e02f3fa8af21139be216fc57c123779808dBen Lin * you may not use this file except in compliance with the License. 635f99e02f3fa8af21139be216fc57c123779808dBen Lin * You may obtain a copy of the License at 735f99e02f3fa8af21139be216fc57c123779808dBen Lin * 835f99e02f3fa8af21139be216fc57c123779808dBen Lin * http://www.apache.org/licenses/LICENSE-2.0 935f99e02f3fa8af21139be216fc57c123779808dBen Lin * 1035f99e02f3fa8af21139be216fc57c123779808dBen Lin * Unless required by applicable law or agreed to in writing, software 1135f99e02f3fa8af21139be216fc57c123779808dBen Lin * distributed under the License is distributed on an "AS IS" BASIS, 1235f99e02f3fa8af21139be216fc57c123779808dBen Lin * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1335f99e02f3fa8af21139be216fc57c123779808dBen Lin * See the License for the specific language governing permissions and 1435f99e02f3fa8af21139be216fc57c123779808dBen Lin * limitations under the License. 1535f99e02f3fa8af21139be216fc57c123779808dBen Lin */ 1635f99e02f3fa8af21139be216fc57c123779808dBen Lin 1735f99e02f3fa8af21139be216fc57c123779808dBen Linpackage com.android.documentsui.dirlist; 1835f99e02f3fa8af21139be216fc57c123779808dBen Lin 19d9caa6ab53aa784acaf241c0ded3c4ae2d342bf8Steve McKayimport static com.android.documentsui.base.Shared.DEBUG; 20f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay 2135f99e02f3fa8af21139be216fc57c123779808dBen Linimport android.content.ClipData; 2235f99e02f3fa8af21139be216fc57c123779808dBen Linimport android.content.Context; 2335f99e02f3fa8af21139be216fc57c123779808dBen Linimport android.graphics.drawable.Drawable; 24f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKayimport android.support.annotation.VisibleForTesting; 2535f99e02f3fa8af21139be216fc57c123779808dBen Linimport android.util.Log; 2635f99e02f3fa8af21139be216fc57c123779808dBen Linimport android.view.View; 2735f99e02f3fa8af21139be216fc57c123779808dBen Lin 281c45629929c31cf7903cf955a48f170a8cdee255Ben Linimport com.android.documentsui.DragShadowBuilder; 29e967033315ed64bca8c89d601d187fd12754f1fbGarfield Tanimport com.android.documentsui.Model; 30d080506e3aa8547605cd4783eb660775d7d2b8eeSteve McKayimport com.android.documentsui.base.DocumentInfo; 31d9caa6ab53aa784acaf241c0ded3c4ae2d342bf8Steve McKayimport com.android.documentsui.base.Events; 32d9caa6ab53aa784acaf241c0ded3c4ae2d342bf8Steve McKayimport com.android.documentsui.base.Events.InputEvent; 331c45629929c31cf7903cf955a48f170a8cdee255Ben Linimport com.android.documentsui.base.State; 3435f99e02f3fa8af21139be216fc57c123779808dBen Linimport com.android.documentsui.clipping.DocumentClipper; 354f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKayimport com.android.documentsui.selection.Selection; 361c45629929c31cf7903cf955a48f170a8cdee255Ben Linimport com.android.documentsui.selection.SelectionManager; 3735f99e02f3fa8af21139be216fc57c123779808dBen Linimport com.android.documentsui.services.FileOperationService; 38f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKayimport com.android.documentsui.services.FileOperationService.OpType; 3935f99e02f3fa8af21139be216fc57c123779808dBen Lin 405a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Linimport java.util.List; 4135f99e02f3fa8af21139be216fc57c123779808dBen Linimport java.util.function.Function; 4235f99e02f3fa8af21139be216fc57c123779808dBen Lin 4335f99e02f3fa8af21139be216fc57c123779808dBen Linimport javax.annotation.Nullable; 4435f99e02f3fa8af21139be216fc57c123779808dBen Lin 4535f99e02f3fa8af21139be216fc57c123779808dBen Lin/** 4635f99e02f3fa8af21139be216fc57c123779808dBen Lin * Listens for potential "drag-like" events and kick-start dragging as needed. Also allows external 4735f99e02f3fa8af21139be216fc57c123779808dBen Lin * direct call to {@code #startDrag(RecyclerView, View)} if explicit start is needed, such as long- 4835f99e02f3fa8af21139be216fc57c123779808dBen Lin * pressing on an item via touch. (e.g. {@link UserInputHandler#onLongPress(InputEvent)} via touch.) 4935f99e02f3fa8af21139be216fc57c123779808dBen Lin */ 50f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKayinterface DragStartListener { 5135f99e02f3fa8af21139be216fc57c123779808dBen Lin 52f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay public static final DragStartListener DUMMY = new DragStartListener() { 53f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay @Override 54f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay public boolean onMouseDragEvent(InputEvent event) { 55f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay return false; 5635f99e02f3fa8af21139be216fc57c123779808dBen Lin } 57f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay @Override 58f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay public boolean onTouchDragEvent(InputEvent event) { 5935f99e02f3fa8af21139be216fc57c123779808dBen Lin return false; 6035f99e02f3fa8af21139be216fc57c123779808dBen Lin } 61f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay }; 62f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay 63f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay boolean onMouseDragEvent(InputEvent event); 64f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay boolean onTouchDragEvent(InputEvent event); 65f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay 66f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay @VisibleForTesting 67f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay static class ActiveListener implements DragStartListener { 68f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay 69f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay private static String TAG = "DragStartListener"; 70f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay 71f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay private final State mState; 724f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay private final SelectionManager mSelectionMgr; 73f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay private final ViewFinder mViewFinder; 74f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay private final Function<View, String> mIdFinder; 75f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay private final ClipDataFactory mClipFactory; 76f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay private final Function<Selection, DragShadowBuilder> mShadowFactory; 775a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Lin private Function<Selection, List<DocumentInfo>> mDocsConverter; 78f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay 79f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay // use DragStartListener.create 80f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay @VisibleForTesting 81f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay public ActiveListener( 82f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay State state, 834f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay SelectionManager selectionMgr, 84f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay ViewFinder viewFinder, 85f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay Function<View, String> idFinder, 865a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Lin Function<Selection, List<DocumentInfo>> docsConverter, 87f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay ClipDataFactory clipFactory, 88f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay Function<Selection, DragShadowBuilder> shadowFactory) { 89f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay 90f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay mState = state; 91f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay mSelectionMgr = selectionMgr; 92f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay mViewFinder = viewFinder; 93f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay mIdFinder = idFinder; 945a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Lin mDocsConverter = docsConverter; 95f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay mClipFactory = clipFactory; 96f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay mShadowFactory = shadowFactory; 97f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay } 9835f99e02f3fa8af21139be216fc57c123779808dBen Lin 99f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay @Override 100f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay public final boolean onMouseDragEvent(InputEvent event) { 101f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay assert(Events.isMouseDragEvent(event)); 102329eb937ce0e2ff9fb1ff807624a9e56cf3a3c6dBen Lin return startDrag(mViewFinder.findView(event.getX(), event.getY()), event); 10335f99e02f3fa8af21139be216fc57c123779808dBen Lin } 10435f99e02f3fa8af21139be216fc57c123779808dBen Lin 105f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay @Override 106f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay public final boolean onTouchDragEvent(InputEvent event) { 107329eb937ce0e2ff9fb1ff807624a9e56cf3a3c6dBen Lin return startDrag(mViewFinder.findView(event.getX(), event.getY()), event); 108f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay } 10935f99e02f3fa8af21139be216fc57c123779808dBen Lin 110f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay /** 111f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay * May be called externally when drag is initiated from other event handling code. 112f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay */ 113329eb937ce0e2ff9fb1ff807624a9e56cf3a3c6dBen Lin private final boolean startDrag(@Nullable View view, InputEvent event) { 114f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay 115f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay if (view == null) { 116f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay if (DEBUG) Log.d(TAG, "Ignoring drag event, null view."); 117f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay return false; 118f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay } 119f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay 120f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay @Nullable String modelId = mIdFinder.apply(view); 121f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay if (modelId == null) { 122f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay if (DEBUG) Log.d(TAG, "Ignoring drag on view not represented in model."); 123f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay return false; 124f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay } 125f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay 126329eb937ce0e2ff9fb1ff807624a9e56cf3a3c6dBen Lin Selection selection = getSelectionToBeCopied(modelId, event); 127f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay 1285a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Lin final List<DocumentInfo> invalidDest = mDocsConverter.apply(selection); 1295a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Lin invalidDest.add(mState.stack.peek()); 130f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay // NOTE: Preparation of the ClipData object can require a lot of time 131f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay // and ideally should be done in the background. Unfortunately 132f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay // the current code layout and framework assumptions don't support 133f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay // this. So for now, we could end up doing a bunch of i/o on main thread. 134f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay startDragAndDrop( 135f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay view, 136f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay mClipFactory.create( 137f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay selection, 138f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay FileOperationService.OPERATION_COPY), 139f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay mShadowFactory.apply(selection), 1405a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Lin invalidDest, 141f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay View.DRAG_FLAG_GLOBAL 142d020212fdad3be1537dfa47ff5b67e3bc4272d5eBen Lin | View.DRAG_FLAG_OPAQUE 143f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay | View.DRAG_FLAG_GLOBAL_URI_READ 144f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay | View.DRAG_FLAG_GLOBAL_URI_WRITE); 14535f99e02f3fa8af21139be216fc57c123779808dBen Lin 146f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay return true; 14735f99e02f3fa8af21139be216fc57c123779808dBen Lin } 14835f99e02f3fa8af21139be216fc57c123779808dBen Lin 149f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay /** 150329eb937ce0e2ff9fb1ff807624a9e56cf3a3c6dBen Lin * Given the InputEvent (for CTRL case) and modelId of the view associated with the 151329eb937ce0e2ff9fb1ff807624a9e56cf3a3c6dBen Lin * coordinates of the event, return a valid selection for drag and drop operation 152329eb937ce0e2ff9fb1ff807624a9e56cf3a3c6dBen Lin */ 153329eb937ce0e2ff9fb1ff807624a9e56cf3a3c6dBen Lin @VisibleForTesting 154329eb937ce0e2ff9fb1ff807624a9e56cf3a3c6dBen Lin Selection getSelectionToBeCopied(String modelId, InputEvent event) { 155329eb937ce0e2ff9fb1ff807624a9e56cf3a3c6dBen Lin Selection selection = new Selection(); 156329eb937ce0e2ff9fb1ff807624a9e56cf3a3c6dBen Lin // If CTRL-key is held down and there's other existing selection, add item to 157329eb937ce0e2ff9fb1ff807624a9e56cf3a3c6dBen Lin // selection (if not already selected) 158329eb937ce0e2ff9fb1ff807624a9e56cf3a3c6dBen Lin if (event.isCtrlKeyDown() && !mSelectionMgr.getSelection().contains(modelId) 159329eb937ce0e2ff9fb1ff807624a9e56cf3a3c6dBen Lin && mSelectionMgr.hasSelection()) { 160329eb937ce0e2ff9fb1ff807624a9e56cf3a3c6dBen Lin mSelectionMgr.toggleSelection(modelId); 161329eb937ce0e2ff9fb1ff807624a9e56cf3a3c6dBen Lin } 162329eb937ce0e2ff9fb1ff807624a9e56cf3a3c6dBen Lin 163329eb937ce0e2ff9fb1ff807624a9e56cf3a3c6dBen Lin if (mSelectionMgr.getSelection().contains(modelId)) { 164329eb937ce0e2ff9fb1ff807624a9e56cf3a3c6dBen Lin mSelectionMgr.getSelection(selection); 165329eb937ce0e2ff9fb1ff807624a9e56cf3a3c6dBen Lin } else { 166329eb937ce0e2ff9fb1ff807624a9e56cf3a3c6dBen Lin selection.add(modelId); 167329eb937ce0e2ff9fb1ff807624a9e56cf3a3c6dBen Lin mSelectionMgr.clearSelection(); 168329eb937ce0e2ff9fb1ff807624a9e56cf3a3c6dBen Lin } 169329eb937ce0e2ff9fb1ff807624a9e56cf3a3c6dBen Lin return selection; 170329eb937ce0e2ff9fb1ff807624a9e56cf3a3c6dBen Lin } 171329eb937ce0e2ff9fb1ff807624a9e56cf3a3c6dBen Lin 172329eb937ce0e2ff9fb1ff807624a9e56cf3a3c6dBen Lin /** 173f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay * This exists as a testing workaround since {@link View#startDragAndDrop} is final. 174f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay */ 175f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay @VisibleForTesting 176f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay void startDragAndDrop( 177f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay View view, 178f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay ClipData data, 179f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay DragShadowBuilder shadowBuilder, 1805a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Lin Object localState, 181f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay int flags) { 182f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay 1835a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Lin view.startDragAndDrop(data, shadowBuilder, localState, flags); 18435f99e02f3fa8af21139be216fc57c123779808dBen Lin } 18535f99e02f3fa8af21139be216fc57c123779808dBen Lin } 18635f99e02f3fa8af21139be216fc57c123779808dBen Lin 187f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay public static DragStartListener create( 188f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay IconHelper iconHelper, 189f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay Context context, 190f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay Model model, 1914f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay SelectionManager selectionMgr, 192f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay DocumentClipper clipper, 193f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay State state, 194f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay Function<View, String> idFinder, 195f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay ViewFinder viewFinder, 1961c45629929c31cf7903cf955a48f170a8cdee255Ben Lin Drawable defaultDragIcon, 1971c45629929c31cf7903cf955a48f170a8cdee255Ben Lin DragShadowBuilder shadowBuilder) { 1981c45629929c31cf7903cf955a48f170a8cdee255Ben Lin 1991c45629929c31cf7903cf955a48f170a8cdee255Ben Lin DragShadowBuilder.Updater shadowFactory = new DragShadowBuilder.Updater( 2001c45629929c31cf7903cf955a48f170a8cdee255Ben Lin context, 2011c45629929c31cf7903cf955a48f170a8cdee255Ben Lin shadowBuilder, 2021c45629929c31cf7903cf955a48f170a8cdee255Ben Lin model, 2031c45629929c31cf7903cf955a48f170a8cdee255Ben Lin iconHelper, 2041c45629929c31cf7903cf955a48f170a8cdee255Ben Lin defaultDragIcon); 205f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay 206f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay return new ActiveListener( 207f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay state, 208f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay selectionMgr, 209f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay viewFinder, 210f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay idFinder, 2115a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Lin model::getDocuments, 212f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay (Selection selection, @OpType int operationType) -> { 213f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay return clipper.getClipDataForDocuments( 214f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay model::getItemUri, 215f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay selection, 216f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay FileOperationService.OPERATION_COPY); 217f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay }, 218f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay shadowFactory); 21935f99e02f3fa8af21139be216fc57c123779808dBen Lin } 22035f99e02f3fa8af21139be216fc57c123779808dBen Lin 22135f99e02f3fa8af21139be216fc57c123779808dBen Lin @FunctionalInterface 22235f99e02f3fa8af21139be216fc57c123779808dBen Lin interface ViewFinder { 22335f99e02f3fa8af21139be216fc57c123779808dBen Lin @Nullable View findView(float x, float y); 22435f99e02f3fa8af21139be216fc57c123779808dBen Lin } 225f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay 226f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay @FunctionalInterface 227f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay interface ClipDataFactory { 228f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay ClipData create(Selection selection, @OpType int operationType); 229f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay } 23035f99e02f3fa8af21139be216fc57c123779808dBen Lin} 231