From 8a1211c685bca0950e03f73f7ea48f9a953ca506 Mon Sep 17 00:00:00 2001 From: Bohdan Pomohaibo Date: Wed, 21 Oct 2026 22:51:54 +0300 Subject: [PATCH 1/2] Prevent disabled Android TextInput from claiming native touches --- .../react/views/textinput/ReactEditText.kt | 5 +++ .../textinput/ReactTextInputPropertyTest.kt | 42 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt index 754f10026624..e01f2274251f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt @@ -314,6 +314,11 @@ public open class ReactEditText public constructor(context: Context) : AppCompat } override fun onTouchEvent(ev: MotionEvent): Boolean { + // Disabled inputs must not block an ancestor from handling the touch. + if (!isEnabled) { + return false + } + when (ev.action) { MotionEvent.ACTION_DOWN -> { detectScrollMovement = true diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt index 82ec014eb89e..dd7ff74debf9 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt @@ -22,8 +22,10 @@ import android.text.Spanned import android.text.TextUtils import android.util.DisplayMetrics import android.view.Gravity +import android.view.MotionEvent import android.view.View import android.view.inputmethod.EditorInfo +import android.widget.FrameLayout import androidx.annotation.RequiresApi import androidx.autofill.HintConstants import androidx.core.content.res.ResourcesCompat.ID_NULL @@ -224,6 +226,46 @@ class ReactTextInputPropertyTest { assertThat(view.ellipsize).isEqualTo(TextUtils.TruncateAt.END) } + @Test + fun testDisabledInputDoesNotClaimNativeTouches() { + val interceptionRequests = mutableListOf() + val parent = + object : FrameLayout(themedContext) { + override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) { + interceptionRequests.add(disallowIntercept) + super.requestDisallowInterceptTouchEvent(disallowIntercept) + } + } + parent.addView(view) + view.isEnabled = false + + for (action in + listOf( + MotionEvent.ACTION_DOWN, + MotionEvent.ACTION_MOVE, + MotionEvent.ACTION_UP, + MotionEvent.ACTION_CANCEL, + )) { + val event = MotionEvent.obtain(0, 0, action, 10f, 10f, 0) + try { + assertThat(view.onTouchEvent(event)).isFalse() + } finally { + event.recycle() + } + } + assertThat(interceptionRequests).isEmpty() + + // Re-enabling the same input must restore its normal touch handling. + view.isEnabled = true + val down = MotionEvent.obtain(1, 1, MotionEvent.ACTION_DOWN, 10f, 10f, 0) + try { + view.onTouchEvent(down) + } finally { + down.recycle() + } + assertThat(interceptionRequests).containsExactly(true) + } + @Test fun testEditable() { manager.updateProperties(view, buildStyles()) From 637048c995555cc465d2b8207df81abaf4ad94eb Mon Sep 17 00:00:00 2001 From: Bohdan Pomohaibo Date: Thu, 22 Oct 2026 00:56:43 +0300 Subject: [PATCH 2/2] fix(android): release touch interception when TextInput is disabled --- .../react/views/textinput/ReactEditText.kt | 11 ++++ .../textinput/ReactTextInputPropertyTest.kt | 50 +++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt index e01f2274251f..ce7c023043d6 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt @@ -132,6 +132,7 @@ public open class ReactEditText public constructor(context: Context) : AppCompat private var scrollWatcher: ScrollWatcher? private var keyListener: InternalKeyListener? = null private var detectScrollMovement = false + private var isParentInterceptDisallowed = false private var onKeyPress = false private val textAttributes: TextAttributes private var typefaceDirty = false @@ -316,6 +317,11 @@ public open class ReactEditText public constructor(context: Context) : AppCompat override fun onTouchEvent(ev: MotionEvent): Boolean { // Disabled inputs must not block an ancestor from handling the touch. if (!isEnabled) { + if (isParentInterceptDisallowed) { + parent?.requestDisallowInterceptTouchEvent(false) + isParentInterceptDisallowed = false + } + detectScrollMovement = false return false } @@ -325,6 +331,7 @@ public open class ReactEditText public constructor(context: Context) : AppCompat // Disallow parent views to intercept touch events, until we can detect if we should be // capturing these touches or not. this.parent.requestDisallowInterceptTouchEvent(true) + isParentInterceptDisallowed = true } MotionEvent.ACTION_MOVE -> @@ -337,9 +344,13 @@ public open class ReactEditText public constructor(context: Context) : AppCompat ) { // We cannot scroll, let parent views take care of these touches. this.parent.requestDisallowInterceptTouchEvent(false) + isParentInterceptDisallowed = false } detectScrollMovement = false } + + MotionEvent.ACTION_UP, + MotionEvent.ACTION_CANCEL -> isParentInterceptDisallowed = false } return super.onTouchEvent(ev) } diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt index dd7ff74debf9..08dc4159b5c8 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt @@ -266,6 +266,56 @@ class ReactTextInputPropertyTest { assertThat(interceptionRequests).containsExactly(true) } + @Test + fun testDisablingInputDuringGestureAllowsParentInterception() { + for (moveBeforeDisabling in listOf(false, true)) { + // Keep interception disallowed even if the input receives a MOVE before being disabled. + val input = + object : ReactEditText(themedContext) { + override fun canScrollVertically(direction: Int): Boolean = true + } + var interceptedMove = false + val parent = + object : FrameLayout(themedContext) { + override fun onInterceptTouchEvent(event: MotionEvent): Boolean { + if (event.action == MotionEvent.ACTION_MOVE) { + interceptedMove = true + return true + } + return false + } + + override fun onTouchEvent(event: MotionEvent): Boolean = true + } + parent.addView(input) + parent.layout(0, 0, 100, 100) + input.layout(0, 0, 100, 100) + + fun dispatch(action: Int): Boolean { + val event = MotionEvent.obtain(0, 0, action, 10f, 10f, 0) + return try { + parent.dispatchTouchEvent(event) + } finally { + event.recycle() + } + } + + assertThat(dispatch(MotionEvent.ACTION_DOWN)).isTrue() + if (moveBeforeDisabling) { + dispatch(MotionEvent.ACTION_MOVE) + } + assertThat(interceptedMove).isFalse() + + input.isEnabled = false + // The first disabled MOVE releases the parent's interception flag. + dispatch(MotionEvent.ACTION_MOVE) + assertThat(interceptedMove).isFalse() + dispatch(MotionEvent.ACTION_MOVE) + assertThat(interceptedMove).isTrue() + dispatch(MotionEvent.ACTION_UP) + } + } + @Test fun testEditable() { manager.updateProperties(view, buildStyles())