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