Fix drag enter/exit reporting

Now, each ViewGroup is tracking which of its child views [which might
themselves be ViewGroups] is currently under the drag point, and when the
drag leaves that child, a DRAG_EXITED is synthesized and dispatched all
the way down to the leaf view previously under the point.  ENTERED is
still *not* dispatched down like this; instead, it's calculated and
synthesized directly at each level based on the new LOCATION.

The ViewRoot still tracks the leaf drag target, but solely for the
purpose of reporting changes to the OS after full dispatch of a new
LOCATION -- the entered/exited messaging is no longer initiated at the
ViewRoot level.

Change-Id: I0089cc538b7e33a0440187543fcfd2f8b12e197d
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 0f9312c..ad343a3 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -870,7 +870,7 @@
         switch (event.mAction) {
         case DragEvent.ACTION_DRAG_STARTED: {
             // clear state to recalculate which views we drag over
-            root.setDragFocus(event, null);
+            mCurrentDragView = null;
 
             // Now dispatch down to our children, caching the responses
             mChildAcceptsDrag = false;
@@ -915,11 +915,28 @@
             final View target = findFrontmostDroppableChildAt(event.mX, event.mY, mLocalPoint);
 
             // If we've changed apparent drag target, tell the view root which view
-            // we're over now.  This will in turn send out DRAG_ENTERED / DRAG_EXITED
-            // notifications as appropriate.
+            // we're over now [for purposes of the eventual drag-recipient-changed
+            // notifications to the framework] and tell the new target that the drag
+            // has entered its bounds.  The root will see setDragFocus() calls all
+            // the way down to the final leaf view that is handling the LOCATION event
+            // before reporting the new potential recipient to the framework.
             if (mCurrentDragView != target) {
-                root.setDragFocus(event, target);
+                root.setDragFocus(target);
+
+                final int action = event.mAction;
+                // If we've dragged off of a child view, send it the EXITED message
+                if (mCurrentDragView != null) {
+                    event.mAction = DragEvent.ACTION_DRAG_EXITED;
+                    mCurrentDragView.dispatchDragEvent(event);
+                }
                 mCurrentDragView = target;
+
+                // If we've dragged over a new child view, send it the ENTERED message
+                if (target != null) {
+                    event.mAction = DragEvent.ACTION_DRAG_ENTERED;
+                    target.dispatchDragEvent(event);
+                }
+                event.mAction = action;  // restore the event's original state
             }
 
             // Dispatch the actual drag location notice, localized into its coordinates
@@ -934,6 +951,25 @@
             }
         } break;
 
+        /* Entered / exited dispatch
+         *
+         * DRAG_ENTERED is not dispatched downwards from ViewGroup.  The reason for this is
+         * that we're about to get the corresponding LOCATION event, which we will use to
+         * determine which of our children is the new target; at that point we will
+         * push a DRAG_ENTERED down to the new target child [which may itself be a ViewGroup].
+         *
+         * DRAG_EXITED *is* dispatched all the way down immediately: once we know the
+         * drag has left this ViewGroup, we know by definition that every contained subview
+         * is also no longer under the drag point.
+         */
+
+        case DragEvent.ACTION_DRAG_EXITED: {
+            if (mCurrentDragView != null) {
+                mCurrentDragView.dispatchDragEvent(event);
+                mCurrentDragView = null;
+            }
+        } break;
+
         case DragEvent.ACTION_DROP: {
             if (ViewDebug.DEBUG_DRAG) Log.d(View.VIEW_LOG_TAG, "Drop event: " + event);
             View target = findFrontmostDroppableChildAt(event.mX, event.mY, mLocalPoint);
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 22a7773..c7c2071 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -2493,11 +2493,12 @@
                 // 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);
+                mView.dispatchDragEvent(event);
             } 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) {
+                    mCurrentDragView = null;    // Start the current-recipient tracking
                     mDragDescription = event.mClipDescription;
                 } else {
                     event.mClipDescription = mDragDescription;
@@ -2557,22 +2558,10 @@
         outLocation.y = (int) mLastTouchPoint.y;
     }
 
-    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
+    public void setDragFocus(View newDragTarget) {
         if (mCurrentDragView != newDragTarget) {
-            if (mCurrentDragView != null) {
-                event.mAction = DragEvent.ACTION_DRAG_EXITED;
-                mCurrentDragView.dispatchDragEvent(event);
-            }
             mCurrentDragView = newDragTarget;
         }
-        // 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);
-        }
-        event.mAction = action;  // restore the event's original state
     }
 
     private AudioManager getAudioManager() {