Android enables Flutter semantics for any bound accessibility service, so a
TV running the Projectivy Launcher (whose service only wants foreground-app
events) paid for a full semantics tree on every frame that touched a node:
on an Android 14 box, 2 ms per frame, ~20 ms on each spotlight swap, and a
third of the navigation-time GC churn.
The Android side now reports whether any enabled service can consume the
tree (touch exploration, an accessibility tool, or spoken/braille/audible/
visual feedback; an empty list means UiAutomation, which reads it too), and
AssistiveTechnologyService closes a SemanticsTreeGate on the app binding
when none can. The gate sits under SemanticsBinding.semanticsEnabled, so the
pipeline owner drops the semantics owner exactly as if the platform had
turned accessibility off; explicit ensureSemantics clients (the debug handle
for Maestro) always win. Re-evaluated on platform toggles, service-list
changes (API 33+) and resume.
Cold navigation on the box with Projectivy's service bound, 30 D-pad steps
over three hubs: SEMANTICS phase 700-770 ms -> 0, UI frame max 31-37 ms ->
25 ms, GC 3.0 s -> 2.2-2.6 s.
On Linux the Home page, a library's Recommended tab and movie/show detail
pages could not be scrolled at all, while Browse, Collections, Playlists,
search, downloads and settings scrolled normally. Those three screens are
exactly the ones whose viewport is covered by horizontal hub rows.
A wheel, trackball, trackpoint or button-scroll event expresses one-axis
intent, but the host reports both axes in the same event: on Linux only
touchpad-sourced scrolling becomes a pan/zoom sequence that resolves in the
gesture arena, and everything else arrives as one PointerScrollEvent
carrying whatever dx the device produced. A Scrollable claims a scroll
signal as soon as the delta along its own axis is non-zero, and the deepest
claimant wins the PointerSignalResolver, so a tenth of a pixel of dx handed
the whole event to the horizontal row and the page behind it never moved.
Collapse a scroll signal onto its dominant axis in the binding, before hit
testing - the last point where the delta can still be corrected, because a
Scrollable has already registered with the resolver by the time it sees the
event. Pan/zoom events are left alone, so trackpad panning keeps both axes,
and a horizontal wheel or tilt still scrolls the row it points at.
close#2081