Drag/drop APIs and infrastructure

A View initiates a drag-and-drop operation (hereafter just called a "drag")
by calling its startDrag(ClipData) method.  Within the processing of that
call, two callbacks are made into the originating View.  The first is to
onMeasureDragThumbnail().  Similarly to the core onMeasure() method, this
callback must respond by calling setDragThumbnailDimension(width, height) to
declare the size of the drag thumbnail image that should be used.  Following
this, the View's onDrawDragThumbnail(canvas) method will be invoked to
actually produce the bits of the thumbnail image.

If all goes well, startDrag() will return 'true', and the drag is off and
running.  (The other arguments to startDrag() provide reconciliation between
the current finger position and where the thumbnail should be placed on
the screen relative to it.)

Potential receipients of the ClipData behind the drag are notified by a
new dispatch mechanism, roughly parallel to motion event dispatch.  The core
routine is the View's onDragEvent(event) callback, with the mechanics of
dispatch itself being routed through dispatchDragEvent(event) -- as in
the case of motion events, the dispatch logic is in ViewGroup, with leaf
View objects not needing to consider the dispatch flow.

Several different event 'actions' are delivered through this dispatch
mechanism:

ACTION_DRAG_STARTED: this event is propagated to every View in every window
(including windows created during the course of a drag).  It serves as a
global notification that a drag has started with a payload whose matching
ClipDescription is supplied with the event.  A View that is prepared to
consume the data described in this event should return 'true' from their
onDragEvent() method, and ideally will also make some visible on-screen
indication that they are a potential target of the drop.

ACTION_DRAG_ENTERED: this event is sent once when the drag point
enters the View's bounds.  It is an opportunity for the View to set up
feedback that they are the one who will see the drop if the finger goes
up now.

ACTION_DRAG_LOCATION: when the drag point is over a given View, that
View will receive a stream of DRAG_LOCATION events, providing an
opportunity for the View to show visual feedback tied to the drag point.

ACTION_DRAG_EXITED: like DRAG_ENTERED, but called when the drag point
leaves the View's bounds.  The View should undo any visuals meant to
emphasize their being the hovered-over target.

ACTION_DROP: when the drag ends at a given point, the View under that
point is sent this event, with the full ClipData of the payload.

ACTION_DRAG_ENDED: paralleling the DRAG_STARTED action, this is the global
broadcast that the drag has ended and all Views should return to their
normal visual state.  This happens after the DROP event.

Change-Id: Ia8d0fb1516bce8c735d87ffd101af0976d7e84b6
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index f2d134b..595bac8 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import android.content.ClipData;
+import android.content.ClipDescription;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -53,6 +55,7 @@
 import android.util.Pools;
 import android.util.SparseArray;
 import android.view.ContextMenu.ContextMenuInfo;
+import android.view.DragEvent;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityEventSource;
 import android.view.accessibility.AccessibilityManager;
@@ -614,6 +617,7 @@
  */
 public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
     private static final boolean DBG = false;
+    static final boolean DEBUG_DRAG = true;
 
     /**
      * The logging tag used by this class with android.util.Log.
@@ -2030,6 +2034,15 @@
     private int mTouchSlop;
 
     /**
+     * Cache drag/drop state
+     *
+     */
+    boolean mCanAcceptDrop;
+    private boolean mIsCurrentDropTarget;
+    private int mThumbnailWidth;
+    private int mThumbnailHeight;
+
+    /**
      * Simple constructor to use when creating a view from code.
      *
      * @param context The Context the view is running in, through which it can
@@ -9762,6 +9775,137 @@
     }
 
     /**
+     * Drag and drop.  App calls startDrag(), then callbacks to onMeasureDragThumbnail()
+     * and onDrawDragThumbnail() happen, then the drag operation is handed over to the
+     * OS.
+     * !!! TODO: real docs
+     * @hide
+     */
+    public final boolean startDrag(ClipData data, float touchX, float touchY,
+            float thumbnailTouchX, float thumbnailTouchY, boolean myWindowOnly) {
+        if (DEBUG_DRAG) {
+            Log.d(VIEW_LOG_TAG, "startDrag: touch=(" + touchX + "," + touchY
+                    + ") thumb=(" + thumbnailTouchX + "," + thumbnailTouchY
+                    + ") data=" + data + " local=" + myWindowOnly);
+        }
+        boolean okay = false;
+
+        measureThumbnail();     // throws if the view fails to specify dimensions
+
+        Surface surface = new Surface();
+        try {
+            IBinder token = mAttachInfo.mSession.prepareDrag(mAttachInfo.mWindow,
+                    myWindowOnly, mThumbnailWidth, mThumbnailHeight, surface);
+            if (DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "prepareDrag returned token=" + token
+                    + " surface=" + surface);
+            if (token != null) {
+                Canvas canvas = surface.lockCanvas(null);
+                onDrawDragThumbnail(canvas);
+                surface.unlockCanvasAndPost(canvas);
+
+                okay = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, token,
+                        touchX, touchY, thumbnailTouchX, thumbnailTouchX, data);
+                if (DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "performDrag returned " + okay);
+            }
+        } catch (Exception e) {
+            Log.e(VIEW_LOG_TAG, "Unable to initiate drag", e);
+            surface.destroy();
+        }
+
+        return okay;
+    }
+
+    private void measureThumbnail() {
+        mPrivateFlags &= ~MEASURED_DIMENSION_SET;
+
+        onMeasureDragThumbnail();
+
+        // flag not set, setDragThumbnailDimension() was not invoked, we raise
+        // an exception to warn the developer
+        if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
+            throw new IllegalStateException("onMeasureDragThumbnail() did not set the"
+                    + " measured dimension by calling setDragThumbnailDimension()");
+        }
+
+        if (DEBUG_DRAG) {
+            Log.d(VIEW_LOG_TAG, "Drag thumb measured: w=" + mThumbnailWidth
+                    + " h=" + mThumbnailHeight);
+        }
+    }
+
+    /**
+     * The View must call this method from onMeasureDragThumbnail() in order to
+     * specify the dimensions of the drag thumbnail image.
+     *
+     * @param width
+     * @param height
+     */
+    protected final void setDragThumbnailDimension(int width, int height) {
+        mPrivateFlags |= MEASURED_DIMENSION_SET;
+        mThumbnailWidth = width;
+        mThumbnailHeight = height;
+    }
+
+    /**
+     * The default implementation specifies a drag thumbnail that matches the
+     * View's current size and appearance.
+     */
+    protected void onMeasureDragThumbnail() {
+        setDragThumbnailDimension(getWidth(), getHeight());
+    }
+
+    /**
+     * The default implementation just draws the current View appearance as the thumbnail
+     * @param canvas
+     */
+    protected void onDrawDragThumbnail(Canvas canvas) {
+        draw(canvas);
+    }
+
+    /**
+     * Drag-and-drop event dispatch.  The event.getAction() verb is one of the DragEvent
+     * constants DRAG_STARTED_EVENT, DRAG_EVENT, DROP_EVENT, and DRAG_ENDED_EVENT.
+     *
+     * For DRAG_STARTED_EVENT, event.getClipDescription() describes the content
+     * being dragged.  onDragEvent() should return 'true' if the view can handle
+     * a drop of that content.  A view that returns 'false' here will receive no
+     * further calls to onDragEvent() about the drag/drop operation.
+     *
+     * For DRAG_ENTERED, event.getClipDescription() describes the content being
+     * dragged.  This will be the same content description passed in the
+     * DRAG_STARTED_EVENT invocation.
+     *
+     * For DRAG_EXITED, event.getClipDescription() describes the content being
+     * dragged.  This will be the same content description passed in the
+     * DRAG_STARTED_EVENT invocation.  The view should return to its approriate
+     * drag-acceptance visual state.
+     *
+     * For DRAG_LOCATION_EVENT, event.getX() and event.getY() give the location in View
+     * coordinates of the current drag point.  The view must return 'true' if it
+     * can accept a drop of the current drag content, false otherwise.
+     *
+     * For DROP_EVENT, event.getX() and event.getY() give the location of the drop
+     * within the view; also, event.getClipData() returns the full data payload
+     * being dropped.  The view should return 'true' if it consumed the dropped
+     * content, 'false' if it did not.
+     *
+     * For DRAG_ENDED_EVENT, the 'event' argument may be null.  The view should return
+     * to its normal visual state.
+     */
+    protected boolean onDragEvent(DragEvent event) {
+        return false;
+    }
+
+    /**
+     * Views typically don't need to override dispatchDragEvent(); it just calls
+     * onDragEvent(what, event) and passes the result up appropriately.
+     *
+     */
+    public boolean dispatchDragEvent(DragEvent event) {
+        return onDragEvent(event);
+    }
+
+    /**
      * This needs to be a better API (NOT ON VIEW) before it is exposed.  If
      * it is ever exposed at all.
      * @hide