1 /* 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 17 package com.android.documentsui; 18 19 import android.content.ClipData; 20 import android.graphics.drawable.Drawable; 21 import android.util.Log; 22 import android.view.DragEvent; 23 import android.view.View; 24 import android.view.View.OnDragListener; 25 26 import com.android.documentsui.ItemDragListener.DragHost; 27 import com.android.internal.annotations.VisibleForTesting; 28 29 import java.util.Timer; 30 import java.util.TimerTask; 31 32 import javax.annotation.Nullable; 33 34 /** 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 */ 38 public class ItemDragListener<H extends DragHost> implements OnDragListener { 39 40 private static final String TAG = "ItemDragListener"; 41 42 @VisibleForTesting 43 static final int DEFAULT_SPRING_TIMEOUT = 1500; 44 45 protected final H mDragHost; 46 private final Timer mHoverTimer; 47 private final int mSpringTimeout; 48 49 public ItemDragListener(H dragHost) { 50 this(dragHost, new Timer(), DEFAULT_SPRING_TIMEOUT); 51 } 52 53 public ItemDragListener(H dragHost, int springTimeout) { 54 this(dragHost, new Timer(), springTimeout); 55 } 56 57 @VisibleForTesting 58 protected ItemDragListener(H dragHost, Timer timer, int springTimeout) { 59 mDragHost = dragHost; 60 mHoverTimer = timer; 61 mSpringTimeout = springTimeout; 62 } 63 64 @Override 65 public boolean onDrag(final View v, DragEvent event) { 66 switch (event.getAction()) { 67 case DragEvent.ACTION_DRAG_STARTED: 68 return true; 69 case DragEvent.ACTION_DRAG_ENTERED: 70 handleEnteredEvent(v, event); 71 return true; 72 case DragEvent.ACTION_DRAG_LOCATION: 73 handleLocationEvent(v, event.getX(), event.getY()); 74 return true; 75 case DragEvent.ACTION_DRAG_EXITED: 76 mDragHost.onDragExited(v); 77 handleExitedEndedEvent(v, event); 78 return true; 79 case DragEvent.ACTION_DRAG_ENDED: 80 mDragHost.onDragEnded(); 81 handleExitedEndedEvent(v, event); 82 return true; 83 case DragEvent.ACTION_DROP: 84 return handleDropEvent(v, event); 85 } 86 87 return false; 88 } 89 90 private void handleEnteredEvent(View v, DragEvent event) { 91 mDragHost.onDragEntered(v); 92 @Nullable TimerTask task = createOpenTask(v, event); 93 mDragHost.setDropTargetHighlight(v, true); 94 if (task == null) { 95 return; 96 } 97 v.setTag(R.id.drag_hovering_tag, task); 98 mHoverTimer.schedule(task, mSpringTimeout); 99 } 100 101 private void handleLocationEvent(View v, float x, float y) { 102 Drawable background = v.getBackground(); 103 if (background != null) { 104 background.setHotspot(x, y); 105 } 106 } 107 108 private void handleExitedEndedEvent(View v, DragEvent event) { 109 mDragHost.setDropTargetHighlight(v, false); 110 TimerTask task = (TimerTask) v.getTag(R.id.drag_hovering_tag); 111 if (task != null) { 112 task.cancel(); 113 } 114 } 115 116 private boolean handleDropEvent(View v, DragEvent event) { 117 ClipData clipData = event.getClipData(); 118 if (clipData == null) { 119 Log.w(TAG, "Received invalid drop event with null clipdata. Ignoring."); 120 return false; 121 } 122 123 return handleDropEventChecked(v, event); 124 } 125 126 /** 127 * Sub-classes such as {@link DirectoryDragListener} can override this method and return null. 128 */ 129 public @Nullable TimerTask createOpenTask(final View v, DragEvent event) { 130 TimerTask task = new TimerTask() { 131 @Override 132 public void run() { 133 mDragHost.runOnUiThread(() -> { 134 mDragHost.onViewHovered(v); 135 }); 136 } 137 }; 138 return task; 139 } 140 141 /** 142 * Handles a drop event. Override it if you want to do something on drop event. It's called when 143 * {@link DragEvent#ACTION_DROP} happens. ClipData in DragEvent is guaranteed not null. 144 * 145 * @param v The view where user drops. 146 * @param event the drag event. 147 * @return true if this event is consumed; false otherwise 148 */ 149 public boolean handleDropEventChecked(View v, DragEvent event) { 150 return false; // we didn't handle the drop 151 } 152 153 /** 154 * An interface {@link ItemDragListener} uses to make some callbacks. 155 */ 156 public interface DragHost { 157 158 /** 159 * Runs this runnable in main thread. 160 */ 161 void runOnUiThread(Runnable runnable); 162 163 /** 164 * Highlights/unhighlights the view to visually indicate this view is being hovered. 165 * 166 * Called after {@link #onDragEntered(View)}, {@link #onDragExited(View)} 167 * or {@link #onDragEnded()}. 168 * 169 * @param v the view being hovered 170 * @param highlight true if highlight the view; false if unhighlight it 171 */ 172 void setDropTargetHighlight(View v, boolean highlight); 173 174 /** 175 * Notifies hovering timeout has elapsed 176 * @param v the view being hovered 177 */ 178 void onViewHovered(View v); 179 180 /** 181 * Notifies right away when drag shadow enters the view 182 * @param v the view which drop shadow just entered 183 */ 184 void onDragEntered(View v); 185 186 /** 187 * Notifies right away when drag shadow exits the view 188 * @param v the view which drop shadow just exited 189 */ 190 void onDragExited(View v); 191 192 /** 193 * Notifies when the drag and drop has ended. 194 */ 195 void onDragEnded(); 196 } 197 } 198