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