Skip to content

Commit 06635d3

Browse files
authored
Mirror Android platform views a11y tree in the Flutter a11y tree. (flutter#8237)
This PR mirrors virtual a11y tree of embedded platform views in the Flutter a11y tree. Non virtual hierarchies are not currently supported. Only works on Android versions earlier than Android P as it relies on reflection access to hidden system APIs which cannot be done starting Android P. A11y is not yet working as we also need to delegate a11y events from the platform view to the FlutterView. This will be done in a following PR to keep the change size a little saner.
1 parent 28433c5 commit 06635d3

File tree

6 files changed

+363
-5
lines changed

6 files changed

+363
-5
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/util/PathUtils.java
524524
FILE: ../../../flutter/shell/platform/android/io/flutter/util/Preconditions.java
525525
FILE: ../../../flutter/shell/platform/android/io/flutter/util/Predicate.java
526526
FILE: ../../../flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java
527+
FILE: ../../../flutter/shell/platform/android/io/flutter/view/AccessibilityViewEmbedder.java
527528
FILE: ../../../flutter/shell/platform/android/io/flutter/view/FlutterCallbackInformation.java
528529
FILE: ../../../flutter/shell/platform/android/io/flutter/view/FlutterMain.java
529530
FILE: ../../../flutter/shell/platform/android/io/flutter/view/FlutterNativeView.java

shell/platform/android/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ java_library("flutter_shell_java") {
166166
"io/flutter/util/Preconditions.java",
167167
"io/flutter/util/Predicate.java",
168168
"io/flutter/view/AccessibilityBridge.java",
169+
"io/flutter/view/AccessibilityViewEmbedder.java",
169170
"io/flutter/view/FlutterCallbackInformation.java",
170171
"io/flutter/view/FlutterMain.java",
171172
"io/flutter/view/FlutterNativeView.java",

shell/platform/android/io/flutter/plugin/platform/PlatformViewsAccessibilityDelegate.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,19 @@
44

55
package io.flutter.plugin.platform;
66

7+
import android.view.View;
78
import io.flutter.view.AccessibilityBridge;
89

910
/**
1011
* Facilitates interaction between the accessibility bridge and embedded platform views.
1112
*/
1213
public interface PlatformViewsAccessibilityDelegate {
13-
// TODO(amirh): add a View getViewById(int id) here.
14-
// not filing a tracking issue as this is going to be done in the next PR.
14+
15+
/**
16+
* Returns the root of the view hierarchy for the platform view with the requested id, or null if there is no
17+
* corresponding view.
18+
*/
19+
View getPlatformViewById(Integer id);
1520

1621
/**
1722
* Attaches an accessibility bridge for this platform views accessibility delegate.

shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,7 @@ public void onPreEngineRestart() {
120120
flushAllViews();
121121
}
122122

123-
/**
124-
* Returns the embedded view with id, or null if no view with this id is registered.
125-
*/
123+
@Override
126124
public View getPlatformViewById(Integer id) {
127125
VirtualDisplayController controller = vdControllers.get(id);
128126
if (controller == null) {

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,16 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
7575
private static final float SCROLL_POSITION_CAP_FOR_INFINITY = 70000.0f;
7676
private static final int ROOT_NODE_ID = 0;
7777

78+
// The minimal ID for an engine generated AccessibilityNodeInfo.
79+
//
80+
// The AccessibilityNodeInfo node IDs are generated by the framework for most Flutter semantic nodes.
81+
// When embedding platform views, the framework does not have the accessibility information for the embedded view;
82+
// in this case the engine generates AccessibilityNodeInfo that mirrors the a11y information exposed by the platform
83+
// view. To avoid the need of synchronizing the framework and engine mechanisms for generating the next ID, we split
84+
// the 32bit range of virtual node IDs into 2. The least significant 16 bits are used for framework generated IDs
85+
// and the most significant 16 bits are used for engine generated IDs.
86+
private static final int MIN_ENGINE_GENERATED_NODE_ID = 1<<16;
87+
7888
/// Value is derived from ACTION_TYPE_MASK in AccessibilityNodeInfo.java
7989
private static int FIRST_RESOURCE_ID = 267386881;
8090

@@ -92,6 +102,9 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
92102
@NonNull
93103
private final AccessibilityManager accessibilityManager;
94104

105+
@NonNull
106+
private final AccessibilityViewEmbedder accessibilityViewEmbedder;
107+
95108
// The delegate for interacting with embedded platform views. Used to embed accessibility data for an embedded
96109
// view in the accessibility tree.
97110
@NonNull
@@ -363,6 +376,7 @@ public void onTouchExplorationStateChanged(boolean isTouchExplorationEnabled) {
363376
if (platformViewsAccessibilityDelegate != null) {
364377
platformViewsAccessibilityDelegate.attachAccessibilityBridge(this);
365378
}
379+
accessibilityViewEmbedder = new AccessibilityViewEmbedder(rootAccessibilityView, MIN_ENGINE_GENERATED_NODE_ID);
366380
}
367381

368382
/**
@@ -456,6 +470,11 @@ private boolean shouldSetCollectionInfo(final SemanticsNode semanticsNode) {
456470
@Override
457471
@SuppressWarnings("deprecation")
458472
public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
473+
if (virtualViewId >= MIN_ENGINE_GENERATED_NODE_ID) {
474+
// The node is in the engine generated range, and is provided by the accessibility view embedder.
475+
return accessibilityViewEmbedder.createAccessibilityNodeInfo(virtualViewId);
476+
}
477+
459478
if (virtualViewId == View.NO_ID) {
460479
AccessibilityNodeInfo result = AccessibilityNodeInfo.obtain(rootAccessibilityView);
461480
rootAccessibilityView.onInitializeAccessibilityNodeInfo(result);
@@ -472,6 +491,13 @@ public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
472491
return null;
473492
}
474493

494+
if (semanticsNode.platformViewId != -1) {
495+
// For platform views we delegate the node creation to the accessibility view embedder.
496+
View embeddedView = platformViewsAccessibilityDelegate.getPlatformViewById(semanticsNode.platformViewId);
497+
Rect bounds = semanticsNode.getGlobalRect();
498+
return accessibilityViewEmbedder.getRootNode(embeddedView, semanticsNode.id, bounds);
499+
}
500+
475501
AccessibilityNodeInfo result = AccessibilityNodeInfo.obtain(rootAccessibilityView, virtualViewId);
476502
// Work around for https://github.com/flutter/flutter/issues/2101
477503
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {

0 commit comments

Comments
 (0)