Commit d672c96
Summary:
Android's framework `ScrollView` can throw `IllegalArgumentException: pointerIndex out of range` from `MotionEvent.getY()` in `ScrollView.onTouchEvent` when the view's tracked active pointer becomes stale after a multi-touch `ACTION_POINTER_UP`.
### This is a known Android framework bug
The same exception in sibling widgets (`SwipeRefreshLayout`, `ViewPager`) has been reported and worked around with `try`/`catch` since at least 2014 — see the [Stack Overflow question](https://stackoverflow.com/questions/27662682/illegalargumentexception-pointerindex-out-of-range-from-swiperefreshlayout) already linked in the existing in-tree comment, and Google's AOSP [Issue #36987494](https://issuetracker.google.com/issues/36987494). React Native added the same workaround to `ReactScrollView.onInterceptTouchEvent` in [67c3ad4](67c3ad4) (2018-02-16, originally proposed in #12085 and #13166), with the comment:
```java
} catch (IllegalArgumentException e) {
// Log and ignore the error. This seems to be a bug in the android SDK and
// this is the commonly accepted workaround.
// https://tinyurl.com/mw6qkod (Stack Overflow)
FLog.w(ReactConstants.TAG, "Error intercepting touch event.", e);
}
```
The bug is still active on current Android releases — the production stack trace below is from **Android 15 (SDK 35) on a Motorola moto g15, May 2026**.
### The gap this PR closes
`ReactScrollView`, `ReactHorizontalScrollView` and `ReactNestedScrollView` already catch this `IllegalArgumentException` in `onInterceptTouchEvent`, but **the same catch is missing from `onTouchEvent`** in all three classes, even though `super.onTouchEvent(ev)` reaches the same framework code path that can throw. This PR mirrors the existing in-file workaround into `onTouchEvent`.
### Production stack trace (Sentry, May 2026)
**Device:** Motorola moto g15
**OS:** Android 15 (SDK 35, build `VVTA35.51-153`)
**App stack:** React Native 0.85.3 with react-native-gesture-handler
```
java.lang.IllegalArgumentException: invalid pointerIndex -1 for MotionEvent { action=POINTER_UP(1), id[0]=0, x[0]=591.75, y[0]=153.185, id[1]=1, x[1]=875.5, y[1]=194.935, pointerCount=2, eventTime=15421926904000, downTime=15421677988000, deviceId=12, source=TOUCHSCREEN, displayId=0, eventId=0x38b97d3}
at android.view.MotionEvent.nativeGetAxisValue(MotionEvent.java)
at android.view.MotionEvent.getY(MotionEvent.java:2828)
at android.widget.ScrollView.onTouchEvent(ScrollView.java:941)
at com.facebook.react.views.scroll.l.onTouchEvent(r8-map-id-42abedea19c8d6be0f67a7054855378c0d176430e10d921ab2155e03012a3488:77)
at android.view.View.performOnTouchCallback(View.java:16474)
at android.view.View.dispatchTouchEvent(View.java:16426)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3169)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2811)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3197)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
at com.swmansion.gesturehandler.react.k.dispatchTouchEvent(r8-map-id-42abedea19c8d6be0f67a7054855378c0d176430e10d921ab2155e03012a3488:47)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3175)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2825)
at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:458)
at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1980)
at android.app.Activity.dispatchTouchEvent(Activity.java:4579)
at androidx.appcompat.view.i.dispatchTouchEvent(r8-map-id-42abedea19c8d6be0f67a7054855378c0d176430e10d921ab2155e03012a3488:3)
at io.sentry.android.core.internal.gestures.k.dispatchTouchEvent(r8-map-id-42abedea19c8d6be0f67a7054855378c0d176430e10d921ab2155e03012a3488:3)
at io.sentry.android.core.internal.gestures.i.dispatchTouchEvent(r8-map-id-42abedea19c8d6be0f67a7054855378c0d176430e10d921ab2155e03012a3488:40)
at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:416)
at android.view.View.dispatchPointerEvent(View.java:16761)
at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:8145)
at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:7889)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:7281)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:7338)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:7304)
at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:7470)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:7312)
at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:7527)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:7285)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:7338)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:7304)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:7312)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:7285)
at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:10505)
at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:10452)
at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:10407)
at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:10670)
at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:295)
at android.os.MessageQueue.nativePollOnce(MessageQueue.java)
at android.os.MessageQueue.next(MessageQueue.java:346)
at android.os.Looper.loopOnce(Looper.java:191)
at android.os.Looper.loop(Looper.java:319)
at android.app.ActivityThread.main(ActivityThread.java:8754)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:962)
```
The `MotionEvent` dump pinpoints the trigger: a two-finger `ACTION_POINTER_UP(1)` where the framework `ScrollView`'s tracked `mActivePointerId` is stale, so `findPointerIndex(...)` returns `-1` and `getY(-1)` throws from native code.
Closes #30320
Closes #29642
Both issues were auto-closed by the stale bot without a fix shipping, despite multiple confirmed reproductions over 4+ years.
## Changelog:
[ANDROID] [FIXED] - Catch IllegalArgumentException in ScrollView.onTouchEvent to prevent crashes from a known Android framework multi-touch bug
Pull Request resolved: #56926
Test Plan: Can't really reproduce locally — the crash happens very infrequently in production. The Sentry stack trace above is what triggered this PR.
Reviewed By: fabriziocucci
Differential Revision: D106042303
Pulled By: Abbondanzo
fbshipit-source-id: fe662a390673418a558da83c7bd94011bbf0cab5
1 parent 58012fb commit d672c96
3 files changed
Lines changed: 28 additions & 4 deletions
File tree
- packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll
Lines changed: 9 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
838 | 838 | | |
839 | 839 | | |
840 | 840 | | |
841 | | - | |
| 841 | + | |
| 842 | + | |
| 843 | + | |
| 844 | + | |
| 845 | + | |
| 846 | + | |
| 847 | + | |
| 848 | + | |
| 849 | + | |
842 | 850 | | |
843 | 851 | | |
844 | 852 | | |
| |||
Lines changed: 10 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
| 7 | + | |
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| |||
680 | 680 | | |
681 | 681 | | |
682 | 682 | | |
683 | | - | |
| 683 | + | |
| 684 | + | |
| 685 | + | |
| 686 | + | |
| 687 | + | |
| 688 | + | |
| 689 | + | |
| 690 | + | |
| 691 | + | |
684 | 692 | | |
685 | 693 | | |
686 | 694 | | |
| |||
Lines changed: 9 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
672 | 672 | | |
673 | 673 | | |
674 | 674 | | |
675 | | - | |
| 675 | + | |
| 676 | + | |
| 677 | + | |
| 678 | + | |
| 679 | + | |
| 680 | + | |
| 681 | + | |
| 682 | + | |
| 683 | + | |
676 | 684 | | |
677 | 685 | | |
678 | 686 | | |
| |||
0 commit comments