1da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan/* 2da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * Copyright (C) 2017 The Android Open Source Project 3da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * 4da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * Licensed under the Apache License, Version 2.0 (the "License"); 5da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * you may not use this file except in compliance with the License. 6da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * You may obtain a copy of the License at 7da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * 8da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * http://www.apache.org/licenses/LICENSE-2.0 9da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * 10da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * Unless required by applicable law or agreed to in writing, software 11da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * distributed under the License is distributed on an "AS IS" BASIS, 12da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * See the License for the specific language governing permissions and 14da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * limitations under the License. 15da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan */ 16da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 17da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tanpackage com.android.documentsui; 18da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 19da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tanimport android.annotation.IntDef; 20da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tanimport android.annotation.Nullable; 21da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tanimport android.content.ClipData; 22da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tanimport android.content.Context; 23da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tanimport android.graphics.drawable.Drawable; 24da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tanimport android.net.Uri; 25da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tanimport android.provider.DocumentsContract; 26da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tanimport android.support.annotation.VisibleForTesting; 27da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tanimport android.view.DragEvent; 28da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tanimport android.view.KeyEvent; 29da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tanimport android.view.View; 30da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 317f7ee10edaac1214e23f267a595014b510562d1eBen Linimport com.android.documentsui.MenuManager.SelectionDetails; 32da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tanimport com.android.documentsui.base.DocumentInfo; 33da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tanimport com.android.documentsui.base.DocumentStack; 34da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tanimport com.android.documentsui.base.RootInfo; 35da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tanimport com.android.documentsui.clipping.DocumentClipper; 36da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tanimport com.android.documentsui.dirlist.IconHelper; 37da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tanimport com.android.documentsui.services.FileOperationService; 38da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tanimport com.android.documentsui.services.FileOperationService.OpType; 39da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tanimport com.android.documentsui.services.FileOperations; 40da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 41da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tanimport java.lang.annotation.Retention; 42da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tanimport java.lang.annotation.RetentionPolicy; 43da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tanimport java.util.ArrayList; 44da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tanimport java.util.List; 45da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 46da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan/** 47da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * Manager that tracks control key state, calculates the default file operation (move or copy) 48da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * when user drops, and updates drag shadow state. 49da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan */ 50da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tanpublic interface DragAndDropManager { 51da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 52da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan @IntDef({ STATE_NOT_ALLOWED, STATE_UNKNOWN, STATE_MOVE, STATE_COPY }) 53da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan @Retention(RetentionPolicy.SOURCE) 54da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan @interface State {} 55da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan int STATE_UNKNOWN = 0; 56da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan int STATE_NOT_ALLOWED = 1; 57da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan int STATE_MOVE = 2; 58da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan int STATE_COPY = 3; 59da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 60da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan /** 61da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * Intercepts and handles a {@link KeyEvent}. Used to track the state of Ctrl key state. 62da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan */ 63da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan void onKeyEvent(KeyEvent event); 64da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 65da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan /** 66da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * Starts a drag and drop. 67da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * 68da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * @param v the view which 69da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * {@link View#startDragAndDrop(ClipData, View.DragShadowBuilder, Object, int)} will be 70da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * called. 71da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * @param srcs documents that are dragged 72da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * @param root the root in which documents being dragged are 73da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * @param invalidDest destinations that don't accept this drag and drop 74da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * @param iconHelper used to load document icons 752e81db6f2030ec6c8dd758c3020ce5db8363bf25Garfield Tan * @param parent {@link DocumentInfo} of the container of srcs 76da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan */ 77da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan void startDrag( 78da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan View v, 79da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan List<DocumentInfo> srcs, 80da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan RootInfo root, 81da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan List<Uri> invalidDest, 827f7ee10edaac1214e23f267a595014b510562d1eBen Lin SelectionDetails selectionDetails, 832e81db6f2030ec6c8dd758c3020ce5db8363bf25Garfield Tan IconHelper iconHelper, 842e81db6f2030ec6c8dd758c3020ce5db8363bf25Garfield Tan @Nullable DocumentInfo parent); 85da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 86da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan /** 87da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * Checks whether the document can be spring opened. 88da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * @param root the root in which the document is 89da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * @param doc the document to check 90da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * @return true if policy allows spring opening it; false otherwise 91da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan */ 92da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan boolean canSpringOpen(RootInfo root, DocumentInfo doc); 93da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 94da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan /** 95da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * Updates the state to {@link #STATE_NOT_ALLOWED} without any further checks. This is used when 96da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * the UI component that handles the drag event already has enough information to disallow 97da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * dropping by itself. 98da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * 99da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * @param v the view which {@link View#updateDragShadow(View.DragShadowBuilder)} will be called. 100da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan */ 101da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan void updateStateToNotAllowed(View v); 102da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 103da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan /** 104da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * Updates the state according to the destination passed. 105da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * @param v the view which {@link View#updateDragShadow(View.DragShadowBuilder)} will be called. 106da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * @param destRoot the root of the destination document. 107da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * @param destDoc the destination document. Can be null if this is TBD. Must be a folder. 108da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * @return the new state. Can be any state in {@link State}. 109da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan */ 110da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan @State int updateState( 111da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan View v, RootInfo destRoot, @Nullable DocumentInfo destDoc); 112da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 113da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan /** 114da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * Resets state back to {@link #STATE_UNKNOWN}. This is used when user drags items leaving a UI 115da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * component. 116da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * @param v the view which {@link View#updateDragShadow(View.DragShadowBuilder)} will be called. 117da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan */ 118da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan void resetState(View v); 119da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 120da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan /** 121da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * Drops items onto the a root. 122da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * 123da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * @param clipData the clip data that contains sources information. 124da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * @param localState used to determine if this is a multi-window drag and drop. 125da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * @param destRoot the target root 126da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * @param actions {@link ActionHandler} used to load root document. 127da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * @param callback callback called when file operation is rejected or scheduled. 128da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * @return true if target accepts this drop; false otherwise 129da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan */ 130da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan boolean drop(ClipData clipData, Object localState, RootInfo destRoot, ActionHandler actions, 131da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan FileOperations.Callback callback); 132da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 133da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan /** 134da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * Drops items onto the target. 135da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * 136da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * @param clipData the clip data that contains sources information. 137da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * @param localState used to determine if this is a multi-window drag and drop. 138da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * @param dstStack the document stack pointing to the destination folder. 139da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * @param callback callback called when file operation is rejected or scheduled. 140da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * @return true if target accepts this drop; false otherwise 141da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan */ 142da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan boolean drop(ClipData clipData, Object localState, DocumentStack dstStack, 143da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan FileOperations.Callback callback); 144da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 145da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan /** 146da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * Called when drag and drop ended. 147da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * 148da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * This can be called multiple times as multiple {@link View.OnDragListener} might delegate 149da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * {@link DragEvent#ACTION_DRAG_ENDED} events to this class so any work inside needs to be 150da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * idempotent. 151da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan */ 152da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan void dragEnded(); 153da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 154da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan static DragAndDropManager create(Context context, DocumentClipper clipper) { 155da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan return new RuntimeDragAndDropManager(context, clipper); 156da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 157da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 158da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan class RuntimeDragAndDropManager implements DragAndDropManager { 159da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan private static final String SRC_ROOT_KEY = "dragAndDropMgr:srcRoot"; 160da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 161da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan private final Context mContext; 162da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan private final DocumentClipper mClipper; 163da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan private final DragShadowBuilder mShadowBuilder; 164da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan private final Drawable mDefaultShadowIcon; 165da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 166da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan private @State int mState = STATE_UNKNOWN; 167da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 168da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan // Key events info. This is used to derive state when user drags items into a view to derive 169da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan // type of file operations. 170da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan private boolean mIsCtrlPressed; 171da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 172da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan // Drag events info. These are used to derive state and update drag shadow when user changes 173da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan // Ctrl key state. 174da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan private View mView; 175da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan private List<Uri> mInvalidDest; 176da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan private ClipData mClipData; 177da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan private RootInfo mDestRoot; 178da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan private DocumentInfo mDestDoc; 179da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 1802e81db6f2030ec6c8dd758c3020ce5db8363bf25Garfield Tan // Boolean flag for current drag and drop operation. Returns true if the files can only 1812e81db6f2030ec6c8dd758c3020ce5db8363bf25Garfield Tan // be copied (ie. files that don't support delete or remove). 1822e81db6f2030ec6c8dd758c3020ce5db8363bf25Garfield Tan private boolean mMustBeCopied; 1832e81db6f2030ec6c8dd758c3020ce5db8363bf25Garfield Tan 184da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan private RuntimeDragAndDropManager(Context context, DocumentClipper clipper) { 185da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan this( 186da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan context.getApplicationContext(), 187da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan clipper, 188da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan new DragShadowBuilder(context), 189da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan context.getDrawable(R.drawable.ic_doc_generic)); 190da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 191da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 192da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan @VisibleForTesting 193da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan RuntimeDragAndDropManager(Context context, DocumentClipper clipper, 194da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan DragShadowBuilder builder, Drawable defaultShadowIcon) { 195da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan mContext = context; 196da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan mClipper = clipper; 197da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan mShadowBuilder = builder; 198da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan mDefaultShadowIcon = defaultShadowIcon; 199da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 200da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 201da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan @Override 202da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan public void onKeyEvent(KeyEvent event) { 203da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan switch (event.getKeyCode()) { 204da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan case KeyEvent.KEYCODE_CTRL_LEFT: 205da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan case KeyEvent.KEYCODE_CTRL_RIGHT: 206da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan adjustCtrlKeyCount(event); 207da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 208da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 209da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 210da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan private void adjustCtrlKeyCount(KeyEvent event) { 211da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan assert(event.getKeyCode() == KeyEvent.KEYCODE_CTRL_LEFT 212da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan || event.getKeyCode() == KeyEvent.KEYCODE_CTRL_RIGHT); 213da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 214da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan mIsCtrlPressed = event.isCtrlPressed(); 215da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 216da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan // There is an ongoing drag and drop if mView is not null. 217da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan if (mView != null) { 218da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan // There is no need to update the state if current state is unknown or not allowed. 219da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan if (mState == STATE_COPY || mState == STATE_MOVE) { 220da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan updateState(mView, mDestRoot, mDestDoc); 221da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 222da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 223da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 224da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 225da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan @Override 226da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan public void startDrag( 227da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan View v, 228da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan List<DocumentInfo> srcs, 229da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan RootInfo root, 230da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan List<Uri> invalidDest, 2317f7ee10edaac1214e23f267a595014b510562d1eBen Lin SelectionDetails selectionDetails, 2322e81db6f2030ec6c8dd758c3020ce5db8363bf25Garfield Tan IconHelper iconHelper, 2332e81db6f2030ec6c8dd758c3020ce5db8363bf25Garfield Tan @Nullable DocumentInfo parent) { 234da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 235da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan mView = v; 236da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan mInvalidDest = invalidDest; 2377f7ee10edaac1214e23f267a595014b510562d1eBen Lin mMustBeCopied = !selectionDetails.canDelete(); 238da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 239da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan List<Uri> uris = new ArrayList<>(srcs.size()); 240da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan for (DocumentInfo doc : srcs) { 241da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan uris.add(doc.derivedUri); 242da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 2432e81db6f2030ec6c8dd758c3020ce5db8363bf25Garfield Tan mClipData = (parent == null) 2442e81db6f2030ec6c8dd758c3020ce5db8363bf25Garfield Tan ? mClipper.getClipDataForDocuments(uris, FileOperationService.OPERATION_UNKNOWN) 2452e81db6f2030ec6c8dd758c3020ce5db8363bf25Garfield Tan : mClipper.getClipDataForDocuments( 246da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan uris, FileOperationService.OPERATION_UNKNOWN, parent); 247da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan mClipData.getDescription().getExtras() 248da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan .putString(SRC_ROOT_KEY, root.getUri().toString()); 249da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 250da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan updateShadow(srcs, iconHelper); 251da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 252c0b6b3f1f43f9464156c6e4f974c416d8dbfe3c2Ben Lin int flag = View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_OPAQUE; 2537f7ee10edaac1214e23f267a595014b510562d1eBen Lin if (!selectionDetails.containsFilesInArchive()) { 254c0b6b3f1f43f9464156c6e4f974c416d8dbfe3c2Ben Lin flag |= View.DRAG_FLAG_GLOBAL_URI_READ 255c0b6b3f1f43f9464156c6e4f974c416d8dbfe3c2Ben Lin | View.DRAG_FLAG_GLOBAL_URI_WRITE; 256c0b6b3f1f43f9464156c6e4f974c416d8dbfe3c2Ben Lin } 257da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan startDragAndDrop( 258da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan v, 259da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan mClipData, 260da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan mShadowBuilder, 261da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan this, // Used to detect multi-window drag and drop 262c0b6b3f1f43f9464156c6e4f974c416d8dbfe3c2Ben Lin flag); 263da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 264da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 265da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan private void updateShadow(List<DocumentInfo> srcs, IconHelper iconHelper) { 266da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan final String title; 267da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan final Drawable icon; 268da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 269da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan final int size = srcs.size(); 270da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan if (size == 1) { 271da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan DocumentInfo doc = srcs.get(0); 272da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan title = doc.displayName; 273da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan icon = iconHelper.getDocumentIcon(mContext, doc); 274da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } else { 275da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan title = mContext.getResources() 276da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan .getQuantityString(R.plurals.elements_dragged, size, size); 277da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan icon = mDefaultShadowIcon; 278da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 279da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 280da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan mShadowBuilder.updateTitle(title); 281da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan mShadowBuilder.updateIcon(icon); 282da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 283da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan mShadowBuilder.onStateUpdated(STATE_UNKNOWN); 284da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 285da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 286da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan /** 287da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * A workaround of that 288da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * {@link View#startDragAndDrop(ClipData, View.DragShadowBuilder, Object, int)} is final. 289da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan */ 290da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan @VisibleForTesting 291da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan void startDragAndDrop(View v, ClipData clipData, DragShadowBuilder builder, 292da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan Object localState, int flags) { 293da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan v.startDragAndDrop(clipData, builder, localState, flags); 294da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 295da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 296da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan @Override 297da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan public boolean canSpringOpen(RootInfo root, DocumentInfo doc) { 298da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan return isValidDestination(root, doc.derivedUri); 299da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 300da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 301da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan @Override 302da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan public void updateStateToNotAllowed(View v) { 303da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan mView = v; 304da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan updateState(STATE_NOT_ALLOWED); 305da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 306da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 307da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan @Override 308da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan public @State int updateState( 309da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan View v, RootInfo destRoot, @Nullable DocumentInfo destDoc) { 310da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 311da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan mView = v; 312da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan mDestRoot = destRoot; 313da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan mDestDoc = destDoc; 314da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 315da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan if (!destRoot.supportsCreate()) { 316da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan updateState(STATE_NOT_ALLOWED); 317da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan return STATE_NOT_ALLOWED; 318da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 319da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 320da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan if (destDoc == null) { 321da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan updateState(STATE_UNKNOWN); 322da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan return STATE_UNKNOWN; 323da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 324da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 325da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan assert(destDoc.isDirectory()); 326da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 327da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan if (!destDoc.isCreateSupported() || mInvalidDest.contains(destDoc.derivedUri)) { 328da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan updateState(STATE_NOT_ALLOWED); 329da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan return STATE_NOT_ALLOWED; 330da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 331da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 332da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan @State int state; 333da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan final @OpType int opType = calculateOpType(mClipData, destRoot); 334da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan switch (opType) { 335da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan case FileOperationService.OPERATION_COPY: 336da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan state = STATE_COPY; 337da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan break; 338da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan case FileOperationService.OPERATION_MOVE: 339da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan state = STATE_MOVE; 340da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan break; 341da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan default: 342da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan // Should never happen 343da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan throw new IllegalStateException("Unknown opType: " + opType); 344da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 345da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 346da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan updateState(state); 347da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan return state; 348da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 349da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 350da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan @Override 351da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan public void resetState(View v) { 352da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan mView = v; 353da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 354da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan updateState(STATE_UNKNOWN); 355da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 356da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 357da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan private void updateState(@State int state) { 358da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan mState = state; 359da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 360da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan mShadowBuilder.onStateUpdated(state); 361da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan updateDragShadow(mView); 362da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 363da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 364da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan /** 365da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan * A workaround of that {@link View#updateDragShadow(View.DragShadowBuilder)} is final. 366da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan */ 367da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan @VisibleForTesting 368da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan void updateDragShadow(View v) { 369da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan v.updateDragShadow(mShadowBuilder); 370da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 371da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 372da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan @Override 373da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan public boolean drop(ClipData clipData, Object localState, RootInfo destRoot, 374da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan ActionHandler action, FileOperations.Callback callback) { 375da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 376da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan final Uri rootDocUri = 377da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan DocumentsContract.buildDocumentUri(destRoot.authority, destRoot.documentId); 378da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan if (!isValidDestination(destRoot, rootDocUri)) { 379da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan return false; 380da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 381da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 3823b987ccda884319ec0129b64410621f68eb2d7bdGarfield Tan // Calculate the op type now just in case user releases Ctrl key while we're obtaining 3833b987ccda884319ec0129b64410621f68eb2d7bdGarfield Tan // root document in the background. 3843b987ccda884319ec0129b64410621f68eb2d7bdGarfield Tan final @OpType int opType = calculateOpType(clipData, destRoot); 385da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan action.getRootDocument( 386da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan destRoot, 387da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan TimeoutTask.DEFAULT_TIMEOUT, 388da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan (DocumentInfo doc) -> { 3893b987ccda884319ec0129b64410621f68eb2d7bdGarfield Tan dropOnRootDocument(clipData, localState, destRoot, doc, opType, callback); 390da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan }); 391da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 392da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan return true; 393da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 394da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 3953b987ccda884319ec0129b64410621f68eb2d7bdGarfield Tan private void dropOnRootDocument( 3963b987ccda884319ec0129b64410621f68eb2d7bdGarfield Tan ClipData clipData, 3973b987ccda884319ec0129b64410621f68eb2d7bdGarfield Tan Object localState, 3983b987ccda884319ec0129b64410621f68eb2d7bdGarfield Tan RootInfo destRoot, 3993b987ccda884319ec0129b64410621f68eb2d7bdGarfield Tan @Nullable DocumentInfo destRootDoc, 4003b987ccda884319ec0129b64410621f68eb2d7bdGarfield Tan @OpType int opType, 4013b987ccda884319ec0129b64410621f68eb2d7bdGarfield Tan FileOperations.Callback callback) { 402da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan if (destRootDoc == null) { 403da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan callback.onOperationResult( 404da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan FileOperations.Callback.STATUS_FAILED, 4053b987ccda884319ec0129b64410621f68eb2d7bdGarfield Tan opType, 406da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 0); 407da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } else { 408da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan dropChecked( 4093b987ccda884319ec0129b64410621f68eb2d7bdGarfield Tan clipData, 4103b987ccda884319ec0129b64410621f68eb2d7bdGarfield Tan localState, 4113b987ccda884319ec0129b64410621f68eb2d7bdGarfield Tan new DocumentStack(destRoot, destRootDoc), 4123b987ccda884319ec0129b64410621f68eb2d7bdGarfield Tan opType, 4133b987ccda884319ec0129b64410621f68eb2d7bdGarfield Tan callback); 414da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 415da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 416da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 417da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan @Override 418da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan public boolean drop(ClipData clipData, Object localState, DocumentStack dstStack, 419da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan FileOperations.Callback callback) { 420da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 421da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan if (!canCopyTo(dstStack)) { 422da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan return false; 423da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 424da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 4253b987ccda884319ec0129b64410621f68eb2d7bdGarfield Tan dropChecked( 4263b987ccda884319ec0129b64410621f68eb2d7bdGarfield Tan clipData, 4273b987ccda884319ec0129b64410621f68eb2d7bdGarfield Tan localState, 4283b987ccda884319ec0129b64410621f68eb2d7bdGarfield Tan dstStack, 4293b987ccda884319ec0129b64410621f68eb2d7bdGarfield Tan calculateOpType(clipData, dstStack.getRoot()), 4303b987ccda884319ec0129b64410621f68eb2d7bdGarfield Tan callback); 431da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan return true; 432da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 433da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 434da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan private void dropChecked(ClipData clipData, Object localState, DocumentStack dstStack, 4353b987ccda884319ec0129b64410621f68eb2d7bdGarfield Tan @OpType int opType, FileOperations.Callback callback) { 436da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 437da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan // Recognize multi-window drag and drop based on the fact that localState is not 438da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan // carried between processes. It will stop working when the localsState behavior 439da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan // is changed. The info about window should be passed in the localState then. 440da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan // The localState could also be null for copying from Recents in single window 441da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan // mode, but Recents doesn't offer this functionality (no directories). 442da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan Metrics.logUserAction(mContext, 443da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan localState == null ? Metrics.USER_ACTION_DRAG_N_DROP_MULTI_WINDOW 444da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan : Metrics.USER_ACTION_DRAG_N_DROP); 445da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 4463b987ccda884319ec0129b64410621f68eb2d7bdGarfield Tan mClipper.copyFromClipData(dstStack, clipData, opType, callback); 447da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 448da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 449da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan @Override 450da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan public void dragEnded() { 451da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan // Multiple drag listeners might delegate drag ended event to this method, so anything 452da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan // in this method needs to be idempotent. Otherwise we need to designate one listener 453da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan // that always exists and only let it notify us when drag ended, which will further 454da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan // complicate code and introduce one more coupling. This is a Android framework 455da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan // limitation. 456da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 457da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan mView = null; 458da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan mInvalidDest = null; 459da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan mClipData = null; 460da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan mDestDoc = null; 461da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan mDestRoot = null; 462c1a32aea59f47b06f3157a7f87ef5b9bf45f7627Ben Lin mMustBeCopied = false; 463da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 464da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 465da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan private @OpType int calculateOpType(ClipData clipData, RootInfo destRoot) { 466c1a32aea59f47b06f3157a7f87ef5b9bf45f7627Ben Lin if (mMustBeCopied) { 467c1a32aea59f47b06f3157a7f87ef5b9bf45f7627Ben Lin return FileOperationService.OPERATION_COPY; 468c1a32aea59f47b06f3157a7f87ef5b9bf45f7627Ben Lin } 469c1a32aea59f47b06f3157a7f87ef5b9bf45f7627Ben Lin 470da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan final String srcRootUri = clipData.getDescription().getExtras().getString(SRC_ROOT_KEY); 471da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan final String destRootUri = destRoot.getUri().toString(); 472da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 473da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan assert(srcRootUri != null); 474da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan assert(destRootUri != null); 475da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 476da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan if (srcRootUri.equals(destRootUri)) { 477da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan return mIsCtrlPressed 478da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan ? FileOperationService.OPERATION_COPY 479da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan : FileOperationService.OPERATION_MOVE; 480da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } else { 481da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan return mIsCtrlPressed 482da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan ? FileOperationService.OPERATION_MOVE 483da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan : FileOperationService.OPERATION_COPY; 484da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 485da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 486da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 487da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan private boolean canCopyTo(DocumentStack dstStack) { 488da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan final RootInfo root = dstStack.getRoot(); 489da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan final DocumentInfo dst = dstStack.peek(); 490da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan return isValidDestination(root, dst.derivedUri); 491da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 492da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan 493da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan private boolean isValidDestination(RootInfo root, Uri dstUri) { 494da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan return root.supportsCreate() && !mInvalidDest.contains(dstUri); 495da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 496da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan } 497da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan} 498