Skip to content

Commit 0e7b432

Browse files
committed
Do not traverse children if a touch event is not within view groups bounds
1 parent 0bcad7e commit 0e7b432

File tree

4 files changed

+57
-26
lines changed

4 files changed

+57
-26
lines changed

sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/AndroidViewGestureTargetLocator.java

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ public final class AndroidViewGestureTargetLocator implements GestureTargetLocat
1818
private static final String ORIGIN = "old_view_system";
1919

2020
private final boolean isAndroidXAvailable;
21-
private final int[] coordinates = new int[2];
2221

2322
public AndroidViewGestureTargetLocator(final boolean isAndroidXAvailable) {
2423
this.isAndroidXAvailable = isAndroidXAvailable;
@@ -31,13 +30,11 @@ public AndroidViewGestureTargetLocator(final boolean isAndroidXAvailable) {
3130
return null;
3231
}
3332
final View view = (View) root;
34-
if (touchWithinBounds(view, x, y)) {
35-
if (targetType == UiElement.Type.CLICKABLE && isViewTappable(view)) {
36-
return createUiElement(view);
37-
} else if (targetType == UiElement.Type.SCROLLABLE
38-
&& isViewScrollable(view, isAndroidXAvailable)) {
39-
return createUiElement(view);
40-
}
33+
if (targetType == UiElement.Type.CLICKABLE && isViewTappable(view)) {
34+
return createUiElement(view);
35+
} else if (targetType == UiElement.Type.SCROLLABLE
36+
&& isViewScrollable(view, isAndroidXAvailable)) {
37+
return createUiElement(view);
4138
}
4239
return null;
4340
}
@@ -52,17 +49,6 @@ private UiElement createUiElement(final @NotNull View targetView) {
5249
}
5350
}
5451

55-
private boolean touchWithinBounds(final @NotNull View view, final float x, final float y) {
56-
view.getLocationOnScreen(coordinates);
57-
int vx = coordinates[0];
58-
int vy = coordinates[1];
59-
60-
int w = view.getWidth();
61-
int h = view.getHeight();
62-
63-
return !(x < vx || x > vx + w || y < vy || y > vy + h);
64-
}
65-
6652
private static boolean isViewTappable(final @NotNull View view) {
6753
return view.isClickable() && view.getVisibility() == View.VISIBLE;
6854
}

sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/ViewUtils.java

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,32 @@
1616
@ApiStatus.Internal
1717
public final class ViewUtils {
1818

19+
private static final int[] coordinates = new int[2];
20+
21+
/**
22+
* Verifies if the given touch coordinates are within the bounds of the given view.
23+
*
24+
* @param view the view to check if the touch coordinates are within its bounds
25+
* @param x - the x coordinate of a {@link MotionEvent}
26+
* @param y - the y coordinate of {@link MotionEvent}
27+
* @return true if the touch coordinates are within the bounds of the view, false otherwise
28+
*/
29+
private static boolean touchWithinBounds(
30+
final @Nullable View view, final float x, final float y) {
31+
if (view == null) {
32+
return false;
33+
}
34+
35+
view.getLocationOnScreen(coordinates);
36+
int vx = coordinates[0];
37+
int vy = coordinates[1];
38+
39+
int w = view.getWidth();
40+
int h = view.getHeight();
41+
42+
return !(x < vx || x > vx + w || y < vy || y > vy + h);
43+
}
44+
1945
/**
2046
* Finds a target view, that has been selected/clicked by the given coordinates x and y and the
2147
* given {@code viewTargetSelector}.
@@ -41,6 +67,11 @@ public final class ViewUtils {
4167
while (queue.size() > 0) {
4268
final View view = queue.poll();
4369

70+
if (!touchWithinBounds(view, x, y)) {
71+
// if the touch is not hitting the view, skip traversal of its children
72+
continue;
73+
}
74+
4475
if (view instanceof ViewGroup) {
4576
final ViewGroup viewGroup = (ViewGroup) view;
4677
for (int i = 0; i < viewGroup.getChildCount(); i++) {
@@ -53,7 +84,7 @@ public final class ViewUtils {
5384
if (newTarget != null) {
5485
if (targetType == UiElement.Type.CLICKABLE) {
5586
target = newTarget;
56-
} else {
87+
} else if (targetType == UiElement.Type.SCROLLABLE) {
5788
return newTarget;
5889
}
5990
}

sentry-android-core/src/test/java/io/sentry/android/core/UserInteractionIntegrationTest.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ class UserInteractionIntegrationTest {
140140
)
141141
)
142142

143-
sut.register(fixture.hub, fixture.options)
143+
sut.register(fixture.scopes, fixture.options)
144144
sut.onActivityPaused(fixture.activity)
145145

146146
verify(fixture.window).callback = null
@@ -161,7 +161,7 @@ class UserInteractionIntegrationTest {
161161
)
162162
)
163163

164-
sut.register(fixture.hub, fixture.options)
164+
sut.register(fixture.scopes, fixture.options)
165165
sut.onActivityPaused(fixture.activity)
166166

167167
verify(fixture.window).callback = delegate
@@ -172,7 +172,7 @@ class UserInteractionIntegrationTest {
172172
val callback = mock<SentryWindowCallback>()
173173
val sut = fixture.getSut(callback)
174174

175-
sut.register(fixture.hub, fixture.options)
175+
sut.register(fixture.scopes, fixture.options)
176176
sut.onActivityPaused(fixture.activity)
177177

178178
verify(callback).stopTracking()
@@ -192,7 +192,7 @@ class UserInteractionIntegrationTest {
192192
)
193193
val sut = fixture.getSut(existingCallback)
194194

195-
sut.register(fixture.hub, fixture.options)
195+
sut.register(fixture.scopes, fixture.options)
196196
sut.onActivityResumed(fixture.activity)
197197

198198
val argumentCaptor = argumentCaptor<Window.Callback>()

sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerClickTest.kt

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ class SentryGestureListenerClickTest {
223223

224224
val event = mock<MotionEvent>()
225225
val sut = fixture.getSut<LocalView>(event, attachViewsToRoot = false)
226-
fixture.window.mockDecorView<ViewGroup>(event = event, touchWithinBounds = false) {
226+
fixture.window.mockDecorView<ViewGroup>(event = event, touchWithinBounds = true) {
227227
whenever(it.childCount).thenReturn(1)
228228
whenever(it.getChildAt(0)).thenReturn(fixture.target)
229229
}
@@ -244,7 +244,7 @@ class SentryGestureListenerClickTest {
244244

245245
val event = mock<MotionEvent>()
246246
val sut = fixture.getSut<LocalView>(event, attachViewsToRoot = false)
247-
fixture.window.mockDecorView<ViewGroup>(event = event, touchWithinBounds = false) {
247+
fixture.window.mockDecorView<ViewGroup>(event = event, touchWithinBounds = true) {
248248
whenever(it.childCount).thenReturn(1)
249249
whenever(it.getChildAt(0)).thenReturn(fixture.target)
250250
}
@@ -253,4 +253,18 @@ class SentryGestureListenerClickTest {
253253

254254
verify(fixture.scope).propagationContext = any()
255255
}
256+
257+
@Test
258+
fun `if touch is not within view group bounds does not traverse its children`() {
259+
val event = mock<MotionEvent>()
260+
val sut = fixture.getSut<View>(event, attachViewsToRoot = true)
261+
fixture.window.mockDecorView<ViewGroup>(event = event, touchWithinBounds = false) {
262+
whenever(it.childCount).thenReturn(1)
263+
whenever(it.getChildAt(0)).thenReturn(fixture.target)
264+
}
265+
266+
sut.onSingleTapUp(event)
267+
268+
verify(fixture.scopes, never()).addBreadcrumb(any<Breadcrumb>())
269+
}
256270
}

0 commit comments

Comments
 (0)