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