@@ -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