DragStartListener.java revision 4f78ba643270b9d84da1952d8e408220b25ec6fd
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 28d080506e3aa8547605cd4783eb660775d7d2b8eeSteve McKayimport com.android.documentsui.base.DocumentInfo; 29d9caa6ab53aa784acaf241c0ded3c4ae2d342bf8Steve McKayimport com.android.documentsui.base.Events; 30d9caa6ab53aa784acaf241c0ded3c4ae2d342bf8Steve McKayimport com.android.documentsui.base.State; 31d9caa6ab53aa784acaf241c0ded3c4ae2d342bf8Steve McKayimport com.android.documentsui.base.Events.InputEvent; 3235f99e02f3fa8af21139be216fc57c123779808dBen Linimport com.android.documentsui.clipping.DocumentClipper; 334f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKayimport com.android.documentsui.selection.SelectionManager; 344f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKayimport com.android.documentsui.selection.Selection; 3535f99e02f3fa8af21139be216fc57c123779808dBen Linimport com.android.documentsui.services.FileOperationService; 36f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKayimport com.android.documentsui.services.FileOperationService.OpType; 3735f99e02f3fa8af21139be216fc57c123779808dBen Lin 385a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Linimport java.util.List; 3935f99e02f3fa8af21139be216fc57c123779808dBen Linimport java.util.function.Function; 4035f99e02f3fa8af21139be216fc57c123779808dBen Lin 4135f99e02f3fa8af21139be216fc57c123779808dBen Linimport javax.annotation.Nullable; 4235f99e02f3fa8af21139be216fc57c123779808dBen Lin 4335f99e02f3fa8af21139be216fc57c123779808dBen Lin/** 4435f99e02f3fa8af21139be216fc57c123779808dBen Lin * Listens for potential "drag-like" events and kick-start dragging as needed. Also allows external 4535f99e02f3fa8af21139be216fc57c123779808dBen Lin * direct call to {@code #startDrag(RecyclerView, View)} if explicit start is needed, such as long- 4635f99e02f3fa8af21139be216fc57c123779808dBen Lin * pressing on an item via touch. (e.g. {@link UserInputHandler#onLongPress(InputEvent)} via touch.) 4735f99e02f3fa8af21139be216fc57c123779808dBen Lin */ 48f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKayinterface DragStartListener { 4935f99e02f3fa8af21139be216fc57c123779808dBen Lin 50f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay public static final DragStartListener DUMMY = new DragStartListener() { 51f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay @Override 52f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay public boolean onMouseDragEvent(InputEvent event) { 53f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay return false; 5435f99e02f3fa8af21139be216fc57c123779808dBen Lin } 55f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay @Override 56f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay public boolean onTouchDragEvent(InputEvent event) { 5735f99e02f3fa8af21139be216fc57c123779808dBen Lin return false; 5835f99e02f3fa8af21139be216fc57c123779808dBen Lin } 59f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay }; 60f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay 61f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay boolean onMouseDragEvent(InputEvent event); 62f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay boolean onTouchDragEvent(InputEvent event); 63f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay 64f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay @VisibleForTesting 65f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay static class ActiveListener implements DragStartListener { 66f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay 67f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay private static String TAG = "DragStartListener"; 68f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay 69f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay private final State mState; 704f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay private final SelectionManager mSelectionMgr; 71f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay private final ViewFinder mViewFinder; 72f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay private final Function<View, String> mIdFinder; 73f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay private final ClipDataFactory mClipFactory; 74f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay private final Function<Selection, DragShadowBuilder> mShadowFactory; 755a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Lin private Function<Selection, List<DocumentInfo>> mDocsConverter; 76f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay 77f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay // use DragStartListener.create 78f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay @VisibleForTesting 79f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay public ActiveListener( 80f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay State state, 814f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay SelectionManager selectionMgr, 82f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay ViewFinder viewFinder, 83f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay Function<View, String> idFinder, 845a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Lin Function<Selection, List<DocumentInfo>> docsConverter, 85f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay ClipDataFactory clipFactory, 86f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay Function<Selection, DragShadowBuilder> shadowFactory) { 87f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay 88f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay mState = state; 89f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay mSelectionMgr = selectionMgr; 90f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay mViewFinder = viewFinder; 91f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay mIdFinder = idFinder; 925a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Lin mDocsConverter = docsConverter; 93f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay mClipFactory = clipFactory; 94f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay mShadowFactory = shadowFactory; 95f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay } 9635f99e02f3fa8af21139be216fc57c123779808dBen Lin 97f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay @Override 98f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay public final boolean onMouseDragEvent(InputEvent event) { 99f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay assert(Events.isMouseDragEvent(event)); 100f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay return startDrag(mViewFinder.findView(event.getX(), event.getY())); 10135f99e02f3fa8af21139be216fc57c123779808dBen Lin } 10235f99e02f3fa8af21139be216fc57c123779808dBen Lin 103f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay @Override 104f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay public final boolean onTouchDragEvent(InputEvent event) { 105f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay return startDrag(mViewFinder.findView(event.getX(), event.getY())); 106f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay } 10735f99e02f3fa8af21139be216fc57c123779808dBen Lin 108f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay /** 109f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay * May be called externally when drag is initiated from other event handling code. 110f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay */ 111f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay private final boolean startDrag(@Nullable View view) { 112f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay 113f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay if (view == null) { 114f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay if (DEBUG) Log.d(TAG, "Ignoring drag event, null view."); 115f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay return false; 116f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay } 117f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay 118f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay @Nullable String modelId = mIdFinder.apply(view); 119f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay if (modelId == null) { 120f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay if (DEBUG) Log.d(TAG, "Ignoring drag on view not represented in model."); 121f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay return false; 122f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay } 123f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay 124f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay 125f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay Selection selection = new Selection(); 126f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay 127f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay // User can drag an unselected item. Ideally if CTRL key was pressed 128f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay // we'd extend the selection, if not, the selection would be cleared. 129f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay // Buuuuuut, there's an impedance mismatch between event-handling policies, 130f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay // and drag and drop. So we only initiate drag of a single item when 131f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay // drag starts on an item that is unselected. This behavior 132f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay // would look like a bug, if it were not for the implicitly coupled 133f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay // behavior where we clear the selection in the UI (finish action mode) 134f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay // in DirectoryFragment#onDragStart. 135f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay if (!mSelectionMgr.getSelection().contains(modelId)) { 136f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay selection.add(modelId); 137f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay } else { 138f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay mSelectionMgr.getSelection(selection); 139f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay } 140f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay 1415a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Lin final List<DocumentInfo> invalidDest = mDocsConverter.apply(selection); 1425a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Lin invalidDest.add(mState.stack.peek()); 143f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay // NOTE: Preparation of the ClipData object can require a lot of time 144f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay // and ideally should be done in the background. Unfortunately 145f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay // the current code layout and framework assumptions don't support 146f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay // this. So for now, we could end up doing a bunch of i/o on main thread. 147f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay startDragAndDrop( 148f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay view, 149f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay mClipFactory.create( 150f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay selection, 151f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay FileOperationService.OPERATION_COPY), 152f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay mShadowFactory.apply(selection), 1535a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Lin invalidDest, 154f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay View.DRAG_FLAG_GLOBAL 155f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay | View.DRAG_FLAG_GLOBAL_URI_READ 156f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay | View.DRAG_FLAG_GLOBAL_URI_WRITE); 15735f99e02f3fa8af21139be216fc57c123779808dBen Lin 158f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay return true; 15935f99e02f3fa8af21139be216fc57c123779808dBen Lin } 16035f99e02f3fa8af21139be216fc57c123779808dBen Lin 161f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay /** 162f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay * This exists as a testing workaround since {@link View#startDragAndDrop} is final. 163f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay */ 164f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay @VisibleForTesting 165f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay void startDragAndDrop( 166f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay View view, 167f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay ClipData data, 168f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay DragShadowBuilder shadowBuilder, 1695a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Lin Object localState, 170f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay int flags) { 171f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay 1725a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Lin view.startDragAndDrop(data, shadowBuilder, localState, flags); 17335f99e02f3fa8af21139be216fc57c123779808dBen Lin } 17435f99e02f3fa8af21139be216fc57c123779808dBen Lin } 17535f99e02f3fa8af21139be216fc57c123779808dBen Lin 176f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay public static DragStartListener create( 177f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay IconHelper iconHelper, 178f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay Context context, 179f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay Model model, 1804f78ba643270b9d84da1952d8e408220b25ec6fdSteve McKay SelectionManager selectionMgr, 181f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay DocumentClipper clipper, 182f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay State state, 183f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay Function<View, String> idFinder, 184f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay ViewFinder viewFinder, 185f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay Drawable defaultDragIcon) { 186f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay 187f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay DragShadowBuilder.Factory shadowFactory = 188f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay new DragShadowBuilder.Factory(context, model, iconHelper, defaultDragIcon); 189f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay 190f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay return new ActiveListener( 191f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay state, 192f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay selectionMgr, 193f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay viewFinder, 194f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay idFinder, 1955a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Lin model::getDocuments, 196f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay (Selection selection, @OpType int operationType) -> { 197f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay return clipper.getClipDataForDocuments( 198f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay model::getItemUri, 199f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay selection, 200f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay FileOperationService.OPERATION_COPY); 201f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay }, 202f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay shadowFactory); 20335f99e02f3fa8af21139be216fc57c123779808dBen Lin } 20435f99e02f3fa8af21139be216fc57c123779808dBen Lin 20535f99e02f3fa8af21139be216fc57c123779808dBen Lin @FunctionalInterface 20635f99e02f3fa8af21139be216fc57c123779808dBen Lin interface ViewFinder { 20735f99e02f3fa8af21139be216fc57c123779808dBen Lin @Nullable View findView(float x, float y); 20835f99e02f3fa8af21139be216fc57c123779808dBen Lin } 209f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay 210f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay @FunctionalInterface 211f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay interface ClipDataFactory { 212f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay ClipData create(Selection selection, @OpType int operationType); 213f0fceb4cd731f70270970279791365cc6f6e4a49Steve McKay } 21435f99e02f3fa8af21139be216fc57c123779808dBen Lin} 215