Skip to content

fix: android flicker on a11y tree traversal#3635

Open
isekovanic wants to merge 2 commits into
developfrom
fix/android-flicker-on-a11y-tree-traversal
Open

fix: android flicker on a11y tree traversal#3635
isekovanic wants to merge 2 commits into
developfrom
fix/android-flicker-on-a11y-tree-traversal

Conversation

@isekovanic

Copy link
Copy Markdown
Contributor

🎯 Goal

This PR fixes an Android only full screen flash that occurs when closing the message context menu, unfortunately introduced alongside the focus trap work in #3634. Fortunately though it was caught early.

The flash is the Android framework rewalking the wrapped subtree's accessibility nodes when importantForAccessibility flips back off on OverlayA11yShield. That rewalk lands on the same frame as react-native-teleport's reparent back of the message subtree and the closing portal hosts, and is visible as a fullscreen flicker. The gallery doesn't show it for two reasons: it doesn't use the teleport mechanism on close and its fullscreen overlay visually masks the same window.

The native rewalk isn't avoidable while the prop toggles - but most users don't need the prop to toggle at all. The trap exists to keep TalkBack / Switch Access / Voice Access focus inside the overlay; everyone else gains nothing from it and shouldn't pay for it.

🛠 Implementation details

Three layer gate on whether the shield's importantForAccessibility ever flips:

  1. Integrator optin (accessibility.enabled). Moved to the OverlayProvider call site so the shield isn't even mounted in the tree when the integrator hasn't opted into a11y. Apps that don't pass accessibility={{ enabled: true }} to OverlayProvider see zero overhead and no extra View, no hook subscriptions, no native prop ever touched. This naturally means that if we change the accessibility.enabled configuration on the fly we'll see a remount but we anyway do not recommend doing this nor is this something intended to go back and forth.
  2. Android a11y service running. The new useAccessibilityServiceEnabled hook at mirrors the shape of the existing useScreenReaderEnabled except it covers all services. Wraps Android's AccessibilityInfo.isAccessibilityServiceEnabled - which reports any a11y service, not just screen readers, so the trap covers TalkBack, Switch Access, Voice Access, Select to Speak, etc. RN does not emit a dedicated change event for this signal, so the hook repolls on screenReaderChanged and on AppState foreground transitions (the realistic path for a user toggling Accessibility settings and returning to the app).
  3. Overlay actually open. The gallery or the message context menu has to be the thing on screen.

All three are checked in the new private useShouldActivateTrap hook in OverlayA11yShield.tsx. The wrapper stays unconditionally mounted on Android (within the accessibility.enabled branch); only the importantForAccessibility prop toggles. Mounting the wrapper unconditionally was deliberate - gating the wrapper itself on per overlay state remounts the entire chat subtree on every menu open/close, which manifests as a visible chat tree reset. Also remounting when screen readers get enabled/disabled feels off, so for now we'll stick to this.

If we think about it we only need the trap for when a11y services are enabled, not particularly in the normal scenarios. Its sole purpose is anyway to not let your a11y cursor get away from the overlays as compensation for the lack of accessibilityViewIsModal as we have for iOS.

Also adds pointerEvents='none' on the wrapper for good measure so it can never intercept a touch from sibling overlays.

🎨 UI Changes

iOS
Before After
Android
Before After

🧪 Testing

☑️ Checklist

  • I have signed the Stream CLA (required)
  • PR targets the develop branch
  • Documentation is updated
  • New code is tested in main example apps, including all possible scenarios
    • SampleApp iOS and Android
    • Expo iOS and Android

@Stream-SDK-Bot

Copy link
Copy Markdown
Contributor

SDK Size

title develop branch diff status
js_bundle_size 1733 KB 1734 KB +1787 B 🔴

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants