SIGN IN SIGN UP
facebook / react-native UNCLAIMED

A framework for building native applications using React

0 0 155 C++

Fix `IllegalArgumentException` crash in `ScrollView.onTouchEvent` on Android (#56926)

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](https://github.com/facebook/react-native/commit/67c3ad4e6a1847cbac43115b01f72cc5c8932a61) (2018-02-16, originally proposed in https://github.com/facebook/react-native/issues/12085 and https://github.com/facebook/react-native/issues/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 https://github.com/facebook/react-native/issues/30320
Closes https://github.com/facebook/react-native/issues/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: https://github.com/facebook/react-native/pull/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
T
Tomek Zawadzki committed
d672c96445eff16da06baa95b63c44fd13be47ab
Parent: 58012fb
Committed by meta-codesync[bot] <215208954+meta-codesync[bot]@users.noreply.github.com> on 5/22/2026, 4:39:01 PM