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