| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.view; |
| |
| import android.content.ClipData; |
| import android.content.ClipDescription; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| |
| import com.android.internal.view.IDragAndDropPermissions; |
| |
| //TODO: Improve Javadoc |
| /** |
| * Represents an event that is sent out by the system at various times during a drag and drop |
| * operation. It is a data structure that contains several important pieces of data about |
| * the operation and the underlying data. |
| * <p> |
| * View objects that receive a DragEvent call {@link #getAction()}, which returns |
| * an action type that indicates the state of the drag and drop operation. This allows a View |
| * object to react to a change in state by changing its appearance or performing other actions. |
| * For example, a View can react to the {@link #ACTION_DRAG_ENTERED} action type by |
| * by changing one or more colors in its displayed image. |
| * </p> |
| * <p> |
| * During a drag and drop operation, the system displays an image that the user drags. This image |
| * is called a drag shadow. Several action types reflect the position of the drag shadow relative |
| * to the View receiving the event. |
| * </p> |
| * <p> |
| * Most methods return valid data only for certain event actions. This is summarized in the |
| * following table. Each possible {@link #getAction()} value is listed in the first column. The |
| * other columns indicate which method or methods return valid data for that getAction() value: |
| * </p> |
| * <table> |
| * <tr> |
| * <th scope="col">getAction() Value</th> |
| * <th scope="col">getClipDescription()</th> |
| * <th scope="col">getLocalState()</th> |
| * <th scope="col">getX()</th> |
| * <th scope="col">getY()</th> |
| * <th scope="col">getClipData()</th> |
| * <th scope="col">getResult()</th> |
| * </tr> |
| * <tr> |
| * <td>ACTION_DRAG_STARTED</td> |
| * <td style="text-align: center;">X</td> |
| * <td style="text-align: center;">X</td> |
| * <td style="text-align: center;">X</td> |
| * <td style="text-align: center;">X</td> |
| * <td style="text-align: center;"> </td> |
| * <td style="text-align: center;"> </td> |
| * </tr> |
| * <tr> |
| * <td>ACTION_DRAG_ENTERED</td> |
| * <td style="text-align: center;">X</td> |
| * <td style="text-align: center;">X</td> |
| * <td style="text-align: center;"> </td> |
| * <td style="text-align: center;"> </td> |
| * <td style="text-align: center;"> </td> |
| * <td style="text-align: center;"> </td> |
| * </tr> |
| * <tr> |
| * <td>ACTION_DRAG_LOCATION</td> |
| * <td style="text-align: center;">X</td> |
| * <td style="text-align: center;">X</td> |
| * <td style="text-align: center;">X</td> |
| * <td style="text-align: center;">X</td> |
| * <td style="text-align: center;"> </td> |
| * <td style="text-align: center;"> </td> |
| * </tr> |
| * <tr> |
| * <td>ACTION_DRAG_EXITED</td> |
| * <td style="text-align: center;">X</td> |
| * <td style="text-align: center;">X</td> |
| * <td style="text-align: center;"> </td> |
| * <td style="text-align: center;"> </td> |
| * <td style="text-align: center;"> </td> |
| * <td style="text-align: center;"> </td> |
| * </tr> |
| * <tr> |
| * <td>ACTION_DROP</td> |
| * <td style="text-align: center;">X</td> |
| * <td style="text-align: center;">X</td> |
| * <td style="text-align: center;">X</td> |
| * <td style="text-align: center;">X</td> |
| * <td style="text-align: center;">X</td> |
| * <td style="text-align: center;"> </td> |
| * </tr> |
| * <tr> |
| * <td>ACTION_DRAG_ENDED</td> |
| * <td style="text-align: center;"> </td> |
| * <td style="text-align: center;"> </td> |
| * <td style="text-align: center;"> </td> |
| * <td style="text-align: center;"> </td> |
| * <td style="text-align: center;"> </td> |
| * <td style="text-align: center;">X</td> |
| * </tr> |
| * </table> |
| * <p> |
| * The {@link android.view.DragEvent#getAction()}, |
| * {@link android.view.DragEvent#describeContents()}, |
| * {@link android.view.DragEvent#writeToParcel(Parcel,int)}, and |
| * {@link android.view.DragEvent#toString()} methods always return valid data. |
| * </p> |
| * |
| * <div class="special reference"> |
| * <h3>Developer Guides</h3> |
| * <p>For a guide to implementing drag and drop features, read the |
| * <a href="{@docRoot}guide/topics/ui/drag-drop.html">Drag and Drop</a> developer guide.</p> |
| * </div> |
| */ |
| public class DragEvent implements Parcelable { |
| private static final boolean TRACK_RECYCLED_LOCATION = false; |
| |
| int mAction; |
| float mX, mY; |
| ClipDescription mClipDescription; |
| ClipData mClipData; |
| IDragAndDropPermissions mDragAndDropPermissions; |
| |
| Object mLocalState; |
| boolean mDragResult; |
| boolean mEventHandlerWasCalled; |
| |
| private DragEvent mNext; |
| private RuntimeException mRecycledLocation; |
| private boolean mRecycled; |
| |
| private static final int MAX_RECYCLED = 10; |
| private static final Object gRecyclerLock = new Object(); |
| private static int gRecyclerUsed = 0; |
| private static DragEvent gRecyclerTop = null; |
| |
| /** |
| * Action constant returned by {@link #getAction()}: Signals the start of a |
| * drag and drop operation. The View should return {@code true} from its |
| * {@link View#onDragEvent(DragEvent) onDragEvent()} handler method or |
| * {@link View.OnDragListener#onDrag(View,DragEvent) OnDragListener.onDrag()} listener |
| * if it can accept a drop. The onDragEvent() or onDrag() methods usually inspect the metadata |
| * from {@link #getClipDescription()} to determine if they can accept the data contained in |
| * this drag. For an operation that doesn't represent data transfer, these methods may |
| * perform other actions to determine whether or not the View should accept the data. |
| * If the View wants to indicate that it is a valid drop target, it can also react by |
| * changing its appearance. |
| * <p> |
| * Views added or becoming visible for the first time during a drag operation receive this |
| * event when they are added or becoming visible. |
| * </p> |
| * <p> |
| * A View only receives further drag events for the drag operation if it returns {@code true} |
| * in response to ACTION_DRAG_STARTED. |
| * </p> |
| * @see #ACTION_DRAG_ENDED |
| * @see #getX() |
| * @see #getY() |
| */ |
| public static final int ACTION_DRAG_STARTED = 1; |
| |
| /** |
| * Action constant returned by {@link #getAction()}: Sent to a View after |
| * {@link #ACTION_DRAG_ENTERED} while the drag shadow is still within the View object's bounding |
| * box, but not within a descendant view that can accept the data. The {@link #getX()} and |
| * {@link #getY()} methods supply |
| * the X and Y position of of the drag point within the View object's bounding box. |
| * <p> |
| * A View receives an {@link #ACTION_DRAG_ENTERED} event before receiving any |
| * ACTION_DRAG_LOCATION events. |
| * </p> |
| * <p> |
| * The system stops sending ACTION_DRAG_LOCATION events to a View once the user moves the |
| * drag shadow out of the View object's bounding box or into a descendant view that can accept |
| * the data. If the user moves the drag shadow back into the View object's bounding box or out |
| * of a descendant view that can accept the data, the View receives an ACTION_DRAG_ENTERED again |
| * before receiving any more ACTION_DRAG_LOCATION events. |
| * </p> |
| * @see #ACTION_DRAG_ENTERED |
| * @see #getX() |
| * @see #getY() |
| */ |
| public static final int ACTION_DRAG_LOCATION = 2; |
| |
| /** |
| * Action constant returned by {@link #getAction()}: Signals to a View that the user |
| * has released the drag shadow, and the drag point is within the bounding box of the View and |
| * not within a descendant view that can accept the data. |
| * The View should retrieve the data from the DragEvent by calling {@link #getClipData()}. |
| * The methods {@link #getX()} and {@link #getY()} return the X and Y position of the drop point |
| * within the View object's bounding box. |
| * <p> |
| * The View should return {@code true} from its {@link View#onDragEvent(DragEvent)} |
| * handler or {@link View.OnDragListener#onDrag(View,DragEvent) OnDragListener.onDrag()} |
| * listener if it accepted the drop, and {@code false} if it ignored the drop. |
| * </p> |
| * <p> |
| * The View can also react to this action by changing its appearance. |
| * </p> |
| * @see #getClipData() |
| * @see #getX() |
| * @see #getY() |
| */ |
| public static final int ACTION_DROP = 3; |
| |
| /** |
| * Action constant returned by {@link #getAction()}: Signals to a View that the drag and drop |
| * operation has concluded. A View that changed its appearance during the operation should |
| * return to its usual drawing state in response to this event. |
| * <p> |
| * All views with listeners that returned boolean <code>true</code> for the ACTION_DRAG_STARTED |
| * event will receive the ACTION_DRAG_ENDED event even if they are not currently visible when |
| * the drag ends. Views removed during the drag operation won't receive the ACTION_DRAG_ENDED |
| * event. |
| * </p> |
| * <p> |
| * The View object can call {@link #getResult()} to see the result of the operation. |
| * If a View returned {@code true} in response to {@link #ACTION_DROP}, then |
| * getResult() returns {@code true}, otherwise it returns {@code false}. |
| * </p> |
| * @see #ACTION_DRAG_STARTED |
| * @see #getResult() |
| */ |
| public static final int ACTION_DRAG_ENDED = 4; |
| |
| /** |
| * Action constant returned by {@link #getAction()}: Signals to a View that the drag point has |
| * entered the bounding box of the View. |
| * <p> |
| * If the View can accept a drop, it can react to ACTION_DRAG_ENTERED |
| * by changing its appearance in a way that tells the user that the View is the current |
| * drop target. |
| * </p> |
| * The system stops sending ACTION_DRAG_LOCATION events to a View once the user moves the |
| * drag shadow out of the View object's bounding box or into a descendant view that can accept |
| * the data. If the user moves the drag shadow back into the View object's bounding box or out |
| * of a descendant view that can accept the data, the View receives an ACTION_DRAG_ENTERED again |
| * before receiving any more ACTION_DRAG_LOCATION events. |
| * </p> |
| * @see #ACTION_DRAG_ENTERED |
| * @see #ACTION_DRAG_LOCATION |
| */ |
| public static final int ACTION_DRAG_ENTERED = 5; |
| |
| /** |
| * Action constant returned by {@link #getAction()}: Signals that the user has moved the |
| * drag shadow out of the bounding box of the View or into a descendant view that can accept |
| * the data. |
| * The View can react by changing its appearance in a way that tells the user that |
| * View is no longer the immediate drop target. |
| * <p> |
| * After the system sends an ACTION_DRAG_EXITED event to the View, the View receives no more |
| * ACTION_DRAG_LOCATION events until the user drags the drag shadow back over the View. |
| * </p> |
| * |
| */ |
| public static final int ACTION_DRAG_EXITED = 6; |
| |
| private DragEvent() { |
| } |
| |
| private void init(int action, float x, float y, ClipDescription description, ClipData data, |
| IDragAndDropPermissions dragAndDropPermissions, Object localState, boolean result) { |
| mAction = action; |
| mX = x; |
| mY = y; |
| mClipDescription = description; |
| mClipData = data; |
| this.mDragAndDropPermissions = dragAndDropPermissions; |
| mLocalState = localState; |
| mDragResult = result; |
| } |
| |
| static DragEvent obtain() { |
| return DragEvent.obtain(0, 0f, 0f, null, null, null, null, false); |
| } |
| |
| /** @hide */ |
| public static DragEvent obtain(int action, float x, float y, Object localState, |
| ClipDescription description, ClipData data, |
| IDragAndDropPermissions dragAndDropPermissions, boolean result) { |
| final DragEvent ev; |
| synchronized (gRecyclerLock) { |
| if (gRecyclerTop == null) { |
| ev = new DragEvent(); |
| ev.init(action, x, y, description, data, dragAndDropPermissions, localState, |
| result); |
| return ev; |
| } |
| ev = gRecyclerTop; |
| gRecyclerTop = ev.mNext; |
| gRecyclerUsed -= 1; |
| } |
| ev.mRecycledLocation = null; |
| ev.mRecycled = false; |
| ev.mNext = null; |
| |
| ev.init(action, x, y, description, data, dragAndDropPermissions, localState, result); |
| |
| return ev; |
| } |
| |
| /** @hide */ |
| public static DragEvent obtain(DragEvent source) { |
| return obtain(source.mAction, source.mX, source.mY, source.mLocalState, |
| source.mClipDescription, source.mClipData, source.mDragAndDropPermissions, |
| source.mDragResult); |
| } |
| |
| /** |
| * Inspect the action value of this event. |
| * @return One of the following action constants, in the order in which they usually occur |
| * during a drag and drop operation: |
| * <ul> |
| * <li>{@link #ACTION_DRAG_STARTED}</li> |
| * <li>{@link #ACTION_DRAG_ENTERED}</li> |
| * <li>{@link #ACTION_DRAG_LOCATION}</li> |
| * <li>{@link #ACTION_DROP}</li> |
| * <li>{@link #ACTION_DRAG_EXITED}</li> |
| * <li>{@link #ACTION_DRAG_ENDED}</li> |
| * </ul> |
| */ |
| public int getAction() { |
| return mAction; |
| } |
| |
| /** |
| * Gets the X coordinate of the drag point. The value is only valid if the event action is |
| * {@link #ACTION_DRAG_STARTED}, {@link #ACTION_DRAG_LOCATION} or {@link #ACTION_DROP}. |
| * @return The current drag point's X coordinate |
| */ |
| public float getX() { |
| return mX; |
| } |
| |
| /** |
| * Gets the Y coordinate of the drag point. The value is only valid if the event action is |
| * {@link #ACTION_DRAG_STARTED}, {@link #ACTION_DRAG_LOCATION} or {@link #ACTION_DROP}. |
| * @return The current drag point's Y coordinate |
| */ |
| public float getY() { |
| return mY; |
| } |
| |
| /** |
| * Returns the {@link android.content.ClipData} object sent to the system as part of the call |
| * to |
| * {@link android.view.View#startDragAndDrop(ClipData,View.DragShadowBuilder,Object,int) |
| * startDragAndDrop()}. |
| * This method only returns valid data if the event action is {@link #ACTION_DROP}. |
| * @return The ClipData sent to the system by startDragAndDrop(). |
| */ |
| public ClipData getClipData() { |
| return mClipData; |
| } |
| |
| /** |
| * Returns the {@link android.content.ClipDescription} object contained in the |
| * {@link android.content.ClipData} object sent to the system as part of the call to |
| * {@link android.view.View#startDragAndDrop(ClipData,View.DragShadowBuilder,Object,int) |
| * startDragAndDrop()}. |
| * The drag handler or listener for a View can use the metadata in this object to decide if the |
| * View can accept the dragged View object's data. |
| * <p> |
| * This method returns valid data for all event actions except for {@link #ACTION_DRAG_ENDED}. |
| * @return The ClipDescription that was part of the ClipData sent to the system by |
| * startDragAndDrop(). |
| */ |
| public ClipDescription getClipDescription() { |
| return mClipDescription; |
| } |
| |
| /** @hide */ |
| public IDragAndDropPermissions getDragAndDropPermissions() { |
| return mDragAndDropPermissions; |
| } |
| |
| /** |
| * Returns the local state object sent to the system as part of the call to |
| * {@link android.view.View#startDragAndDrop(ClipData,View.DragShadowBuilder,Object,int) |
| * startDragAndDrop()}. |
| * The object is intended to provide local information about the drag and drop operation. For |
| * example, it can indicate whether the drag and drop operation is a copy or a move. |
| * <p> |
| * The local state is available only to views in the activity which has started the drag |
| * operation. In all other activities this method will return null |
| * </p> |
| * <p> |
| * This method returns valid data for all event actions except for {@link #ACTION_DRAG_ENDED}. |
| * </p> |
| * @return The local state object sent to the system by startDragAndDrop(). |
| */ |
| public Object getLocalState() { |
| return mLocalState; |
| } |
| |
| /** |
| * <p> |
| * Returns an indication of the result of the drag and drop operation. |
| * This method only returns valid data if the action type is {@link #ACTION_DRAG_ENDED}. |
| * The return value depends on what happens after the user releases the drag shadow. |
| * </p> |
| * <p> |
| * If the user releases the drag shadow on a View that can accept a drop, the system sends an |
| * {@link #ACTION_DROP} event to the View object's drag event listener. If the listener |
| * returns {@code true}, then getResult() will return {@code true}. |
| * If the listener returns {@code false}, then getResult() returns {@code false}. |
| * </p> |
| * <p> |
| * Notice that getResult() also returns {@code false} if no {@link #ACTION_DROP} is sent. This |
| * happens, for example, when the user releases the drag shadow over an area outside of the |
| * application. In this case, the system sends out {@link #ACTION_DRAG_ENDED} for the current |
| * operation, but never sends out {@link #ACTION_DROP}. |
| * </p> |
| * @return {@code true} if a drag event listener returned {@code true} in response to |
| * {@link #ACTION_DROP}. If the system did not send {@link #ACTION_DROP} before |
| * {@link #ACTION_DRAG_ENDED}, or if the listener returned {@code false} in response to |
| * {@link #ACTION_DROP}, then {@code false} is returned. |
| */ |
| public boolean getResult() { |
| return mDragResult; |
| } |
| |
| /** |
| * Recycle the DragEvent, to be re-used by a later caller. After calling |
| * this function you must never touch the event again. |
| * |
| * @hide |
| */ |
| public final void recycle() { |
| // Ensure recycle is only called once! |
| if (TRACK_RECYCLED_LOCATION) { |
| if (mRecycledLocation != null) { |
| throw new RuntimeException(toString() + " recycled twice!", mRecycledLocation); |
| } |
| mRecycledLocation = new RuntimeException("Last recycled here"); |
| } else { |
| if (mRecycled) { |
| throw new RuntimeException(toString() + " recycled twice!"); |
| } |
| mRecycled = true; |
| } |
| |
| mClipData = null; |
| mClipDescription = null; |
| mLocalState = null; |
| mEventHandlerWasCalled = false; |
| |
| synchronized (gRecyclerLock) { |
| if (gRecyclerUsed < MAX_RECYCLED) { |
| gRecyclerUsed++; |
| mNext = gRecyclerTop; |
| gRecyclerTop = this; |
| } |
| } |
| } |
| |
| /** |
| * Returns a string containing a concise, human-readable representation of this DragEvent |
| * object. |
| * @return A string representation of the DragEvent object. |
| */ |
| @Override |
| public String toString() { |
| return "DragEvent{" + Integer.toHexString(System.identityHashCode(this)) |
| + " action=" + mAction + " @ (" + mX + ", " + mY + ") desc=" + mClipDescription |
| + " data=" + mClipData + " local=" + mLocalState + " result=" + mDragResult |
| + "}"; |
| } |
| |
| /* Parcelable interface */ |
| |
| /** |
| * Returns information about the {@link android.os.Parcel} representation of this DragEvent |
| * object. |
| * @return Information about the {@link android.os.Parcel} representation. |
| */ |
| public int describeContents() { |
| return 0; |
| } |
| |
| /** |
| * Creates a {@link android.os.Parcel} object from this DragEvent object. |
| * @param dest A {@link android.os.Parcel} object in which to put the DragEvent object. |
| * @param flags Flags to store in the Parcel. |
| */ |
| public void writeToParcel(Parcel dest, int flags) { |
| dest.writeInt(mAction); |
| dest.writeFloat(mX); |
| dest.writeFloat(mY); |
| dest.writeInt(mDragResult ? 1 : 0); |
| if (mClipData == null) { |
| dest.writeInt(0); |
| } else { |
| dest.writeInt(1); |
| mClipData.writeToParcel(dest, flags); |
| } |
| if (mClipDescription == null) { |
| dest.writeInt(0); |
| } else { |
| dest.writeInt(1); |
| mClipDescription.writeToParcel(dest, flags); |
| } |
| if (mDragAndDropPermissions == null) { |
| dest.writeInt(0); |
| } else { |
| dest.writeInt(1); |
| dest.writeStrongBinder(mDragAndDropPermissions.asBinder()); |
| } |
| } |
| |
| /** |
| * A container for creating a DragEvent from a Parcel. |
| */ |
| public static final Parcelable.Creator<DragEvent> CREATOR = |
| new Parcelable.Creator<DragEvent>() { |
| public DragEvent createFromParcel(Parcel in) { |
| DragEvent event = DragEvent.obtain(); |
| event.mAction = in.readInt(); |
| event.mX = in.readFloat(); |
| event.mY = in.readFloat(); |
| event.mDragResult = (in.readInt() != 0); |
| if (in.readInt() != 0) { |
| event.mClipData = ClipData.CREATOR.createFromParcel(in); |
| } |
| if (in.readInt() != 0) { |
| event.mClipDescription = ClipDescription.CREATOR.createFromParcel(in); |
| } |
| if (in.readInt() != 0) { |
| event.mDragAndDropPermissions = |
| IDragAndDropPermissions.Stub.asInterface(in.readStrongBinder());; |
| } |
| return event; |
| } |
| |
| public DragEvent[] newArray(int size) { |
| return new DragEvent[size]; |
| } |
| }; |
| } |