Home | History | Annotate | Download | only in view
      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;">&nbsp;</td>
     66  *      <td style="text-align: center;">&nbsp;</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;">&nbsp;</td>
     73  *      <td style="text-align: center;">&nbsp;</td>
     74  *      <td style="text-align: center;">&nbsp;</td>
     75  *      <td style="text-align: center;">&nbsp;</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;">&nbsp;</td>
     84  *      <td style="text-align: center;">&nbsp;</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;">&nbsp;</td>
     91  *      <td style="text-align: center;">&nbsp;</td>
     92  *      <td style="text-align: center;">&nbsp;</td>
     93  *      <td style="text-align: center;">&nbsp;</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;">&nbsp;</td>
    103  *  </tr>
    104  *  <tr>
    105  *      <td>ACTION_DRAG_ENDED</td>
    106  *      <td style="text-align: center;">&nbsp;</td>
    107  *      <td style="text-align: center;">X</td>
    108  *      <td style="text-align: center;">&nbsp;</td>
    109  *      <td style="text-align: center;">&nbsp;</td>
    110  *      <td style="text-align: center;">&nbsp;</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