From 59a422e90035ce5df45c526607db2d3303e3112e Mon Sep 17 00:00:00 2001 From: Jeff Brown Date: Thu, 19 Apr 2012 15:19:19 -0700 Subject: [PATCH] Ensure that touch and hover targets are cleared when needed. When views are removed from a view or a view is detached from a window, we need to update the touch and hover targets appropriately. Failing to do this resulted in a NPE while dispatching an ACTION_HOVER_EXIT to a view that had previously been removed. Removed views should not get input events. Change-Id: I4af4f8e2c4028347d3f570894fd1b3b366d11455 --- core/java/android/view/View.java | 2 +- core/java/android/view/ViewGroup.java | 78 +++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 537c4743d4727..ab3413e60f6d3 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -7338,7 +7338,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT); // If the window does not have input focus we take away accessibility // focus as soon as the user stop hovering over the view. - if (!mAttachInfo.mHasWindowFocus) { + if (mAttachInfo != null && !mAttachInfo.mHasWindowFocus) { getViewRootImpl().setAccessibilityFocusedHost(null); } } diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 91e945b14e257..1641d4c784d14 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -1569,6 +1569,43 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return handled; } + private void exitHoverTargets() { + if (mHoveredSelf || mFirstHoverTarget != null) { + final long now = SystemClock.uptimeMillis(); + MotionEvent event = MotionEvent.obtain(now, now, + MotionEvent.ACTION_HOVER_EXIT, 0.0f, 0.0f, 0); + event.setSource(InputDevice.SOURCE_TOUCHSCREEN); + dispatchHoverEvent(event); + event.recycle(); + } + } + + private void cancelHoverTarget(View view) { + HoverTarget predecessor = null; + HoverTarget target = mFirstHoverTarget; + while (target != null) { + final HoverTarget next = target.next; + if (target.child == view) { + if (predecessor == null) { + mFirstHoverTarget = next; + } else { + predecessor.next = next; + } + target.recycle(); + + final long now = SystemClock.uptimeMillis(); + MotionEvent event = MotionEvent.obtain(now, now, + MotionEvent.ACTION_HOVER_EXIT, 0.0f, 0.0f, 0); + event.setSource(InputDevice.SOURCE_TOUCHSCREEN); + view.dispatchHoverEvent(event); + event.recycle(); + return; + } + predecessor = target; + target = next; + } + } + /** @hide */ @Override protected boolean hasHoveredChild() { @@ -1997,6 +2034,32 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } + private void cancelTouchTarget(View view) { + TouchTarget predecessor = null; + TouchTarget target = mFirstTouchTarget; + while (target != null) { + final TouchTarget next = target.next; + if (target.child == view) { + if (predecessor == null) { + mFirstTouchTarget = next; + } else { + predecessor.next = next; + } + target.recycle(); + + final long now = SystemClock.uptimeMillis(); + MotionEvent event = MotionEvent.obtain(now, now, + MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); + event.setSource(InputDevice.SOURCE_TOUCHSCREEN); + view.dispatchTouchEvent(event); + event.recycle(); + return; + } + predecessor = target; + target = next; + } + } + /** * Returns true if a child view can receive pointer events. * @hide @@ -2416,6 +2479,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager // first send it an ACTION_CANCEL motion event. cancelAndClearTouchTargets(null); + // Similarly, set ACTION_EXIT to all hover targets and clear them. + exitHoverTargets(); + // In case view is detached while transition is running mLayoutSuppressed = false; @@ -3453,6 +3519,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager clearChildFocus = true; } + cancelTouchTarget(view); + cancelHoverTarget(view); + if (view.getAnimation() != null || (mTransitioningViews != null && mTransitioningViews.contains(view))) { addDisappearingView(view); @@ -3533,6 +3602,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager clearChildFocus = view; } + cancelTouchTarget(view); + cancelHoverTarget(view); + if (view.getAnimation() != null || (mTransitioningViews != null && mTransitioningViews.contains(view))) { addDisappearingView(view); @@ -3603,6 +3675,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager clearChildFocus = view; } + cancelTouchTarget(view); + cancelHoverTarget(view); + if (view.getAnimation() != null || (mTransitioningViews != null && mTransitioningViews.contains(view))) { addDisappearingView(view); @@ -3648,6 +3723,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager child.clearFocus(); } + cancelTouchTarget(child); + cancelHoverTarget(child); + if ((animate && child.getAnimation() != null) || (mTransitioningViews != null && mTransitioningViews.contains(child))) { addDisappearingView(child);