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