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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user