diff --git a/shell/platform/android/io/flutter/view/AccessibilityViewEmbedder.java b/shell/platform/android/io/flutter/view/AccessibilityViewEmbedder.java index 330ded624ddc0..fbc2260d260e0 100644 --- a/shell/platform/android/io/flutter/view/AccessibilityViewEmbedder.java +++ b/shell/platform/android/io/flutter/view/AccessibilityViewEmbedder.java @@ -51,11 +51,10 @@ class AccessibilityViewEmbedder { // Maps a platform view and originId to a corresponding flutterID. private final Map originToFlutterId; - // Maps the flutterId of an accessibility node to the screen bounds of - // the root semantic node for the embedded view. + // Maps an embedded view to it's screen bounds. // This is used to translate the coordinates of the accessibility node subtree to the main display's coordinate // system. - private final SparseArray flutterIdToDisplayBounds; + private final Map embeddedViewToDisplayBounds; private int nextFlutterId; @@ -64,8 +63,8 @@ class AccessibilityViewEmbedder { flutterIdToOrigin = new SparseArray<>(); this.rootAccessibilityView = rootAccessibiiltyView; nextFlutterId = firstVirtualNodeId; - flutterIdToDisplayBounds = new SparseArray<>(); originToFlutterId = new HashMap<>(); + embeddedViewToDisplayBounds = new HashMap<>(); } /** @@ -80,10 +79,9 @@ public AccessibilityNodeInfo getRootNode(@NonNull View embeddedView, int flutter if (originPackedId == null) { return null; } + embeddedViewToDisplayBounds.put(embeddedView, displayBounds); int originId = ReflectionAccessors.getVirtualNodeId(originPackedId); - flutterIdToOrigin.put(flutterId, new ViewAndId(embeddedView, originId)); - flutterIdToDisplayBounds.put(flutterId, displayBounds); - originToFlutterId.put(new ViewAndId(embeddedView, originId), flutterId); + cacheVirtualIdMappings(embeddedView, originId, flutterId); return convertToFlutterNode(originNode, flutterId, embeddedView); } @@ -96,6 +94,13 @@ public AccessibilityNodeInfo createAccessibilityNodeInfo(int flutterId) { if (origin == null) { return null; } + if (!embeddedViewToDisplayBounds.containsKey(origin.view)) { + // This might happen if the embedded view is sending accessibility event before the first Flutter semantics + // tree was sent to the accessibility bridge. In this case we don't return a node as we do not know the + // bounds yet. + // https://github.com/flutter/flutter/issues/30068 + return null; + } AccessibilityNodeProvider provider = origin.view.getAccessibilityNodeProvider(); if (provider == null) { // The provider is null for views that don't have a virtual accessibility tree. @@ -127,7 +132,7 @@ private AccessibilityNodeInfo convertToFlutterNode( result.setSource(rootAccessibilityView, flutterId); result.setClassName(originNode.getClassName()); - Rect displayBounds = flutterIdToDisplayBounds.get(flutterId); + Rect displayBounds = embeddedViewToDisplayBounds.get(embeddedView); copyAccessibilityFields(originNode, result); setFlutterNodesTranslateBounds(originNode, displayBounds, result); @@ -172,14 +177,21 @@ private void addChildrenToFlutterNode( childFlutterId = originToFlutterId.get(origin); } else { childFlutterId = nextFlutterId++; - originToFlutterId.put(origin, childFlutterId); - flutterIdToOrigin.put(childFlutterId, origin); - flutterIdToDisplayBounds.put(childFlutterId, displayBounds); + cacheVirtualIdMappings(embeddedView, originId, childFlutterId); } resultNode.addChild(rootAccessibilityView, childFlutterId); } } + // Caches a bidirectional mapping of (embeddedView, originId)<-->flutterId. + // Where originId is a virtual node ID in the embeddedView's tree, and flutterId is the ID + // of the corresponding node in the Flutter virtual accessibility nodes tree. + private void cacheVirtualIdMappings(@NonNull View embeddedView, int originId, int flutterId) { + ViewAndId origin = new ViewAndId(embeddedView, originId); + originToFlutterId.put(origin, flutterId); + flutterIdToOrigin.put(flutterId, origin); + } + private void setFlutterNodesTranslateBounds( @NonNull AccessibilityNodeInfo originNode, @NonNull Rect displayBounds, @@ -265,7 +277,8 @@ public boolean requestSendAccessibilityEvent( int originVirtualId = ReflectionAccessors.getVirtualNodeId(originPackedId); Integer flutterId = originToFlutterId.get(new ViewAndId(embeddedView, originVirtualId)); if (flutterId == null) { - return false; + flutterId = nextFlutterId++; + cacheVirtualIdMappings(embeddedView, originVirtualId, flutterId); } translatedEvent.setSource(rootAccessibilityView, flutterId); translatedEvent.setClassName(event.getClassName()); @@ -333,7 +346,7 @@ public boolean onAccessibilityHoverEvent(int rootFlutterId, @NonNull MotionEvent if (origin == null) { return false; } - Rect displayBounds = flutterIdToDisplayBounds.get(rootFlutterId); + Rect displayBounds = embeddedViewToDisplayBounds.get(origin.view); int pointerCount = event.getPointerCount(); MotionEvent.PointerProperties[] pointerProperties = new MotionEvent.PointerProperties[pointerCount]; MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[pointerCount];