From ef12811560d4fcce3410eaa4d972dc39001efd4d Mon Sep 17 00:00:00 2001 From: Vadim Tryshev Date: Fri, 16 Sep 2016 14:05:53 -0700 Subject: [PATCH] For pre-N apps, keep entered all parents of an drag-entered child. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bug complains that parents of a view under the drag location don’t get drag events. This is first of a 2 CLs that will restore the old functionality (modulus fixing bugs) for pre-N apps. This CL restores pre-N "nested" model of the entered state for pre-N apps. It also makes possible restoring "nested" model for LOCATION and DROP (implemented in a follow-up CL) The CL replaces (for pre-N) generation of ENTER/EXIT events that happens at the moment of changing the drag focus with generation folowing the recursive delivery of coordinate-bearing events. Bug: 31559942 Change-Id: Iead6bde9c1f88819b30afc78c1f424f7c1b64d51 --- core/java/android/view/View.java | 29 ++++++++++++- core/java/android/view/ViewGroup.java | 53 +++++++++++++++++++++++- core/java/android/view/ViewRootImpl.java | 13 +++--- 3 files changed, 84 insertions(+), 11 deletions(-) diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 1d972eafa3793..37b54a87965a9 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -20856,6 +20856,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return false; } + // Dispatches ACTION_DRAG_ENTERED and ACTION_DRAG_EXITED events for pre-Nougat apps. + boolean dispatchDragEnterExitInPreN(DragEvent event) { + return callDragEventHandler(event); + } + /** * Detects if this View is enabled and has a drag event listener. * If both are true, then it calls the drag event listener with the @@ -20884,13 +20889,33 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } final boolean callDragEventHandler(DragEvent event) { + final boolean result; + ListenerInfo li = mListenerInfo; //noinspection SimplifiableIfStatement if (li != null && li.mOnDragListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnDragListener.onDrag(this, event)) { - return true; + result = true; + } else { + result = onDragEvent(event); } - return onDragEvent(event); + + switch (event.mAction) { + case DragEvent.ACTION_DRAG_ENTERED: { + mPrivateFlags2 |= View.PFLAG2_DRAG_HOVERED; + refreshDrawableState(); + } break; + case DragEvent.ACTION_DRAG_EXITED: { + mPrivateFlags2 &= ~View.PFLAG2_DRAG_HOVERED; + refreshDrawableState(); + } break; + case DragEvent.ACTION_DRAG_ENDED: { + mPrivateFlags2 &= ~View.DRAG_MASK; + refreshDrawableState(); + } break; + } + + return result; } boolean canAcceptDrag() { diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 987db0ac58ed4..7111f28f5741b 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -153,6 +153,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager */ Transformation mInvalidationTransformation; + // Current frontmost child that can accept drag and lies under the drag location. + // Used only to generate ENTER/EXIT events for pre-Nougat aps. + private View mCurrentDragChild; + // Metadata about the ongoing drag private DragEvent mCurrentDragStartEvent; private boolean mIsInterestedInDrag; @@ -1352,6 +1356,20 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return mLocalPoint; } + @Override + boolean dispatchDragEnterExitInPreN(DragEvent event) { + if (event.mAction == DragEvent.ACTION_DRAG_EXITED && mCurrentDragChild != null) { + // The drag exited a sub-tree of views; notify of the exit all descendants that are in + // entered state. + // We don't need this recursive delivery for ENTERED events because they get generated + // from the recursive delivery of LOCATION/DROP events, and hence, don't need their own + // recursion. + mCurrentDragChild.dispatchDragEnterExitInPreN(event); + mCurrentDragChild = null; + } + return mIsInterestedInDrag && super.dispatchDragEnterExitInPreN(event); + } + // TODO: Write real docs @Override public boolean dispatchDragEvent(DragEvent event) { @@ -1364,6 +1382,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager switch (event.mAction) { case DragEvent.ACTION_DRAG_STARTED: { + // Clear the state to recalculate which views we drag over. + mCurrentDragChild = null; + // Set up our tracking of drag-started notifications mCurrentDragStartEvent = DragEvent.obtain(event); if (mChildrenInterestedInDrag == null) { @@ -1408,8 +1429,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (child.dispatchDragEvent(event)) { retval = true; } - child.mPrivateFlags2 &= ~View.DRAG_MASK; - child.refreshDrawableState(); } childrenInterestedInDrag.clear(); } @@ -1430,6 +1449,36 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager case DragEvent.ACTION_DROP: { // Find the [possibly new] drag target View target = findFrontmostDroppableChildAt(event.mX, event.mY, localPoint); + + if (target != mCurrentDragChild) { + if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) { + // For pre-Nougat apps, make sure that the whole hierarchy of views that contain + // the drag location is kept in the state between ENTERED and EXITED events. + // (Starting with N, only the innermost view will be in that state). + + 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 (mCurrentDragChild != null) { + event.mAction = DragEvent.ACTION_DRAG_EXITED; + mCurrentDragChild.dispatchDragEnterExitInPreN(event); + } + + if (target != null) { + event.mAction = DragEvent.ACTION_DRAG_ENTERED; + target.dispatchDragEnterExitInPreN(event); + } + + event.mAction = action; + event.mX = tx; + event.mY = ty; + } + mCurrentDragChild = target; + } + if (target == null && mIsInterestedInDrag) { target = this; } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 8abaa14810e13..0f161cdbc900e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -5524,6 +5524,9 @@ public final class ViewRootImpl implements ViewParent, // 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 the EXITED notification to the current drag view, if any. + if (mTargetSdkVersion < Build.VERSION_CODES.N) { + mView.dispatchDragEnterExitInPreN(event); + } setDragFocus(null, event); } else { // For events with a [screen] location, translate into window coordinates @@ -5641,7 +5644,7 @@ public final class ViewRootImpl implements ViewParent, } public void setDragFocus(View newDragTarget, DragEvent event) { - if (mCurrentDragView != newDragTarget) { + if (mCurrentDragView != newDragTarget && mTargetSdkVersion >= Build.VERSION_CODES.N) { // Send EXITED and ENTERED notifications to the old and new drag focus views. final float tx = event.mX; @@ -5654,23 +5657,19 @@ public final class ViewRootImpl implements ViewParent, 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; } + + mCurrentDragView = newDragTarget; } private AudioManager getAudioManager() {