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