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/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 77ba6fe..c63f7d6 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -24,6 +24,7 @@
 import android.graphics.Canvas;
 import android.graphics.PixelFormat;
 import android.graphics.PorterDuff;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.*;
@@ -45,6 +46,8 @@
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.content.ClipData;
+import android.content.ClipDescription;
 import android.content.ComponentCallbacks;
 import android.content.Context;
 import android.app.ActivityManagerNative;
@@ -198,6 +201,11 @@
 
     final ViewConfiguration mViewConfiguration;
 
+    /* Drag/drop */
+    ClipDescription mDragDescription;
+    View mCurrentDragView;
+    final PointF mDragPoint = new PointF();
+
     /**
      * see {@link #playSoundEffect(int)}
      */
@@ -1670,6 +1678,7 @@
     public final static int FINISH_INPUT_CONNECTION = 1012;
     public final static int CHECK_FOCUS = 1013;
     public final static int CLOSE_SYSTEM_DIALOGS = 1014;
+    public final static int DISPATCH_DRAG_EVENT = 1015;
 
     @Override
     public void handleMessage(Message msg) {
@@ -1845,6 +1854,9 @@
                 mView.onCloseSystemDialogs((String)msg.obj);
             }
         } break;
+        case DISPATCH_DRAG_EVENT: {
+            handleDragEvent((DragEvent)msg.obj);
+        } break;
         }
     }
     
@@ -2434,6 +2446,87 @@
         }
     }
 
+    /* drag/drop */
+    private void handleDragEvent(DragEvent event) {
+        // From the root, only drag start/end/location are dispatched.  entered/exited
+        // are determined and dispatched by the viewgroup hierarchy, who then report
+        // that back here for ultimate reporting back to the framework.
+        if (mView != null && mAdded) {
+            final int what = event.mAction;
+
+            if (what == DragEvent.ACTION_DRAG_EXITED) {
+                // A direct EXITED event means that the window manager knows we've just crossed
+                // a window boundary, so the current drag target within this one must have
+                // just been exited.  Send it the usual notifications and then we're done
+                // for now.
+                setDragFocus(event, null);
+            } else {
+                // Cache the drag description when the operation starts, then fill it in
+                // on subsequent calls as a convenience
+                if (what == DragEvent.ACTION_DRAG_STARTED) {
+                    mDragDescription = event.mClipDescription;
+                } else {
+                    event.mClipDescription = mDragDescription;
+                }
+
+                // For events with a [screen] location, translate into window coordinates
+                if ((what == DragEvent.ACTION_DRAG_LOCATION) || (what == DragEvent.ACTION_DROP)) {
+                    mDragPoint.set(event.mX, event.mY);
+                    if (mTranslator != null) {
+                        mTranslator.translatePointInScreenToAppWindow(mDragPoint);
+                    }
+
+                    if (mCurScrollY != 0) {
+                        mDragPoint.offset(0, mCurScrollY);
+                    }
+
+                    event.mX = mDragPoint.x;
+                    event.mY = mDragPoint.y;
+                }
+
+                // Remember who the current drag target is pre-dispatch
+                final View prevDragView = mCurrentDragView;
+
+                // Now dispatch the drag/drop event
+                mView.dispatchDragEvent(event);
+
+                // If we changed apparent drag target, tell the OS about it
+                if (prevDragView != mCurrentDragView) {
+                    try {
+                        if (prevDragView != null) {
+                            sWindowSession.dragRecipientExited(mWindow);
+                        }
+                        if (mCurrentDragView != null) {
+                            sWindowSession.dragRecipientEntered(mWindow);
+                        }
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Unable to note drag target change");
+                    }
+                    mCurrentDragView = prevDragView;
+                }
+            }
+        }
+        event.recycle();
+    }
+
+    public void setDragFocus(DragEvent event, View newDragTarget) {
+        final int action = event.mAction;
+        // If we've dragged off of a view, send it the EXITED message
+        if (mCurrentDragView != newDragTarget) {
+            if (mCurrentDragView != null) {
+                event.mAction = DragEvent.ACTION_DRAG_EXITED;
+                mCurrentDragView.dispatchDragEvent(event);
+            }
+        }
+        // If we've dragged over a new view, send it the ENTERED message
+        if (newDragTarget != null) {
+            event.mAction = DragEvent.ACTION_DRAG_ENTERED;
+            newDragTarget.dispatchDragEvent(event);
+        }
+        mCurrentDragView = newDragTarget;
+        event.mAction = action;  // restore the event's original state
+    }
+
     private AudioManager getAudioManager() {
         if (mView == null) {
             throw new IllegalStateException("getAudioManager called when there is no mView");
@@ -2725,7 +2818,12 @@
         msg.obj = reason;
         sendMessage(msg);
     }
-    
+
+    public void dispatchDragEvent(DragEvent event) {
+        Message msg = obtainMessage(DISPATCH_DRAG_EVENT, event);
+        sendMessage(msg);
+    }
+
     /**
      * The window is getting focus so if there is anything focused/selected
      * send an {@link AccessibilityEvent} to announce that.
@@ -2936,6 +3034,14 @@
                 }
             }
         }
+
+        /* Drag/drop */
+        public void dispatchDragEvent(DragEvent event) {
+            final ViewRoot viewRoot = mViewRoot.get();
+            if (viewRoot != null) {
+                viewRoot.dispatchDragEvent(event);
+            }
+        }
     }
 
     /**