Add view/drawable states for drag-accepting / drag-hovered

Added new drag_can_accept and drag_hovered XML attributes and the View
logic to support them.  Drawable states are now refreshed automatically
when a drag starts/ends and when a drag crosses the boundary of a
participating view.

Change-Id: I25f8ee02c83b3fa4f27201997d7eabf4be653fd8
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 96cddfa..0ef56cc 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -1305,6 +1305,8 @@
     static final int VIEW_STATE_ACTIVATED = 1 << 5;
     static final int VIEW_STATE_ACCELERATED = 1 << 6;
     static final int VIEW_STATE_HOVERED = 1 << 7;
+    static final int VIEW_STATE_DRAG_CAN_ACCEPT = 1 << 8;
+    static final int VIEW_STATE_DRAG_HOVERED = 1 << 9;
 
     static final int[] VIEW_STATE_IDS = new int[] {
         R.attr.state_window_focused,    VIEW_STATE_WINDOW_FOCUSED,
@@ -1315,6 +1317,8 @@
         R.attr.state_activated,         VIEW_STATE_ACTIVATED,
         R.attr.state_accelerated,       VIEW_STATE_ACCELERATED,
         R.attr.state_hovered,           VIEW_STATE_HOVERED,
+        R.attr.state_drag_can_accept,   VIEW_STATE_DRAG_CAN_ACCEPT,
+        R.attr.state_drag_hovered,      VIEW_STATE_DRAG_HOVERED,
     };
 
     static {
@@ -1651,6 +1655,27 @@
      */
     static final int INVALIDATED                  = 0x80000000;
 
+    /* Masks for mPrivateFlags2 */
+
+    /**
+     * Indicates that this view has reported that it can accept the current drag's content.
+     * Cleared when the drag operation concludes.
+     * @hide
+     */
+    static final int DRAG_CAN_ACCEPT              = 0x00000001;
+
+    /**
+     * Indicates that this view is currently directly under the drag location in a
+     * drag-and-drop operation involving content that it can accept.  Cleared when
+     * the drag exits the view, or when the drag operation concludes.
+     * @hide
+     */
+    static final int DRAG_HOVERED                 = 0x00000002;
+
+    /* End of masks for mPrivateFlags2 */
+
+    static final int DRAG_MASK = DRAG_CAN_ACCEPT | DRAG_HOVERED;
+
     /**
      * Always allow a user to over-scroll this view, provided it is a
      * view that can scroll.
@@ -1822,6 +1847,7 @@
         @ViewDebug.FlagToString(mask = DIRTY_MASK, equals = DIRTY, name = "DIRTY")
     })
     int mPrivateFlags;
+    int mPrivateFlags2;
 
     /**
      * This view's request for the visibility of the status bar.
@@ -2251,12 +2277,6 @@
     private ViewPropertyAnimator mAnimator = null;
 
     /**
-     * Cache drag/drop state
-     *
-     */
-    boolean mCanAcceptDrop;
-
-    /**
      * Flag indicating that a drag can cross window boundaries.  When
      * {@link #startDrag(ClipData, DragShadowBuilder, Object, int)} is called
      * with this flag set, all visible applications will be able to participate
@@ -10035,6 +10055,10 @@
         }
         if ((privateFlags & HOVERED) != 0) viewStateIndex |= VIEW_STATE_HOVERED;
 
+        final int privateFlags2 = mPrivateFlags2;
+        if ((privateFlags2 & DRAG_CAN_ACCEPT) != 0) viewStateIndex |= VIEW_STATE_DRAG_CAN_ACCEPT;
+        if ((privateFlags2 & DRAG_HOVERED) != 0) viewStateIndex |= VIEW_STATE_DRAG_HOVERED;
+
         drawableState = VIEW_STATE_SETS[viewStateIndex];
 
         //noinspection ConstantIfStatement
@@ -11701,6 +11725,10 @@
         return onDragEvent(event);
     }
 
+    boolean canAcceptDrag() {
+        return (mPrivateFlags2 & DRAG_CAN_ACCEPT) != 0;
+    }
+
     /**
      * This needs to be a better API (NOT ON VIEW) before it is exposed.  If
      * it is ever exposed at all.
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 058b826..f7f2685 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -929,6 +929,7 @@
             final View[] children = mChildren;
             for (int i = 0; i < count; i++) {
                 final View child = children[i];
+                child.mPrivateFlags2 &= ~View.DRAG_MASK;
                 if (child.getVisibility() == VISIBLE) {
                     final boolean handled = notifyChildOfDrag(children[i]);
                     if (handled) {
@@ -949,6 +950,8 @@
                 for (View child : mDragNotifiedChildren) {
                     // If a child was notified about an ongoing drag, it's told that it's over
                     child.dispatchDragEvent(event);
+                    child.mPrivateFlags2 &= ~View.DRAG_MASK;
+                    child.refreshDrawableState();
                 }
 
                 mDragNotifiedChildren.clear();
@@ -979,8 +982,11 @@
                 final int action = event.mAction;
                 // If we've dragged off of a child view, send it the EXITED message
                 if (mCurrentDragView != null) {
+                    final View view = mCurrentDragView;
                     event.mAction = DragEvent.ACTION_DRAG_EXITED;
-                    mCurrentDragView.dispatchDragEvent(event);
+                    view.dispatchDragEvent(event);
+                    view.mPrivateFlags2 &= ~View.DRAG_HOVERED;
+                    view.refreshDrawableState();
                 }
                 mCurrentDragView = target;
 
@@ -988,6 +994,8 @@
                 if (target != null) {
                     event.mAction = DragEvent.ACTION_DRAG_ENTERED;
                     target.dispatchDragEvent(event);
+                    target.mPrivateFlags2 |= View.DRAG_HOVERED;
+                    target.refreshDrawableState();
                 }
                 event.mAction = action;  // restore the event's original state
             }
@@ -1018,7 +1026,11 @@
 
         case DragEvent.ACTION_DRAG_EXITED: {
             if (mCurrentDragView != null) {
-                mCurrentDragView.dispatchDragEvent(event);
+                final View view = mCurrentDragView;
+                view.dispatchDragEvent(event);
+                view.mPrivateFlags2 &= ~View.DRAG_HOVERED;
+                view.refreshDrawableState();
+
                 mCurrentDragView = null;
             }
         } break;
@@ -1056,7 +1068,7 @@
         final View[] children = mChildren;
         for (int i = count - 1; i >= 0; i--) {
             final View child = children[i];
-            if (!child.mCanAcceptDrop) {
+            if (!child.canAcceptDrag()) {
                 continue;
             }
 
@@ -1072,11 +1084,16 @@
             Log.d(View.VIEW_LOG_TAG, "Sending drag-started to view: " + child);
         }
 
+        boolean canAccept = false;
         if (! mDragNotifiedChildren.contains(child)) {
             mDragNotifiedChildren.add(child);
-            child.mCanAcceptDrop = child.dispatchDragEvent(mCurrentDrag);
+            canAccept = child.dispatchDragEvent(mCurrentDrag);
+            if (canAccept && !child.canAcceptDrag()) {
+                child.mPrivateFlags2 |= View.DRAG_CAN_ACCEPT;
+                child.refreshDrawableState();
+            }
         }
-        return child.mCanAcceptDrop;
+        return canAccept;
     }
 
     @Override
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index e2c440a..77f4e01 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3818,6 +3818,8 @@
          <li>"state_grow"
          <li>"state_move"
          <li>"state_hovered"
+         <li>"state_drag_can_accept"
+         <li>"state_drag_hovered"
          </ul>  -->
     <declare-styleable name="DrawableStates">
         <!-- State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable},
@@ -3870,6 +3872,14 @@
         <!-- State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable},
              set when a pointer is hovering over the view. -->
         <attr name="state_hovered" format="boolean" />
+        <!-- State for {@link android.graphics.drawable.StateListDrawable StateListDrawable}
+             indicating that the Drawable is in a view that is capable of accepting a drop of
+             the content currently being manipulated in a drag-and-drop operation. -->
+        <attr name="state_drag_can_accept" format="boolean" />
+        <!-- State for {@link android.graphics.drawable.StateListDrawable StateListDrawable}
+             indicating that a drag operation (for which the Drawable's view is a valid recipient)
+             is currently positioned over the Drawable. -->
+        <attr name="state_drag_hovered" format="boolean" />
     </declare-styleable>
     <declare-styleable name="ViewDrawableStates">
         <attr name="state_pressed" />
@@ -3880,6 +3890,8 @@
         <attr name="state_activated" />
         <attr name="state_accelerated" />
         <attr name="state_hovered" />
+        <attr name="state_drag_can_accept" />
+        <attr name="state_drag_hovered" />
     </declare-styleable>
     <!-- State array representing a menu item that is currently checked. -->
     <declare-styleable name="MenuItemCheckedState">
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 5432212..0fefbf2 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1654,5 +1654,7 @@
      =============================================================== -->
   <eat-comment />
   <public type="attr" name="state_hovered" />
+  <public type="attr" name="state_drag_can_accept" />
+  <public type="attr" name="state_drag_hovered" />
 
 </resources>