Skip to content

Commit 59a422e

Browse files
author
Jeff Brown
committed
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
1 parent 00710e9 commit 59a422e

File tree

2 files changed

+79
-1
lines changed

2 files changed

+79
-1
lines changed

core/java/android/view/View.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7338,7 +7338,7 @@ && pointInView(event.getX(), event.getY())) {
73387338
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
73397339
// If the window does not have input focus we take away accessibility
73407340
// focus as soon as the user stop hovering over the view.
7341-
if (!mAttachInfo.mHasWindowFocus) {
7341+
if (mAttachInfo != null && !mAttachInfo.mHasWindowFocus) {
73427342
getViewRootImpl().setAccessibilityFocusedHost(null);
73437343
}
73447344
}

core/java/android/view/ViewGroup.java

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1569,6 +1569,43 @@ protected boolean dispatchHoverEvent(MotionEvent event) {
15691569
return handled;
15701570
}
15711571

1572+
private void exitHoverTargets() {
1573+
if (mHoveredSelf || mFirstHoverTarget != null) {
1574+
final long now = SystemClock.uptimeMillis();
1575+
MotionEvent event = MotionEvent.obtain(now, now,
1576+
MotionEvent.ACTION_HOVER_EXIT, 0.0f, 0.0f, 0);
1577+
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
1578+
dispatchHoverEvent(event);
1579+
event.recycle();
1580+
}
1581+
}
1582+
1583+
private void cancelHoverTarget(View view) {
1584+
HoverTarget predecessor = null;
1585+
HoverTarget target = mFirstHoverTarget;
1586+
while (target != null) {
1587+
final HoverTarget next = target.next;
1588+
if (target.child == view) {
1589+
if (predecessor == null) {
1590+
mFirstHoverTarget = next;
1591+
} else {
1592+
predecessor.next = next;
1593+
}
1594+
target.recycle();
1595+
1596+
final long now = SystemClock.uptimeMillis();
1597+
MotionEvent event = MotionEvent.obtain(now, now,
1598+
MotionEvent.ACTION_HOVER_EXIT, 0.0f, 0.0f, 0);
1599+
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
1600+
view.dispatchHoverEvent(event);
1601+
event.recycle();
1602+
return;
1603+
}
1604+
predecessor = target;
1605+
target = next;
1606+
}
1607+
}
1608+
15721609
/** @hide */
15731610
@Override
15741611
protected boolean hasHoveredChild() {
@@ -1997,6 +2034,32 @@ private void removePointersFromTouchTargets(int pointerIdBits) {
19972034
}
19982035
}
19992036

2037+
private void cancelTouchTarget(View view) {
2038+
TouchTarget predecessor = null;
2039+
TouchTarget target = mFirstTouchTarget;
2040+
while (target != null) {
2041+
final TouchTarget next = target.next;
2042+
if (target.child == view) {
2043+
if (predecessor == null) {
2044+
mFirstTouchTarget = next;
2045+
} else {
2046+
predecessor.next = next;
2047+
}
2048+
target.recycle();
2049+
2050+
final long now = SystemClock.uptimeMillis();
2051+
MotionEvent event = MotionEvent.obtain(now, now,
2052+
MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
2053+
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
2054+
view.dispatchTouchEvent(event);
2055+
event.recycle();
2056+
return;
2057+
}
2058+
predecessor = target;
2059+
target = next;
2060+
}
2061+
}
2062+
20002063
/**
20012064
* Returns true if a child view can receive pointer events.
20022065
* @hide
@@ -2416,6 +2479,9 @@ void dispatchDetachedFromWindow() {
24162479
// first send it an ACTION_CANCEL motion event.
24172480
cancelAndClearTouchTargets(null);
24182481

2482+
// Similarly, set ACTION_EXIT to all hover targets and clear them.
2483+
exitHoverTargets();
2484+
24192485
// In case view is detached while transition is running
24202486
mLayoutSuppressed = false;
24212487

@@ -3453,6 +3519,9 @@ private void removeViewInternal(int index, View view) {
34533519
clearChildFocus = true;
34543520
}
34553521

3522+
cancelTouchTarget(view);
3523+
cancelHoverTarget(view);
3524+
34563525
if (view.getAnimation() != null ||
34573526
(mTransitioningViews != null && mTransitioningViews.contains(view))) {
34583527
addDisappearingView(view);
@@ -3533,6 +3602,9 @@ private void removeViewsInternal(int start, int count) {
35333602
clearChildFocus = view;
35343603
}
35353604

3605+
cancelTouchTarget(view);
3606+
cancelHoverTarget(view);
3607+
35363608
if (view.getAnimation() != null ||
35373609
(mTransitioningViews != null && mTransitioningViews.contains(view))) {
35383610
addDisappearingView(view);
@@ -3603,6 +3675,9 @@ public void removeAllViewsInLayout() {
36033675
clearChildFocus = view;
36043676
}
36053677

3678+
cancelTouchTarget(view);
3679+
cancelHoverTarget(view);
3680+
36063681
if (view.getAnimation() != null ||
36073682
(mTransitioningViews != null && mTransitioningViews.contains(view))) {
36083683
addDisappearingView(view);
@@ -3648,6 +3723,9 @@ protected void removeDetachedView(View child, boolean animate) {
36483723
child.clearFocus();
36493724
}
36503725

3726+
cancelTouchTarget(child);
3727+
cancelHoverTarget(child);
3728+
36513729
if ((animate && child.getAnimation() != null) ||
36523730
(mTransitioningViews != null && mTransitioningViews.contains(child))) {
36533731
addDisappearingView(child);

0 commit comments

Comments
 (0)