Skip to content

Commit 626568f

Browse files
leg234-pngfacebook-github-bot
authored andcommitted
fix(android): Stabilize custom accessibility action IDs for TalkBack (#52724)
Summary: This pull request resolves a critical accessibility bug on Android where custom `accessibilityActions` fail to execute when activated via TalkBack's swipe gestures. **The Problem:** - When a user focuses a component with custom `accessibilityActions` (like a `TouchableOpacity`), TalkBack correctly announces the action labels as the user swipes up or down. - However, when the user double-taps to activate the selected action, TalkBack reports an "incompatible action," and the `onAccessibilityAction` event is never triggered. **The Root Cause:** The investigation revealed that the `ReactAccessibilityDelegate` was generating **new, unstable IDs** for custom actions on every UI update. This instability prevents the Android accessibility service from reliably tracking and invoking the selected action. **The Solution:** This change introduces a static, thread-safe cache (`ConcurrentHashMap`) within `ReactAccessibilityDelegate`. This ensures that each unique action name is mapped to a single, stable ID for the entire lifecycle of the application. This provides the consistency required by TalkBack to function correctly. This addresses the issue described in #47268. --- ## Changelog: [Android] [Fixed] - Stabilize custom accessibility action IDs to prevent "incompatible action" errors in TalkBack. --- Pull Request resolved: #52724 Test Plan: The fix was validated extensively using the RNTester app on a physical Android device and an Android emulator. ### Steps to Reproduce (Before Fix) 1. Enable TalkBack on an Android device. 2. Navigate to a `TouchableOpacity` component with several custom `accessibilityActions`. 3. Swipe up or down to cycle through the actions. TalkBack correctly announces them (e.g., "add to cart"). 4. Double-tap to execute the selected action. 5. **Result (Bug):** TalkBack announces *"incompatible action"*, and the `onAccessibilityAction` event is not triggered. ### Validation Steps (After Fix) 1. Follow the same steps as above on the patched version. 2. **Result (Fixed):** After double-tapping, the `onAccessibilityAction` event is **correctly triggered** with the appropriate action name. The "incompatible action" issue is fully resolved. *A screen recording demonstrating the successful fix can be provided if needed.* Uploading fixed bugs view problems (1).mp4… Fixes #47268 Reviewed By: jorge-cab Differential Revision: D78737471 Pulled By: cipolleschi fbshipit-source-id: 877b196597472ac6a4f6df81a05a43956fb34629
1 parent 2c540ac commit 626568f

1 file changed

Lines changed: 17 additions & 6 deletions

File tree

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.java

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import com.facebook.react.uimanager.util.ReactFindViewUtil;
4444
import java.util.HashMap;
4545
import java.util.List;
46+
import java.util.Map;
4647

4748
/**
4849
* Utility class that handles the addition of a "role" for accessibility to either a View or
@@ -54,7 +55,8 @@ public class ReactAccessibilityDelegate extends ExploreByTouchHelper {
5455
public static final HashMap<String, Integer> sActionIdMap = new HashMap<>();
5556

5657
private static final String TAG = "ReactAccessibilityDelegate";
57-
private static int sCounter = 0x3f000000;
58+
private static int sCustomActionCounter = 0x3f000000;
59+
private static final Map<String, Integer> sCustomActionIdMap = new HashMap<>();
5860
private static final int TIMEOUT_SEND_ACCESSIBILITY_EVENT = 200;
5961
private static final int SEND_EVENT = 1;
6062
private static final String delimiter = ", ";
@@ -192,14 +194,23 @@ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCo
192194
if (!action.hasKey("name")) {
193195
throw new IllegalArgumentException("Unknown accessibility action.");
194196
}
195-
int actionId = sCounter;
197+
198+
String actionName = action.getString("name");
196199
String actionLabel = action.hasKey("label") ? action.getString("label") : null;
197-
if (sActionIdMap.containsKey(action.getString("name"))) {
198-
actionId = sActionIdMap.get(action.getString("name"));
200+
int actionId;
201+
202+
if (sActionIdMap.containsKey(actionName)) {
203+
actionId = sActionIdMap.get(actionName);
199204
} else {
200-
sCounter++;
205+
if (sCustomActionIdMap.containsKey(actionName)) {
206+
actionId = sCustomActionIdMap.get(actionName);
207+
} else {
208+
actionId = sCustomActionCounter++;
209+
sCustomActionIdMap.put(actionName, actionId);
210+
}
201211
}
202-
mAccessibilityActionsMap.put(actionId, action.getString("name"));
212+
213+
mAccessibilityActionsMap.put(actionId, actionName);
203214
final AccessibilityActionCompat accessibilityAction =
204215
new AccessibilityActionCompat(actionId, actionLabel);
205216
info.addAction(accessibilityAction);

0 commit comments

Comments
 (0)