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