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
This commit is contained in:
Jeff Brown
2012-04-19 15:19:19 -07:00
parent 00710e906b
commit 59a422e900
2 changed files with 79 additions and 1 deletions

View File

@@ -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);
}
}

View File

@@ -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);