From 2e2da47ee720a55f2b1e6df414f04a55101948b7 Mon Sep 17 00:00:00 2001 From: Yevhenii Date: Wed, 14 Oct 2026 16:49:34 +0300 Subject: [PATCH] Stop routing TalkBack's click to removed accessibility actions on Android A view whose accessibilityActions declared activate kept dispatching it on TalkBack's click after the actions were removed or replaced with an empty list, so the view's own click never ran. setAccessibilityActions returned before writing the tag when the prop was null, and ReactAccessibilityDelegate routed an action through a map of every action it had built node info with, never dropping one. setAccessibilityActions now writes null to the tag, and the delegate resolves an action against the view's current accessibilityActions. --- .../react/uimanager/BaseViewManager.java | 6 +-- .../uimanager/ReactAccessibilityDelegate.kt | 23 ++++++-- .../react/uimanager/BaseViewManagerTest.kt | 54 +++++++++++++++++++ 3 files changed, 74 insertions(+), 9 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java index 55daec06a8c6..585e7748728d 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java @@ -473,11 +473,7 @@ private void updateViewContentDescription(@NonNull T view) { } @ReactProp(name = ViewProps.ACCESSIBILITY_ACTIONS) - public void setAccessibilityActions(T view, ReadableArray accessibilityActions) { - if (accessibilityActions == null) { - return; - } - + public void setAccessibilityActions(T view, @Nullable ReadableArray accessibilityActions) { view.setTag(R.id.accessibility_actions, accessibilityActions); } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.kt index 321a5897226d..8b7ad0d6936c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.kt @@ -57,7 +57,6 @@ public open class ReactAccessibilityDelegate( // The View this delegate is attac host?.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED) } } - private val accessibilityActionsMap = HashMap() private var accessibilityLabelledBy: View? = null init { @@ -130,7 +129,6 @@ public open class ReactAccessibilityDelegate( // The View this delegate is attac val actionId: Int = actionIdMap.get(actionName) ?: customActionIdMap.getOrPut(actionName) { customActionCounter++ } - accessibilityActionsMap[actionId] = actionName val accessibilityAction = AccessibilityActionCompat(actionId, actionLabel) info.addAction(accessibilityAction) } @@ -225,9 +223,10 @@ public open class ReactAccessibilityDelegate( // The View this delegate is attac if (action == AccessibilityNodeInfoCompat.ACTION_EXPAND) { host.setTag(R.id.accessibility_state_expanded, true) } - if (accessibilityActionsMap.containsKey(action)) { + val actionName = findDeclaredActionName(host, action) + if (actionName != null) { val eventData = createMap() - eventData.putString("actionName", accessibilityActionsMap[action]) + eventData.putString("actionName", actionName) if ( action == AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_PROGRESS.id && args?.containsKey(AccessibilityNodeInfo.ACTION_ARGUMENT_PROGRESS_VALUE) == true @@ -273,6 +272,22 @@ public open class ReactAccessibilityDelegate( // The View this delegate is attac return super.performAccessibilityAction(host, action, args) } + private fun findDeclaredActionName(host: View, actionId: Int): String? { + val accessibilityActions = + host.getTag(R.id.accessibility_actions) as ReadableArray? ?: return null + for (i in 0..