1804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan/*
2804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan * Copyright (C) 2016 The Android Open Source Project
3804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan *
4804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan * Licensed under the Apache License, Version 2.0 (the "License");
5804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan * you may not use this file except in compliance with the License.
6804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan * You may obtain a copy of the License at
7804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan *
8804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan *      http://www.apache.org/licenses/LICENSE-2.0
9804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan *
10804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan * Unless required by applicable law or agreed to in writing, software
11804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan * distributed under the License is distributed on an "AS IS" BASIS,
12804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan * See the License for the specific language governing permissions and
14804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan * limitations under the License.
15804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan */
16804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan
17804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tanpackage com.android.documentsui;
18804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan
19804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tanimport android.content.ClipData;
2057facaf76bc8d23b539518a342e1a126b51b64ceGarfield, Tanimport android.graphics.drawable.Drawable;
21804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tanimport android.util.Log;
22804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tanimport android.view.DragEvent;
23804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tanimport android.view.View;
24804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tanimport android.view.View.OnDragListener;
25804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan
26804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tanimport com.android.documentsui.ItemDragListener.DragHost;
27804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tanimport com.android.internal.annotations.VisibleForTesting;
28804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan
29804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tanimport java.util.Timer;
30804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tanimport java.util.TimerTask;
31804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan
325a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Linimport javax.annotation.Nullable;
335a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Lin
34804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan/**
35804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan * An {@link OnDragListener} that adds support for "spring loading views". Use this when you want
36804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan * items to pop-open when user hovers on them during a drag n drop.
37804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan */
38804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tanpublic class ItemDragListener<H extends DragHost> implements OnDragListener {
39804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan
40804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan    private static final String TAG = "ItemDragListener";
41804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan
42804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan    @VisibleForTesting
43b1ab696d110e25a09ac2e9d1af1be3c7c8b1444dBen Lin    static final int DEFAULT_SPRING_TIMEOUT = 1500;
44804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan
45804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan    protected final H mDragHost;
46804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan    private final Timer mHoverTimer;
47b1ab696d110e25a09ac2e9d1af1be3c7c8b1444dBen Lin    private final int mSpringTimeout;
48804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan
49804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan    public ItemDragListener(H dragHost) {
50b1ab696d110e25a09ac2e9d1af1be3c7c8b1444dBen Lin        this(dragHost, new Timer(), DEFAULT_SPRING_TIMEOUT);
51b1ab696d110e25a09ac2e9d1af1be3c7c8b1444dBen Lin    }
52b1ab696d110e25a09ac2e9d1af1be3c7c8b1444dBen Lin
53b1ab696d110e25a09ac2e9d1af1be3c7c8b1444dBen Lin    public ItemDragListener(H dragHost, int springTimeout) {
54b1ab696d110e25a09ac2e9d1af1be3c7c8b1444dBen Lin        this(dragHost, new Timer(), springTimeout);
55804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan    }
56804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan
57804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan    @VisibleForTesting
58b1ab696d110e25a09ac2e9d1af1be3c7c8b1444dBen Lin    protected ItemDragListener(H dragHost, Timer timer, int springTimeout) {
59804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan        mDragHost = dragHost;
60804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan        mHoverTimer = timer;
61b1ab696d110e25a09ac2e9d1af1be3c7c8b1444dBen Lin        mSpringTimeout = springTimeout;
62804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan    }
63804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan
64804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan    @Override
65804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan    public boolean onDrag(final View v, DragEvent event) {
66804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan        switch (event.getAction()) {
67804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan            case DragEvent.ACTION_DRAG_STARTED:
68804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan                return true;
69804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan            case DragEvent.ACTION_DRAG_ENTERED:
705a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Lin                handleEnteredEvent(v, event);
71804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan                return true;
72804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan            case DragEvent.ACTION_DRAG_LOCATION:
7357facaf76bc8d23b539518a342e1a126b51b64ceGarfield, Tan                handleLocationEvent(v, event.getX(), event.getY());
74804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan                return true;
75804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan            case DragEvent.ACTION_DRAG_EXITED:
76da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan                mDragHost.onDragExited(v);
77da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan                handleExitedEndedEvent(v, event);
78da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan                return true;
79804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan            case DragEvent.ACTION_DRAG_ENDED:
80da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan                mDragHost.onDragEnded();
81166c5c6fc17461cb52f2b83eaec86cfc15b53004Ben Lin                handleExitedEndedEvent(v, event);
82804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan                return true;
83804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan            case DragEvent.ACTION_DROP:
84804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan                return handleDropEvent(v, event);
85804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan        }
86804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan
87804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan        return false;
88804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan    }
89804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan
905a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Lin    private void handleEnteredEvent(View v, DragEvent event) {
91da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan        mDragHost.onDragEntered(v);
925a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Lin        @Nullable TimerTask task = createOpenTask(v, event);
93da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan        mDragHost.setDropTargetHighlight(v, true);
945a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Lin        if (task == null) {
955a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Lin            return;
965a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Lin        }
97804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan        v.setTag(R.id.drag_hovering_tag, task);
98b1ab696d110e25a09ac2e9d1af1be3c7c8b1444dBen Lin        mHoverTimer.schedule(task, mSpringTimeout);
99804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan    }
100804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan
10157facaf76bc8d23b539518a342e1a126b51b64ceGarfield, Tan    private void handleLocationEvent(View v, float x, float y) {
10257facaf76bc8d23b539518a342e1a126b51b64ceGarfield, Tan        Drawable background = v.getBackground();
10357facaf76bc8d23b539518a342e1a126b51b64ceGarfield, Tan        if (background != null) {
10457facaf76bc8d23b539518a342e1a126b51b64ceGarfield, Tan            background.setHotspot(x, y);
10557facaf76bc8d23b539518a342e1a126b51b64ceGarfield, Tan        }
10657facaf76bc8d23b539518a342e1a126b51b64ceGarfield, Tan    }
10757facaf76bc8d23b539518a342e1a126b51b64ceGarfield, Tan
108166c5c6fc17461cb52f2b83eaec86cfc15b53004Ben Lin    private void handleExitedEndedEvent(View v, DragEvent event) {
109da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan        mDragHost.setDropTargetHighlight(v, false);
110804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan        TimerTask task = (TimerTask) v.getTag(R.id.drag_hovering_tag);
111804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan        if (task != null) {
112804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan            task.cancel();
113804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan        }
114804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan    }
115804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan
116804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan    private boolean handleDropEvent(View v, DragEvent event) {
117804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan        ClipData clipData = event.getClipData();
118804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan        if (clipData == null) {
119804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan            Log.w(TAG, "Received invalid drop event with null clipdata. Ignoring.");
120804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan            return false;
121804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan        }
122804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan
123804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan        return handleDropEventChecked(v, event);
124804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan    }
125804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan
1265a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Lin    /**
1275a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Lin     * Sub-classes such as {@link DirectoryDragListener} can override this method and return null.
1285a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Lin     */
1295a305b41ecd9c563c54bda9ff3c0d0f3739c5bdaBen Lin    public @Nullable TimerTask createOpenTask(final View v, DragEvent event) {
130804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan        TimerTask task = new TimerTask() {
131804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan            @Override
132804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan            public void run() {
133804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan                mDragHost.runOnUiThread(() -> {
134804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan                    mDragHost.onViewHovered(v);
135804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan                });
136804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan            }
137804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan        };
138804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan        return task;
139804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan    }
140804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan
141804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan    /**
142804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan     * Handles a drop event. Override it if you want to do something on drop event. It's called when
143804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan     * {@link DragEvent#ACTION_DROP} happens. ClipData in DragEvent is guaranteed not null.
144804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan     *
145804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan     * @param v The view where user drops.
146804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan     * @param event the drag event.
147804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan     * @return true if this event is consumed; false otherwise
148804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan     */
149804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan    public boolean handleDropEventChecked(View v, DragEvent event) {
150804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan        return false; // we didn't handle the drop
151804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan    }
152804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan
153804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan    /**
154804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan     * An interface {@link ItemDragListener} uses to make some callbacks.
155804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan     */
156804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan    public interface DragHost {
157804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan
158804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan        /**
159804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan         * Runs this runnable in main thread.
160804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan         */
161804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan        void runOnUiThread(Runnable runnable);
162804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan
163804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan        /**
164804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan         * Highlights/unhighlights the view to visually indicate this view is being hovered.
165da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan         *
166da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan         * Called after {@link #onDragEntered(View)}, {@link #onDragExited(View)}
167da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan         * or {@link #onDragEnded()}.
168da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan         *
169804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan         * @param v the view being hovered
170804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan         * @param highlight true if highlight the view; false if unhighlight it
171804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan         */
172da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan        void setDropTargetHighlight(View v, boolean highlight);
173804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan
174804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan        /**
175804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan         * Notifies hovering timeout has elapsed
176804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan         * @param v the view being hovered
177804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan         */
178804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan        void onViewHovered(View v);
1797f72a3c30ea89e2655ea6c453f8964bfedf4474bBen Lin
1807f72a3c30ea89e2655ea6c453f8964bfedf4474bBen Lin        /**
1817f72a3c30ea89e2655ea6c453f8964bfedf4474bBen Lin         * Notifies right away when drag shadow enters the view
1827f72a3c30ea89e2655ea6c453f8964bfedf4474bBen Lin         * @param v the view which drop shadow just entered
1837f72a3c30ea89e2655ea6c453f8964bfedf4474bBen Lin         */
184da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan        void onDragEntered(View v);
185d020212fdad3be1537dfa47ff5b67e3bc4272d5eBen Lin
186d020212fdad3be1537dfa47ff5b67e3bc4272d5eBen Lin        /**
187d020212fdad3be1537dfa47ff5b67e3bc4272d5eBen Lin         * Notifies right away when drag shadow exits the view
188d020212fdad3be1537dfa47ff5b67e3bc4272d5eBen Lin         * @param v the view which drop shadow just exited
189d020212fdad3be1537dfa47ff5b67e3bc4272d5eBen Lin         */
190da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan        void onDragExited(View v);
191da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan
192da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan        /**
193da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan         * Notifies when the drag and drop has ended.
194da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan         */
195da2c0f0b075ad9f770182e706c2ec158989568a7Garfield Tan        void onDragEnded();
196804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan    }
197804133e4ca98ffa168cd547793054b594cf6d9ccGarfield, Tan}
198