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