Skip to content

RUM-16187: Fix tap target mis-attribution for views clipped by scrolling ancestors#3473

Merged
hamorillo merged 2 commits into
developfrom
hector.morilloprieto/RUM-16187
May 27, 2026
Merged

RUM-16187: Fix tap target mis-attribution for views clipped by scrolling ancestors#3473
hamorillo merged 2 commits into
developfrom
hector.morilloprieto/RUM-16187

Conversation

@hamorillo

@hamorillo hamorillo commented May 26, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Replaces View.getLocationInWindow + width × height with View.getGlobalVisibleRect in AndroidActionTrackingStrategy.hitTest so the gesture target resolver respects ancestor clipping.

Before this change, a clickable view scrolled past its parent's visible region (for example, a Button inside a NestedScrollView whose layout position falls behind a sibling BottomNavigationView) was still considered a valid tap target. Because findTarget performs a BFS that overwrites target on every later match, the visually hidden button was visited after the legitimate BottomNavigationItemView and won the resolution, causing taps on the BottomNavigationView to be attributed to the hidden underlying button.

getGlobalVisibleRect returns the view's bounds intersected with all ancestor clip rectangles, so views that are scrolled out of their parent's visible area no longer satisfy the hit-test. Both APIs return coordinates in the window coordinate space, so the comparison against MotionEvent coordinates is unchanged.

Motivation

RUM-16187 (escalated from RUMS-5868).

Customers report that taps on BottomNavigationView items get mis-attributed to underlying Button views inside the ViewPager2 page when those buttons sit at scroll positions that place their raw layout rect behind the nav bar. The behaviour was reproduced on stock SDK 3.9.1 and 3.10.0 in a controlled repro app mirroring the customer's layout (ConstraintLayout with ViewPager2.bottom_toTopOf="@id/separator", separator.bottom_toTopOf="@id/bottom_nav", BottomNavigationView.bottom_toBottomOf="parent"). At the initial scroll position of the NestedScrollView, the last-but-one and last items have layout positions that fall in the window-coord range occupied by the nav bar (item_11 at y ∈ [2132, 2321] for a tap at y = 2232). The stock SDK's raw-bounds hit-test reported these as valid targets.

Additional Notes

  • Coordinate space is unchanged. Both getLocationInWindow and getGlobalVisibleRect return window-space coordinates (top-left of the decorView is origin) per the AOSP Javadocs. The MotionEvent coordinates received via Window.Callback.dispatchTouchEvent are in the same space, so no conversion is needed.
  • A reproducer project is attached to RUM-16187 in Jira for local verification.

The fix was verified end-to-end with dd-sdk-android published to Maven local against a repro app that mirrors the customer-reported layout. With SDK 3.10.0, three of three overlap-state Messages-tab taps were attributed to MaterialButton(item_11). With this patch applied, all three were correctly attributed to BottomNavigationItemView(nav_messages).

Review checklist (to be filled by reviewers)

  • Feature or bugfix MUST have appropriate tests (unit, integration, e2e)
  • Make sure you discussed the feature or bugfix with the maintaining team in an Issue
  • Make sure each commit and the PR mention the Issue number (cf the CONTRIBUTING doc)

…ing ancestors

The SDK's gesture target resolution used View.getLocationInWindow + width x height to compute a view's window-coordinate rectangle for hit-testing. This returns the view's raw layout bounds and does not account for ancestor clipping. As a result, a clickable view scrolled past its parent's visible region (e.g. a Button inside a NestedScrollView whose layout position falls behind a sibling BottomNavigationView) was still considered a valid tap target. Because the SDK's BFS traversal visits the deepest matching view last and overwrites the target on every later match, taps on the BottomNavigationView were attributed to the (visually hidden) underlying button instead of the nav-bar item.

Switch hitTest in AndroidActionTrackingStrategy to use View.getGlobalVisibleRect. This returns the view's bounds intersected with all ancestor clip rectangles, so views that are scrolled out of their parent's visible area no longer satisfy the hit-test. Both APIs return coordinates in the window coordinate space, so the comparison against MotionEvent coordinates is unchanged.
@codecov-commenter

codecov-commenter commented May 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.71429% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 72.27%. Comparing base (7bba71a) to head (6320f08).
⚠️ Report is 14 commits behind head on develop.

Files with missing lines Patch % Lines
...entation/gestures/AndroidActionTrackingStrategy.kt 85.71% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #3473      +/-   ##
===========================================
+ Coverage    72.21%   72.27%   +0.06%     
===========================================
  Files          964      964              
  Lines        35554    35552       -2     
  Branches      5922     5924       +2     
===========================================
+ Hits         25673    25694      +21     
+ Misses        8263     8242      -21     
+ Partials      1618     1616       -2     
Files with missing lines Coverage Δ
...entation/gestures/AndroidActionTrackingStrategy.kt 94.44% <85.71%> (-0.56%) ⬇️

... and 35 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@hamorillo
hamorillo marked this pull request as ready for review May 27, 2026 06:32
@hamorillo
hamorillo requested review from a team as code owners May 27, 2026 06:32
0xnm
0xnm previously approved these changes May 27, 2026

@0xnm 0xnm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice change 👍 One concern is that it may change action-to-view attribution in customer data (in widgets, monitors, etc.), but I don't think it will be dramatic and anyway this change is for correctness.

ambushwork
ambushwork previously approved these changes May 27, 2026

@ambushwork ambushwork left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well done!

Comment thread detekt_custom_unsafe_calls.yml Outdated
jonathanmos
jonathanmos previously approved these changes May 27, 2026
- Drop unnecessary Float-to-Int conversions in hitTest; Float compares with Int as-is in Kotlin
- Add test covering the clipped-scrollable case for the scroll path (mirrors the existing tap test)
- Remove the misleading comment about Rect.set() being a no-op stub
- Remove the now-unused getLocationInWindow mock from the mockView helper
- Remove the orphaned getLocationInWindow entry from detekt_custom_unsafe_calls.yml
@hamorillo
hamorillo dismissed stale reviews from jonathanmos, ambushwork, and 0xnm via 6320f08 May 27, 2026 09:13

@0xnm 0xnm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getGlobalVisibleRect is heavier than getLocationInWindow, so it is worth checking that we don't have performance regression during the active tap/scroll.

@hamorillo
hamorillo merged commit 59852c8 into develop May 27, 2026
28 checks passed
@hamorillo
hamorillo deleted the hector.morilloprieto/RUM-16187 branch May 27, 2026 11:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants