blob: 530b5509949891e9adfa641a9cc2f70f48a90b1d [file] [log] [blame]
Garfield, Tan804133e2016-04-20 15:13:56 -07001/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.documentsui;
18
19import android.content.ClipData;
Garfield, Tan57facaf72016-05-27 15:02:35 -070020import android.graphics.drawable.Drawable;
Garfield, Tan804133e2016-04-20 15:13:56 -070021import android.util.Log;
22import android.view.DragEvent;
23import android.view.View;
24import android.view.View.OnDragListener;
Garfield, Tan804133e2016-04-20 15:13:56 -070025
26import com.android.documentsui.ItemDragListener.DragHost;
Jeff Sharkeya4ff00f2018-07-09 14:57:51 -060027import androidx.annotation.VisibleForTesting;
Garfield, Tan804133e2016-04-20 15:13:56 -070028
29import java.util.Timer;
30import java.util.TimerTask;
31
Ben Lin5a305b42016-09-08 11:33:07 -070032import javax.annotation.Nullable;
33
Garfield, Tan804133e2016-04-20 15:13:56 -070034/**
35 * An {@link OnDragListener} that adds support for "spring loading views". Use this when you want
36 * items to pop-open when user hovers on them during a drag n drop.
37 */
38public class ItemDragListener<H extends DragHost> implements OnDragListener {
39
40 private static final String TAG = "ItemDragListener";
41
42 @VisibleForTesting
Ben Linb1ab6962017-03-14 10:48:57 -070043 static final int DEFAULT_SPRING_TIMEOUT = 1500;
Garfield, Tan804133e2016-04-20 15:13:56 -070044
45 protected final H mDragHost;
46 private final Timer mHoverTimer;
Ben Linb1ab6962017-03-14 10:48:57 -070047 private final int mSpringTimeout;
Garfield, Tan804133e2016-04-20 15:13:56 -070048
49 public ItemDragListener(H dragHost) {
Ben Linb1ab6962017-03-14 10:48:57 -070050 this(dragHost, new Timer(), DEFAULT_SPRING_TIMEOUT);
51 }
52
53 public ItemDragListener(H dragHost, int springTimeout) {
54 this(dragHost, new Timer(), springTimeout);
Garfield, Tan804133e2016-04-20 15:13:56 -070055 }
56
57 @VisibleForTesting
Ben Linb1ab6962017-03-14 10:48:57 -070058 protected ItemDragListener(H dragHost, Timer timer, int springTimeout) {
Garfield, Tan804133e2016-04-20 15:13:56 -070059 mDragHost = dragHost;
60 mHoverTimer = timer;
Ben Linb1ab6962017-03-14 10:48:57 -070061 mSpringTimeout = springTimeout;
Garfield, Tan804133e2016-04-20 15:13:56 -070062 }
63
64 @Override
65 public boolean onDrag(final View v, DragEvent event) {
Riddle Hsuded3dc52018-05-17 21:27:07 +080066 if (!mDragHost.canHandleDragEvent(v)) {
67 return false;
68 }
69
Garfield, Tan804133e2016-04-20 15:13:56 -070070 switch (event.getAction()) {
71 case DragEvent.ACTION_DRAG_STARTED:
72 return true;
73 case DragEvent.ACTION_DRAG_ENTERED:
Ben Lin5a305b42016-09-08 11:33:07 -070074 handleEnteredEvent(v, event);
Garfield, Tan804133e2016-04-20 15:13:56 -070075 return true;
76 case DragEvent.ACTION_DRAG_LOCATION:
Garfield, Tan57facaf72016-05-27 15:02:35 -070077 handleLocationEvent(v, event.getX(), event.getY());
Garfield, Tan804133e2016-04-20 15:13:56 -070078 return true;
79 case DragEvent.ACTION_DRAG_EXITED:
Garfield Tanda2c0f02017-04-11 13:47:58 -070080 mDragHost.onDragExited(v);
81 handleExitedEndedEvent(v, event);
82 return true;
Garfield, Tan804133e2016-04-20 15:13:56 -070083 case DragEvent.ACTION_DRAG_ENDED:
Garfield Tanda2c0f02017-04-11 13:47:58 -070084 mDragHost.onDragEnded();
Ben Lin166c5c62016-11-01 12:14:38 -070085 handleExitedEndedEvent(v, event);
Garfield, Tan804133e2016-04-20 15:13:56 -070086 return true;
87 case DragEvent.ACTION_DROP:
88 return handleDropEvent(v, event);
89 }
90
91 return false;
92 }
93
Ben Lin5a305b42016-09-08 11:33:07 -070094 private void handleEnteredEvent(View v, DragEvent event) {
Garfield Tanda2c0f02017-04-11 13:47:58 -070095 mDragHost.onDragEntered(v);
Ben Lin5a305b42016-09-08 11:33:07 -070096 @Nullable TimerTask task = createOpenTask(v, event);
Garfield Tanda2c0f02017-04-11 13:47:58 -070097 mDragHost.setDropTargetHighlight(v, true);
Ben Lin5a305b42016-09-08 11:33:07 -070098 if (task == null) {
99 return;
100 }
Garfield, Tan804133e2016-04-20 15:13:56 -0700101 v.setTag(R.id.drag_hovering_tag, task);
Ben Linb1ab6962017-03-14 10:48:57 -0700102 mHoverTimer.schedule(task, mSpringTimeout);
Garfield, Tan804133e2016-04-20 15:13:56 -0700103 }
104
Garfield, Tan57facaf72016-05-27 15:02:35 -0700105 private void handleLocationEvent(View v, float x, float y) {
106 Drawable background = v.getBackground();
107 if (background != null) {
108 background.setHotspot(x, y);
109 }
110 }
111
Ben Lin166c5c62016-11-01 12:14:38 -0700112 private void handleExitedEndedEvent(View v, DragEvent event) {
Garfield Tanda2c0f02017-04-11 13:47:58 -0700113 mDragHost.setDropTargetHighlight(v, false);
Garfield, Tan804133e2016-04-20 15:13:56 -0700114 TimerTask task = (TimerTask) v.getTag(R.id.drag_hovering_tag);
115 if (task != null) {
116 task.cancel();
117 }
118 }
119
120 private boolean handleDropEvent(View v, DragEvent event) {
121 ClipData clipData = event.getClipData();
122 if (clipData == null) {
123 Log.w(TAG, "Received invalid drop event with null clipdata. Ignoring.");
124 return false;
125 }
126
127 return handleDropEventChecked(v, event);
128 }
129
Ben Lin5a305b42016-09-08 11:33:07 -0700130 /**
131 * Sub-classes such as {@link DirectoryDragListener} can override this method and return null.
132 */
133 public @Nullable TimerTask createOpenTask(final View v, DragEvent event) {
Garfield, Tan804133e2016-04-20 15:13:56 -0700134 TimerTask task = new TimerTask() {
135 @Override
136 public void run() {
137 mDragHost.runOnUiThread(() -> {
138 mDragHost.onViewHovered(v);
139 });
140 }
141 };
142 return task;
143 }
144
145 /**
146 * Handles a drop event. Override it if you want to do something on drop event. It's called when
147 * {@link DragEvent#ACTION_DROP} happens. ClipData in DragEvent is guaranteed not null.
148 *
149 * @param v The view where user drops.
150 * @param event the drag event.
151 * @return true if this event is consumed; false otherwise
152 */
153 public boolean handleDropEventChecked(View v, DragEvent event) {
154 return false; // we didn't handle the drop
155 }
156
157 /**
158 * An interface {@link ItemDragListener} uses to make some callbacks.
159 */
160 public interface DragHost {
161
162 /**
163 * Runs this runnable in main thread.
164 */
165 void runOnUiThread(Runnable runnable);
166
167 /**
168 * Highlights/unhighlights the view to visually indicate this view is being hovered.
Garfield Tanda2c0f02017-04-11 13:47:58 -0700169 *
170 * Called after {@link #onDragEntered(View)}, {@link #onDragExited(View)}
171 * or {@link #onDragEnded()}.
172 *
Garfield, Tan804133e2016-04-20 15:13:56 -0700173 * @param v the view being hovered
174 * @param highlight true if highlight the view; false if unhighlight it
175 */
Garfield Tanda2c0f02017-04-11 13:47:58 -0700176 void setDropTargetHighlight(View v, boolean highlight);
Garfield, Tan804133e2016-04-20 15:13:56 -0700177
178 /**
179 * Notifies hovering timeout has elapsed
180 * @param v the view being hovered
181 */
182 void onViewHovered(View v);
Ben Lin7f72a3c2016-09-27 16:37:28 -0700183
184 /**
185 * Notifies right away when drag shadow enters the view
186 * @param v the view which drop shadow just entered
187 */
Garfield Tanda2c0f02017-04-11 13:47:58 -0700188 void onDragEntered(View v);
Ben Lind0202122016-11-10 18:00:12 -0800189
190 /**
191 * Notifies right away when drag shadow exits the view
192 * @param v the view which drop shadow just exited
Ben Lind0202122016-11-10 18:00:12 -0800193 */
Garfield Tanda2c0f02017-04-11 13:47:58 -0700194 void onDragExited(View v);
195
196 /**
197 * Notifies when the drag and drop has ended.
198 */
199 void onDragEnded();
Riddle Hsuded3dc52018-05-17 21:27:07 +0800200
201 /**
202 * Whether drag events can dispatch to the view.
203 * @param v the view being to receive drag events
204 */
205 default boolean canHandleDragEvent(View v) {
206 return true;
207 }
Garfield, Tan804133e2016-04-20 15:13:56 -0700208 }
209}