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, event.getLocalState()); 77 // fall through 78 case DragEvent.ACTION_DRAG_ENDED: 79 handleExitedEndedEvent(v, event); 80 return true; 81 case DragEvent.ACTION_DROP: 82 return handleDropEvent(v, event); 83 } 84 85 return false; 86 } 87 88 private void handleEnteredEvent(View v, DragEvent event) { 89 mDragHost.onDragEntered(v, event.getLocalState()); 90 @Nullable TimerTask task = createOpenTask(v, event); 91 mDragHost.setDropTargetHighlight(v, event.getLocalState(), true); 92 if (task == null) { 93 return; 94 } 95 v.setTag(R.id.drag_hovering_tag, task); 96 mHoverTimer.schedule(task, mSpringTimeout); 97 } 98 99 private void handleLocationEvent(View v, float x, float y) { 100 Drawable background = v.getBackground(); 101 if (background != null) { 102 background.setHotspot(x, y); 103 } 104 } 105 106 private void handleExitedEndedEvent(View v, DragEvent event) { 107 mDragHost.setDropTargetHighlight(v, event.getLocalState(), false); 108 TimerTask task = (TimerTask) v.getTag(R.id.drag_hovering_tag); 109 if (task != null) { 110 task.cancel(); 111 } 112 } 113 114 private boolean handleDropEvent(View v, DragEvent event) { 115 ClipData clipData = event.getClipData(); 116 if (clipData == null) { 117 Log.w(TAG, "Received invalid drop event with null clipdata. Ignoring."); 118 return false; 119 } 120 121 return handleDropEventChecked(v, event); 122 } 123 124 /** 125 * Sub-classes such as {@link DirectoryDragListener} can override this method and return null. 126 */ 127 public @Nullable TimerTask createOpenTask(final View v, DragEvent event) { 128 TimerTask task = new TimerTask() { 129 @Override 130 public void run() { 131 mDragHost.runOnUiThread(() -> { 132 mDragHost.onViewHovered(v); 133 }); 134 } 135 }; 136 return task; 137 } 138 139 /** 140 * Handles a drop event. Override it if you want to do something on drop event. It's called when 141 * {@link DragEvent#ACTION_DROP} happens. ClipData in DragEvent is guaranteed not null. 142 * 143 * @param v The view where user drops. 144 * @param event the drag event. 145 * @return true if this event is consumed; false otherwise 146 */ 147 public boolean handleDropEventChecked(View v, DragEvent event) { 148 return false; // we didn't handle the drop 149 } 150 151 /** 152 * An interface {@link ItemDragListener} uses to make some callbacks. 153 */ 154 public interface DragHost { 155 156 /** 157 * Runs this runnable in main thread. 158 */ 159 void runOnUiThread(Runnable runnable); 160 161 /** 162 * Highlights/unhighlights the view to visually indicate this view is being hovered. 163 * @param v the view being hovered 164 * @param localState the Local state object given by DragEvent 165 * @param highlight true if highlight the view; false if unhighlight it 166 */ 167 void setDropTargetHighlight(View v, Object localState, boolean highlight); 168 169 /** 170 * Notifies hovering timeout has elapsed 171 * @param v the view being hovered 172 */ 173 void onViewHovered(View v); 174 175 /** 176 * Notifies right away when drag shadow enters the view 177 * @param v the view which drop shadow just entered 178 * @param localState the Local state object given by DragEvent 179 */ 180 void onDragEntered(View v, Object localState); 181 182 /** 183 * Notifies right away when drag shadow exits the view 184 * @param v the view which drop shadow just exited 185 * @param localState the Local state object given by DragEvent 186 */ 187 void onDragExited(View v, Object localState); 188 } 189 } 190