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