Skip to content

Commit 37947f9

Browse files
authored
Allow delegation of a11y events from nodes that were not yet traversed (#8333)
The AccessibilityViewEmbedder was not delegating a11y events coming virtual nodes(of the embedded view) that were not previously traversed by the a11y framework. It turns out the a11y framework might leave parts of the tree untraversed in some circumstances, changed the behavior to create an originId<-->flutterId mapping on the fly when an event is delegated from a not previously traversed node. Fixes #30010.
1 parent deeb7a7 commit 37947f9

File tree

1 file changed

+26
-13
lines changed

1 file changed

+26
-13
lines changed

shell/platform/android/io/flutter/view/AccessibilityViewEmbedder.java

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,10 @@ class AccessibilityViewEmbedder {
5151
// Maps a platform view and originId to a corresponding flutterID.
5252
private final Map<ViewAndId, Integer> originToFlutterId;
5353

54-
// Maps the flutterId of an accessibility node to the screen bounds of
55-
// the root semantic node for the embedded view.
54+
// Maps an embedded view to it's screen bounds.
5655
// This is used to translate the coordinates of the accessibility node subtree to the main display's coordinate
5756
// system.
58-
private final SparseArray<Rect> flutterIdToDisplayBounds;
57+
private final Map<View, Rect> embeddedViewToDisplayBounds;
5958

6059
private int nextFlutterId;
6160

@@ -64,8 +63,8 @@ class AccessibilityViewEmbedder {
6463
flutterIdToOrigin = new SparseArray<>();
6564
this.rootAccessibilityView = rootAccessibiiltyView;
6665
nextFlutterId = firstVirtualNodeId;
67-
flutterIdToDisplayBounds = new SparseArray<>();
6866
originToFlutterId = new HashMap<>();
67+
embeddedViewToDisplayBounds = new HashMap<>();
6968
}
7069

7170
/**
@@ -80,10 +79,9 @@ public AccessibilityNodeInfo getRootNode(@NonNull View embeddedView, int flutter
8079
if (originPackedId == null) {
8180
return null;
8281
}
82+
embeddedViewToDisplayBounds.put(embeddedView, displayBounds);
8383
int originId = ReflectionAccessors.getVirtualNodeId(originPackedId);
84-
flutterIdToOrigin.put(flutterId, new ViewAndId(embeddedView, originId));
85-
flutterIdToDisplayBounds.put(flutterId, displayBounds);
86-
originToFlutterId.put(new ViewAndId(embeddedView, originId), flutterId);
84+
cacheVirtualIdMappings(embeddedView, originId, flutterId);
8785
return convertToFlutterNode(originNode, flutterId, embeddedView);
8886
}
8987

@@ -96,6 +94,13 @@ public AccessibilityNodeInfo createAccessibilityNodeInfo(int flutterId) {
9694
if (origin == null) {
9795
return null;
9896
}
97+
if (!embeddedViewToDisplayBounds.containsKey(origin.view)) {
98+
// This might happen if the embedded view is sending accessibility event before the first Flutter semantics
99+
// tree was sent to the accessibility bridge. In this case we don't return a node as we do not know the
100+
// bounds yet.
101+
// https://github.com/flutter/flutter/issues/30068
102+
return null;
103+
}
99104
AccessibilityNodeProvider provider = origin.view.getAccessibilityNodeProvider();
100105
if (provider == null) {
101106
// The provider is null for views that don't have a virtual accessibility tree.
@@ -127,7 +132,7 @@ private AccessibilityNodeInfo convertToFlutterNode(
127132
result.setSource(rootAccessibilityView, flutterId);
128133
result.setClassName(originNode.getClassName());
129134

130-
Rect displayBounds = flutterIdToDisplayBounds.get(flutterId);
135+
Rect displayBounds = embeddedViewToDisplayBounds.get(embeddedView);
131136

132137
copyAccessibilityFields(originNode, result);
133138
setFlutterNodesTranslateBounds(originNode, displayBounds, result);
@@ -172,14 +177,21 @@ private void addChildrenToFlutterNode(
172177
childFlutterId = originToFlutterId.get(origin);
173178
} else {
174179
childFlutterId = nextFlutterId++;
175-
originToFlutterId.put(origin, childFlutterId);
176-
flutterIdToOrigin.put(childFlutterId, origin);
177-
flutterIdToDisplayBounds.put(childFlutterId, displayBounds);
180+
cacheVirtualIdMappings(embeddedView, originId, childFlutterId);
178181
}
179182
resultNode.addChild(rootAccessibilityView, childFlutterId);
180183
}
181184
}
182185

186+
// Caches a bidirectional mapping of (embeddedView, originId)<-->flutterId.
187+
// Where originId is a virtual node ID in the embeddedView's tree, and flutterId is the ID
188+
// of the corresponding node in the Flutter virtual accessibility nodes tree.
189+
private void cacheVirtualIdMappings(@NonNull View embeddedView, int originId, int flutterId) {
190+
ViewAndId origin = new ViewAndId(embeddedView, originId);
191+
originToFlutterId.put(origin, flutterId);
192+
flutterIdToOrigin.put(flutterId, origin);
193+
}
194+
183195
private void setFlutterNodesTranslateBounds(
184196
@NonNull AccessibilityNodeInfo originNode,
185197
@NonNull Rect displayBounds,
@@ -265,7 +277,8 @@ public boolean requestSendAccessibilityEvent(
265277
int originVirtualId = ReflectionAccessors.getVirtualNodeId(originPackedId);
266278
Integer flutterId = originToFlutterId.get(new ViewAndId(embeddedView, originVirtualId));
267279
if (flutterId == null) {
268-
return false;
280+
flutterId = nextFlutterId++;
281+
cacheVirtualIdMappings(embeddedView, originVirtualId, flutterId);
269282
}
270283
translatedEvent.setSource(rootAccessibilityView, flutterId);
271284
translatedEvent.setClassName(event.getClassName());
@@ -333,7 +346,7 @@ public boolean onAccessibilityHoverEvent(int rootFlutterId, @NonNull MotionEvent
333346
if (origin == null) {
334347
return false;
335348
}
336-
Rect displayBounds = flutterIdToDisplayBounds.get(rootFlutterId);
349+
Rect displayBounds = embeddedViewToDisplayBounds.get(origin.view);
337350
int pointerCount = event.getPointerCount();
338351
MotionEvent.PointerProperties[] pointerProperties = new MotionEvent.PointerProperties[pointerCount];
339352
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[pointerCount];

0 commit comments

Comments
 (0)