From 1edc6daf1d13ec472da2aae2a2a88d130c495cb7 Mon Sep 17 00:00:00 2001 From: Vadim Tryshev Date: Thu, 15 Sep 2016 16:19:15 -0700 Subject: [PATCH] =?UTF-8?q?Eliminating=20the=20=E2=80=9Cblack=20hole?= =?UTF-8?q?=E2=80=9D=20effect=20with=20location/drop=20events.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, a container view that doesn’t accept events, but has a child that accepts events, prevents its parent from receiving LOCATION/DROP events while the drag is over the container (but not the child). This is a bug. With this fix, such a container will prevent the parent from invoking a (second) LOCATION/DROP event only if the event was really delivered to any of its descendants. To know whether the event was delivered, I added DragEvent.mEventHandlerWasCalled member. EXITED/ENTERED events are now generated upon delivery of the event that has coordinates in it. Current view that has drag focus is now global to reflect the fact that it’s one per process. Bug: 31469490 Change-Id: I248e8d1de87b7734853136eb4719f7571cea91d5 --- core/java/android/view/DragEvent.java | 2 + core/java/android/view/View.java | 12 ++- core/java/android/view/ViewGroup.java | 113 ++--------------------- core/java/android/view/ViewRootImpl.java | 41 +++++++- 4 files changed, 60 insertions(+), 108 deletions(-) diff --git a/core/java/android/view/DragEvent.java b/core/java/android/view/DragEvent.java index b0f15b5f2329f..e0a6fac1891f9 100644 --- a/core/java/android/view/DragEvent.java +++ b/core/java/android/view/DragEvent.java @@ -134,6 +134,7 @@ public class DragEvent implements Parcelable { Object mLocalState; boolean mDragResult; + boolean mEventHandlerWasCalled; private DragEvent mNext; private RuntimeException mRecycledLocation; @@ -435,6 +436,7 @@ public class DragEvent implements Parcelable { mClipData = null; mClipDescription = null; mLocalState = null; + mEventHandlerWasCalled = false; synchronized (gRecyclerLock) { if (gRecyclerUsed < MAX_RECYCLED) { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 908658feb29c0..1d972eafa3793 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -40,7 +40,6 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; -import android.graphics.Color; import android.graphics.Insets; import android.graphics.Interpolator; import android.graphics.LinearGradient; @@ -20874,6 +20873,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, *

*/ public boolean dispatchDragEvent(DragEvent event) { + event.mEventHandlerWasCalled = true; + if (event.mAction == DragEvent.ACTION_DRAG_LOCATION || + event.mAction == DragEvent.ACTION_DROP) { + // About to deliver an event with coordinates to this view. Notify that now this view + // has drag focus. This will send exit/enter events as needed. + getViewRootImpl().setDragFocus(this, event); + } + return callDragEventHandler(event); + } + + final boolean callDragEventHandler(DragEvent event) { ListenerInfo li = mListenerInfo; //noinspection SimplifiableIfStatement if (li != null && li.mOnDragListener != null && (mViewFlags & ENABLED_MASK) == ENABLED diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 6933efc15b683..987db0ac58ed4 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -153,9 +153,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager */ Transformation mInvalidationTransformation; - // View currently under an ongoing drag. Can be null, a child or this window. - private View mCurrentDragView; - // Metadata about the ongoing drag private DragEvent mCurrentDragStartEvent; private boolean mIsInterestedInDrag; @@ -1362,16 +1359,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final float tx = event.mX; final float ty = event.mY; - ViewRootImpl root = getViewRootImpl(); - // Dispatch down the view hierarchy final PointF localPoint = getLocalPoint(); switch (event.mAction) { case DragEvent.ACTION_DRAG_STARTED: { - // clear state to recalculate which views we drag over - mCurrentDragView = null; - // Set up our tracking of drag-started notifications mCurrentDragStartEvent = DragEvent.obtain(event); if (mChildrenInterestedInDrag == null) { @@ -1434,60 +1426,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } break; - case DragEvent.ACTION_DRAG_LOCATION: { + case DragEvent.ACTION_DRAG_LOCATION: + case DragEvent.ACTION_DROP: { // Find the [possibly new] drag target View target = findFrontmostDroppableChildAt(event.mX, event.mY, localPoint); if (target == null && mIsInterestedInDrag) { target = this; } - // If we've changed apparent drag target, tell the view root which view - // 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(target); - - final int action = event.mAction; - // Position should not be available for ACTION_DRAG_ENTERED and ACTION_DRAG_EXITED. - event.mX = 0; - event.mY = 0; - - // If we've dragged off of a child view or this window, send it the EXITED message - if (mCurrentDragView != null) { - final View view = mCurrentDragView; - event.mAction = DragEvent.ACTION_DRAG_EXITED; - if (view != this) { - view.dispatchDragEvent(event); - view.mPrivateFlags2 &= ~View.PFLAG2_DRAG_HOVERED; - view.refreshDrawableState(); - } else { - super.dispatchDragEvent(event); - } - } - - mCurrentDragView = target; - - // If we've dragged over a new child view, send it the ENTERED message, otherwise - // send it to this window. - if (target != null) { - event.mAction = DragEvent.ACTION_DRAG_ENTERED; - if (target != this) { - target.dispatchDragEvent(event); - target.mPrivateFlags2 |= View.PFLAG2_DRAG_HOVERED; - target.refreshDrawableState(); - } else { - super.dispatchDragEvent(event); - } - } - event.mAction = action; // restore the event's original state - event.mX = tx; - event.mY = ty; - } - - // Dispatch the actual drag location notice, localized into its coordinates + // Dispatch the actual drag notice, localized into the target coordinates. if (target != null) { if (target != this) { event.mX = localPoint.x; @@ -1497,58 +1444,17 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager event.mX = tx; event.mY = ty; + + if (!event.mEventHandlerWasCalled && mIsInterestedInDrag) { + // The child didn't invoke any event handler, but this view is interested in + // drag, so the event goes to this view. + retval = super.dispatchDragEvent(event); + } } else { retval = super.dispatchDragEvent(event); } } } 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]. - * If no suitable child is detected, dispatch to this window. - * - * 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) { - final View view = mCurrentDragView; - if (view != this) { - view.dispatchDragEvent(event); - view.mPrivateFlags2 &= ~View.PFLAG2_DRAG_HOVERED; - view.refreshDrawableState(); - } else { - super.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, localPoint); - if (target != null) { - if (ViewDebug.DEBUG_DRAG) Log.d(View.VIEW_LOG_TAG, " dispatch drop to " + target); - event.mX = localPoint.x; - event.mY = localPoint.y; - retval = target.dispatchDragEvent(event); - event.mX = tx; - event.mY = ty; - } else if (mIsInterestedInDrag) { - retval = super.dispatchDragEvent(event); - } else { - if (ViewDebug.DEBUG_DRAG) { - Log.d(View.VIEW_LOG_TAG, " not dropped on an accepting view"); - } - } - } break; } return retval; @@ -1592,6 +1498,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final boolean canAccept = child.dispatchDragEvent(mCurrentDragStartEvent); mCurrentDragStartEvent.mX = tx; mCurrentDragStartEvent.mY = ty; + mCurrentDragStartEvent.mEventHandlerWasCalled = false; if (canAccept) { mChildrenInterestedInDrag.add(child); if (!child.canAcceptDrag()) { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 1d541f6c0c4ca..8abaa14810e13 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -5523,9 +5523,8 @@ public final class ViewRootImpl implements ViewParent, 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. - mView.dispatchDragEvent(event); + // just been exited. Send the EXITED notification to the current drag view, if any. + setDragFocus(null, event); } else { // For events with a [screen] location, translate into window coordinates if ((what == DragEvent.ACTION_DRAG_LOCATION) || (what == DragEvent.ACTION_DROP)) { @@ -5548,6 +5547,12 @@ public final class ViewRootImpl implements ViewParent, // Now dispatch the drag/drop event boolean result = mView.dispatchDragEvent(event); + if (what == DragEvent.ACTION_DRAG_LOCATION && !event.mEventHandlerWasCalled) { + // If the LOCATION event wasn't delivered to any handler, no view now has a drag + // focus. + setDragFocus(null, event); + } + // If we changed apparent drag target, tell the OS about it if (prevDragView != mCurrentDragView) { try { @@ -5575,6 +5580,7 @@ public final class ViewRootImpl implements ViewParent, // When the drag operation ends, reset drag-related state if (what == DragEvent.ACTION_DRAG_ENDED) { + mCurrentDragView = null; setLocalDragState(null); mAttachInfo.mDragToken = null; if (mAttachInfo.mDragSurface != null) { @@ -5634,9 +5640,36 @@ public final class ViewRootImpl implements ViewParent, return mLastTouchSource; } - public void setDragFocus(View newDragTarget) { + public void setDragFocus(View newDragTarget, DragEvent event) { if (mCurrentDragView != newDragTarget) { + // Send EXITED and ENTERED notifications to the old and new drag focus views. + + final float tx = event.mX; + final float ty = event.mY; + final int action = event.mAction; + // Position should not be available for ACTION_DRAG_ENTERED and ACTION_DRAG_EXITED. + event.mX = 0; + event.mY = 0; + + if (mCurrentDragView != null) { + event.mAction = DragEvent.ACTION_DRAG_EXITED; + mCurrentDragView.callDragEventHandler(event); + mCurrentDragView.mPrivateFlags2 &= ~View.PFLAG2_DRAG_HOVERED; + mCurrentDragView.refreshDrawableState(); + } + mCurrentDragView = newDragTarget; + + if (newDragTarget != null) { + event.mAction = DragEvent.ACTION_DRAG_ENTERED; + newDragTarget.callDragEventHandler(event); + newDragTarget.mPrivateFlags2 |= View.PFLAG2_DRAG_HOVERED; + newDragTarget.refreshDrawableState(); + } + + event.mAction = action; + event.mX = tx; + event.mY = ty; } }