1 /* 2 * Copyright (C) 2010 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 android.view; 18 19 import android.content.ClipData; 20 import android.content.ClipDescription; 21 import android.os.Parcel; 22 import android.os.Parcelable; 23 24 //TODO: Improve Javadoc 25 /** 26 * Represents an event that is sent out by the system at various times during a drag and drop 27 * operation. It is a complex data structure that contains several important pieces of data about 28 * the operation and the underlying data. 29 * <p> 30 * View objects that receive a DragEvent call {@link #getAction()}, which returns 31 * an action type that indicates the state of the drag and drop operation. This allows a View 32 * object to react to a change in state by changing its appearance or performing other actions. 33 * For example, a View can react to the {@link #ACTION_DRAG_ENTERED} action type by 34 * by changing one or more colors in its displayed image. 35 * </p> 36 * <p> 37 * During a drag and drop operation, the system displays an image that the user drags. This image 38 * is called a drag shadow. Several action types reflect the position of the drag shadow relative 39 * to the View receiving the event. 40 * </p> 41 * <p> 42 * Most methods return valid data only for certain event actions. This is summarized in the 43 * following table. Each possible {@link #getAction()} value is listed in the first column. The 44 * other columns indicate which method or methods return valid data for that getAction() value: 45 * </p> 46 * <table> 47 * <tr> 48 * <th scope="col">getAction() Value</th> 49 * <th scope="col">getClipDescription()</th> 50 * <th scope="col">getLocalState()</th> 51 * <th scope="col">getX()</th> 52 * <th scope="col">getY()</th> 53 * <th scope="col">getClipData()</th> 54 * <th scope="col">getResult()</th> 55 * </tr> 56 * <tr> 57 * <td>ACTION_DRAG_STARTED</td> 58 * <td style="text-align: center;">X</td> 59 * <td style="text-align: center;">X</td> 60 * <td style="text-align: center;">X</td> 61 * <td style="text-align: center;">X</td> 62 * <td style="text-align: center;"> </td> 63 * <td style="text-align: center;"> </td> 64 * </tr> 65 * <tr> 66 * <td>ACTION_DRAG_ENTERED</td> 67 * <td style="text-align: center;">X</td> 68 * <td style="text-align: center;">X</td> 69 * <td style="text-align: center;"> </td> 70 * <td style="text-align: center;"> </td> 71 * <td style="text-align: center;"> </td> 72 * <td style="text-align: center;"> </td> 73 * </tr> 74 * <tr> 75 * <td>ACTION_DRAG_LOCATION</td> 76 * <td style="text-align: center;">X</td> 77 * <td style="text-align: center;">X</td> 78 * <td style="text-align: center;">X</td> 79 * <td style="text-align: center;">X</td> 80 * <td style="text-align: center;"> </td> 81 * <td style="text-align: center;"> </td> 82 * </tr> 83 * <tr> 84 * <td>ACTION_DRAG_EXITED</td> 85 * <td style="text-align: center;">X</td> 86 * <td style="text-align: center;">X</td> 87 * <td style="text-align: center;"> </td> 88 * <td style="text-align: center;"> </td> 89 * <td style="text-align: center;"> </td> 90 * <td style="text-align: center;"> </td> 91 * </tr> 92 * <tr> 93 * <td>ACTION_DROP</td> 94 * <td style="text-align: center;">X</td> 95 * <td style="text-align: center;">X</td> 96 * <td style="text-align: center;">X</td> 97 * <td style="text-align: center;">X</td> 98 * <td style="text-align: center;">X</td> 99 * <td style="text-align: center;"> </td> 100 * </tr> 101 * <tr> 102 * <td>ACTION_DRAG_ENDED</td> 103 * <td style="text-align: center;">X</td> 104 * <td style="text-align: center;">X</td> 105 * <td style="text-align: center;"> </td> 106 * <td style="text-align: center;"> </td> 107 * <td style="text-align: center;"> </td> 108 * <td style="text-align: center;">X</td> 109 * </tr> 110 * </table> 111 * <p> 112 * The {@link android.view.DragEvent#getAction()}, 113 * {@link android.view.DragEvent#describeContents()}, 114 * {@link android.view.DragEvent#writeToParcel(Parcel,int)}, and 115 * {@link android.view.DragEvent#toString()} methods always return valid data. 116 * </p> 117 * 118 * <div class="special reference"> 119 * <h3>Developer Guides</h3> 120 * <p>For a guide to implementing drag and drop features, read the 121 * <a href="{@docRoot}guide/topics/ui/drag-drop.html">Drag and Drop</a> developer guide.</p> 122 * </div> 123 */ 124 public class DragEvent implements Parcelable { 125 private static final boolean TRACK_RECYCLED_LOCATION = false; 126 127 int mAction; 128 float mX, mY; 129 ClipDescription mClipDescription; 130 ClipData mClipData; 131 Object mLocalState; 132 boolean mDragResult; 133 134 private DragEvent mNext; 135 private RuntimeException mRecycledLocation; 136 private boolean mRecycled; 137 138 private static final int MAX_RECYCLED = 10; 139 private static final Object gRecyclerLock = new Object(); 140 private static int gRecyclerUsed = 0; 141 private static DragEvent gRecyclerTop = null; 142 143 /** 144 * Action constant returned by {@link #getAction()}: Signals the start of a 145 * drag and drop operation. The View should return {@code true} from its 146 * {@link View#onDragEvent(DragEvent) onDragEvent()} handler method or 147 * {@link View.View.OnDragListener#onDrag(View,DragEvent) OnDragListener.onDrag()} listener 148 * if it can accept a drop. The onDragEvent() or onDrag() methods usually inspect the metadata 149 * from {@link #getClipDescription()} to determine if they can accept the data contained in 150 * this drag. For an operation that doesn't represent data transfer, these methods may 151 * perform other actions to determine whether or not the View should accept the drag. 152 * If the View wants to indicate that it is a valid drop target, it can also react by 153 * changing its appearance. 154 * <p> 155 * A View only receives further drag events if it returns {@code true} in response to 156 * ACTION_DRAG_STARTED. 157 * </p> 158 * @see #ACTION_DRAG_ENDED 159 */ 160 public static final int ACTION_DRAG_STARTED = 1; 161 162 /** 163 * Action constant returned by {@link #getAction()}: Sent to a View after 164 * {@link #ACTION_DRAG_ENTERED} if the drag shadow is still within the View object's bounding 165 * box. The {@link #getX()} and {@link #getY()} methods supply 166 * the X and Y position of of the drag point within the View object's bounding box. 167 * <p> 168 * A View receives an {@link #ACTION_DRAG_ENTERED} event before receiving any 169 * ACTION_DRAG_LOCATION events. 170 * </p> 171 * <p> 172 * The system stops sending ACTION_DRAG_LOCATION events to a View once the user moves the 173 * drag shadow out of the View object's bounding box. If the user moves the drag shadow back 174 * into the View object's bounding box, the View receives an ACTION_DRAG_ENTERED again before 175 * receiving any more ACTION_DRAG_LOCATION events. 176 * </p> 177 * @see #ACTION_DRAG_ENTERED 178 * @see #getX() 179 * @see #getY() 180 */ 181 public static final int ACTION_DRAG_LOCATION = 2; 182 183 /** 184 * Action constant returned by {@link #getAction()}: Signals to a View that the user 185 * has released the drag shadow, and the drag point is within the bounding box of the View. 186 * The View should retrieve the data from the DragEvent by calling {@link #getClipData()}. 187 * The methods {@link #getX()} and {@link #getY()} return the X and Y position of the drop point 188 * within the View object's bounding box. 189 * <p> 190 * The View should return {@code true} from its {@link View#onDragEvent(DragEvent)} 191 * handler or {@link View.View.OnDragListener#onDrag(View,DragEvent) OnDragListener.onDrag()} 192 * listener if it accepted the drop, and {@code false} if it ignored the drop. 193 * </p> 194 * <p> 195 * The View can also react to this action by changing its appearance. 196 * </p> 197 * @see #getClipData() 198 * @see #getX() 199 * @see #getY() 200 */ 201 public static final int ACTION_DROP = 3; 202 203 /** 204 * Action constant returned by {@link #getAction()}: Signals to a View that the drag and drop 205 * operation has concluded. A View that changed its appearance during the operation should 206 * return to its usual drawing state in response to this event. 207 * <p> 208 * All views that received an ACTION_DRAG_STARTED event will receive the 209 * ACTION_DRAG_ENDED event even if they are not currently visible when the drag ends. 210 * </p> 211 * <p> 212 * The View object can call {@link #getResult()} to see the result of the operation. 213 * If a View returned {@code true} in response to {@link #ACTION_DROP}, then 214 * getResult() returns {@code true}, otherwise it returns {@code false}. 215 * </p> 216 * @see #ACTION_DRAG_STARTED 217 * @see #getResult() 218 */ 219 public static final int ACTION_DRAG_ENDED = 4; 220 221 /** 222 * Action constant returned by {@link #getAction()}: Signals to a View that the drag point has 223 * entered the bounding box of the View. 224 * <p> 225 * If the View can accept a drop, it can react to ACTION_DRAG_ENTERED 226 * by changing its appearance in a way that tells the user that the View is the current 227 * drop target. 228 * </p> 229 * The system stops sending ACTION_DRAG_LOCATION events to a View once the user moves the 230 * drag shadow out of the View object's bounding box. If the user moves the drag shadow back 231 * into the View object's bounding box, the View receives an ACTION_DRAG_ENTERED again before 232 * receiving any more ACTION_DRAG_LOCATION events. 233 * </p> 234 * @see #ACTION_DRAG_ENTERED 235 * @see #ACTION_DRAG_LOCATION 236 */ 237 public static final int ACTION_DRAG_ENTERED = 5; 238 239 /** 240 * Action constant returned by {@link #getAction()}: Signals that the user has moved the 241 * drag shadow outside the bounding box of the View. 242 * The View can react by changing its appearance in a way that tells the user that 243 * View is no longer the immediate drop target. 244 * <p> 245 * After the system sends an ACTION_DRAG_EXITED event to the View, the View receives no more 246 * ACTION_DRAG_LOCATION events until the user drags the drag shadow back over the View. 247 * </p> 248 * 249 */ 250 public static final int ACTION_DRAG_EXITED = 6; 251 252 private DragEvent() { 253 } 254 255 private void init(int action, float x, float y, ClipDescription description, ClipData data, 256 Object localState, boolean result) { 257 mAction = action; 258 mX = x; 259 mY = y; 260 mClipDescription = description; 261 mClipData = data; 262 mLocalState = localState; 263 mDragResult = result; 264 } 265 266 static DragEvent obtain() { 267 return DragEvent.obtain(0, 0f, 0f, null, null, null, false); 268 } 269 270 /** @hide */ 271 public static DragEvent obtain(int action, float x, float y, Object localState, 272 ClipDescription description, ClipData data, boolean result) { 273 final DragEvent ev; 274 synchronized (gRecyclerLock) { 275 if (gRecyclerTop == null) { 276 ev = new DragEvent(); 277 ev.init(action, x, y, description, data, localState, result); 278 return ev; 279 } 280 ev = gRecyclerTop; 281 gRecyclerTop = ev.mNext; 282 gRecyclerUsed -= 1; 283 } 284 ev.mRecycledLocation = null; 285 ev.mRecycled = false; 286 ev.mNext = null; 287 288 ev.init(action, x, y, description, data, localState, result); 289 290 return ev; 291 } 292 293 /** @hide */ 294 public static DragEvent obtain(DragEvent source) { 295 return obtain(source.mAction, source.mX, source.mY, source.mLocalState, 296 source.mClipDescription, source.mClipData, source.mDragResult); 297 } 298 299 /** 300 * Inspect the action value of this event. 301 * @return One of the following action constants, in the order in which they usually occur 302 * during a drag and drop operation: 303 * <ul> 304 * <li>{@link #ACTION_DRAG_STARTED}</li> 305 * <li>{@link #ACTION_DRAG_ENTERED}</li> 306 * <li>{@link #ACTION_DRAG_LOCATION}</li> 307 * <li>{@link #ACTION_DROP}</li> 308 * <li>{@link #ACTION_DRAG_EXITED}</li> 309 * <li>{@link #ACTION_DRAG_ENDED}</li> 310 * </ul> 311 */ 312 public int getAction() { 313 return mAction; 314 } 315 316 /** 317 * Gets the X coordinate of the drag point. The value is only valid if the event action is 318 * {@link #ACTION_DRAG_LOCATION} or {@link #ACTION_DROP}. 319 * @return The current drag point's Y coordinate 320 */ 321 public float getX() { 322 return mX; 323 } 324 325 /** 326 * Gets the Y coordinate of the drag point. The value is valid if the 327 * event action is {@link #ACTION_DRAG_ENTERED}, {@link #ACTION_DRAG_LOCATION}, 328 * {@link #ACTION_DROP}, or {@link #ACTION_DRAG_EXITED}. 329 * @return The current drag point's Y coordinate 330 */ 331 public float getY() { 332 return mY; 333 } 334 335 /** 336 * Returns the {@link android.content.ClipData} object sent to the system as part of the call 337 * to 338 * {@link android.view.View#startDrag(ClipData,View.DragShadowBuilder,Object,int) startDrag()}. 339 * This method only returns valid data if the event action is {@link #ACTION_DROP}. 340 * @return The ClipData sent to the system by startDrag(). 341 */ 342 public ClipData getClipData() { 343 return mClipData; 344 } 345 346 /** 347 * Returns the {@link android.content.ClipDescription} object contained in the 348 * {@link android.content.ClipData} object sent to the system as part of the call to 349 * {@link android.view.View#startDrag(ClipData,View.DragShadowBuilder,Object,int) startDrag()}. 350 * The drag handler or listener for a View can use the metadata in this object to decide if the 351 * View can accept the dragged View object's data. 352 * <p> 353 * This method returns valid data for all event actions. 354 * @return The ClipDescription that was part of the ClipData sent to the system by startDrag(). 355 */ 356 public ClipDescription getClipDescription() { 357 return mClipDescription; 358 } 359 360 /** 361 * Returns the local state object sent to the system as part of the call to 362 * {@link android.view.View#startDrag(ClipData,View.DragShadowBuilder,Object,int) startDrag()}. 363 * The object is intended to provide local information about the drag and drop operation. For 364 * example, it can indicate whether the drag and drop operation is a copy or a move. 365 * <p> 366 * This method returns valid data for all event actions. 367 * </p> 368 * @return The local state object sent to the system by startDrag(). 369 */ 370 public Object getLocalState() { 371 return mLocalState; 372 } 373 374 /** 375 * <p> 376 * Returns an indication of the result of the drag and drop operation. 377 * This method only returns valid data if the action type is {@link #ACTION_DRAG_ENDED}. 378 * The return value depends on what happens after the user releases the drag shadow. 379 * </p> 380 * <p> 381 * If the user releases the drag shadow on a View that can accept a drop, the system sends an 382 * {@link #ACTION_DROP} event to the View object's drag event listener. If the listener 383 * returns {@code true}, then getResult() will return {@code true}. 384 * If the listener returns {@code false}, then getResult() returns {@code false}. 385 * </p> 386 * <p> 387 * Notice that getResult() also returns {@code false} if no {@link #ACTION_DROP} is sent. This 388 * happens, for example, when the user releases the drag shadow over an area outside of the 389 * application. In this case, the system sends out {@link #ACTION_DRAG_ENDED} for the current 390 * operation, but never sends out {@link #ACTION_DROP}. 391 * </p> 392 * @return {@code true} if a drag event listener returned {@code true} in response to 393 * {@link #ACTION_DROP}. If the system did not send {@link #ACTION_DROP} before 394 * {@link #ACTION_DRAG_ENDED}, or if the listener returned {@code false} in response to 395 * {@link #ACTION_DROP}, then {@code false} is returned. 396 */ 397 public boolean getResult() { 398 return mDragResult; 399 } 400 401 /** 402 * Recycle the DragEvent, to be re-used by a later caller. After calling 403 * this function you must never touch the event again. 404 * 405 * @hide 406 */ 407 public final void recycle() { 408 // Ensure recycle is only called once! 409 if (TRACK_RECYCLED_LOCATION) { 410 if (mRecycledLocation != null) { 411 throw new RuntimeException(toString() + " recycled twice!", mRecycledLocation); 412 } 413 mRecycledLocation = new RuntimeException("Last recycled here"); 414 } else { 415 if (mRecycled) { 416 throw new RuntimeException(toString() + " recycled twice!"); 417 } 418 mRecycled = true; 419 } 420 421 mClipData = null; 422 mClipDescription = null; 423 mLocalState = null; 424 425 synchronized (gRecyclerLock) { 426 if (gRecyclerUsed < MAX_RECYCLED) { 427 gRecyclerUsed++; 428 mNext = gRecyclerTop; 429 gRecyclerTop = this; 430 } 431 } 432 } 433 434 /** 435 * Returns a string containing a concise, human-readable representation of this DragEvent 436 * object. 437 * @return A string representation of the DragEvent object. 438 */ 439 @Override 440 public String toString() { 441 return "DragEvent{" + Integer.toHexString(System.identityHashCode(this)) 442 + " action=" + mAction + " @ (" + mX + ", " + mY + ") desc=" + mClipDescription 443 + " data=" + mClipData + " local=" + mLocalState + " result=" + mDragResult 444 + "}"; 445 } 446 447 /* Parcelable interface */ 448 449 /** 450 * Returns information about the {@link android.os.Parcel} representation of this DragEvent 451 * object. 452 * @return Information about the {@link android.os.Parcel} representation. 453 */ 454 public int describeContents() { 455 return 0; 456 } 457 458 /** 459 * Creates a {@link android.os.Parcel} object from this DragEvent object. 460 * @param dest A {@link android.os.Parcel} object in which to put the DragEvent object. 461 * @param flags Flags to store in the Parcel. 462 */ 463 public void writeToParcel(Parcel dest, int flags) { 464 dest.writeInt(mAction); 465 dest.writeFloat(mX); 466 dest.writeFloat(mY); 467 dest.writeInt(mDragResult ? 1 : 0); 468 if (mClipData == null) { 469 dest.writeInt(0); 470 } else { 471 dest.writeInt(1); 472 mClipData.writeToParcel(dest, flags); 473 } 474 if (mClipDescription == null) { 475 dest.writeInt(0); 476 } else { 477 dest.writeInt(1); 478 mClipDescription.writeToParcel(dest, flags); 479 } 480 } 481 482 /** 483 * A container for creating a DragEvent from a Parcel. 484 */ 485 public static final Parcelable.Creator<DragEvent> CREATOR = 486 new Parcelable.Creator<DragEvent>() { 487 public DragEvent createFromParcel(Parcel in) { 488 DragEvent event = DragEvent.obtain(); 489 event.mAction = in.readInt(); 490 event.mX = in.readFloat(); 491 event.mY = in.readFloat(); 492 event.mDragResult = (in.readInt() != 0); 493 if (in.readInt() != 0) { 494 event.mClipData = ClipData.CREATOR.createFromParcel(in); 495 } 496 if (in.readInt() != 0) { 497 event.mClipDescription = ClipDescription.CREATOR.createFromParcel(in); 498 } 499 return event; 500 } 501 502 public DragEvent[] newArray(int size) { 503 return new DragEvent[size]; 504 } 505 }; 506 } 507