Skip to content

Commit

Permalink
Mirror Android platform views a11y tree in the Flutter a11y tree. (fl…
Browse files Browse the repository at this point in the history
…utter#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.
  • Loading branch information
amirh authored Mar 21, 2019
1 parent 28433c5 commit 06635d3
Show file tree
Hide file tree
Showing 6 changed files with 363 additions and 5 deletions.
1 change: 1 addition & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/util/PathUtils.java
FILE: ../../../flutter/shell/platform/android/io/flutter/util/Preconditions.java
FILE: ../../../flutter/shell/platform/android/io/flutter/util/Predicate.java
FILE: ../../../flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java
FILE: ../../../flutter/shell/platform/android/io/flutter/view/AccessibilityViewEmbedder.java
FILE: ../../../flutter/shell/platform/android/io/flutter/view/FlutterCallbackInformation.java
FILE: ../../../flutter/shell/platform/android/io/flutter/view/FlutterMain.java
FILE: ../../../flutter/shell/platform/android/io/flutter/view/FlutterNativeView.java
Expand Down
1 change: 1 addition & 0 deletions shell/platform/android/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ java_library("flutter_shell_java") {
"io/flutter/util/Preconditions.java",
"io/flutter/util/Predicate.java",
"io/flutter/view/AccessibilityBridge.java",
"io/flutter/view/AccessibilityViewEmbedder.java",
"io/flutter/view/FlutterCallbackInformation.java",
"io/flutter/view/FlutterMain.java",
"io/flutter/view/FlutterNativeView.java",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@

package io.flutter.plugin.platform;

import android.view.View;
import io.flutter.view.AccessibilityBridge;

/**
* Facilitates interaction between the accessibility bridge and embedded platform views.
*/
public interface PlatformViewsAccessibilityDelegate {
// TODO(amirh): add a View getViewById(int id) here.
// not filing a tracking issue as this is going to be done in the next PR.

/**
* Returns the root of the view hierarchy for the platform view with the requested id, or null if there is no
* corresponding view.
*/
View getPlatformViewById(Integer id);

/**
* Attaches an accessibility bridge for this platform views accessibility delegate.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,7 @@ public void onPreEngineRestart() {
flushAllViews();
}

/**
* Returns the embedded view with id, or null if no view with this id is registered.
*/
@Override
public View getPlatformViewById(Integer id) {
VirtualDisplayController controller = vdControllers.get(id);
if (controller == null) {
Expand Down
26 changes: 26 additions & 0 deletions shell/platform/android/io/flutter/view/AccessibilityBridge.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,16 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
private static final float SCROLL_POSITION_CAP_FOR_INFINITY = 70000.0f;
private static final int ROOT_NODE_ID = 0;

// The minimal ID for an engine generated AccessibilityNodeInfo.
//
// The AccessibilityNodeInfo node IDs are generated by the framework for most Flutter semantic nodes.
// When embedding platform views, the framework does not have the accessibility information for the embedded view;
// in this case the engine generates AccessibilityNodeInfo that mirrors the a11y information exposed by the platform
// view. To avoid the need of synchronizing the framework and engine mechanisms for generating the next ID, we split
// the 32bit range of virtual node IDs into 2. The least significant 16 bits are used for framework generated IDs
// and the most significant 16 bits are used for engine generated IDs.
private static final int MIN_ENGINE_GENERATED_NODE_ID = 1<<16;

/// Value is derived from ACTION_TYPE_MASK in AccessibilityNodeInfo.java
private static int FIRST_RESOURCE_ID = 267386881;

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

@NonNull
private final AccessibilityViewEmbedder accessibilityViewEmbedder;

// The delegate for interacting with embedded platform views. Used to embed accessibility data for an embedded
// view in the accessibility tree.
@NonNull
Expand Down Expand Up @@ -363,6 +376,7 @@ public void onTouchExplorationStateChanged(boolean isTouchExplorationEnabled) {
if (platformViewsAccessibilityDelegate != null) {
platformViewsAccessibilityDelegate.attachAccessibilityBridge(this);
}
accessibilityViewEmbedder = new AccessibilityViewEmbedder(rootAccessibilityView, MIN_ENGINE_GENERATED_NODE_ID);
}

/**
Expand Down Expand Up @@ -456,6 +470,11 @@ private boolean shouldSetCollectionInfo(final SemanticsNode semanticsNode) {
@Override
@SuppressWarnings("deprecation")
public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
if (virtualViewId >= MIN_ENGINE_GENERATED_NODE_ID) {
// The node is in the engine generated range, and is provided by the accessibility view embedder.
return accessibilityViewEmbedder.createAccessibilityNodeInfo(virtualViewId);
}

if (virtualViewId == View.NO_ID) {
AccessibilityNodeInfo result = AccessibilityNodeInfo.obtain(rootAccessibilityView);
rootAccessibilityView.onInitializeAccessibilityNodeInfo(result);
Expand All @@ -472,6 +491,13 @@ public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
return null;
}

if (semanticsNode.platformViewId != -1) {
// For platform views we delegate the node creation to the accessibility view embedder.
View embeddedView = platformViewsAccessibilityDelegate.getPlatformViewById(semanticsNode.platformViewId);
Rect bounds = semanticsNode.getGlobalRect();
return accessibilityViewEmbedder.getRootNode(embeddedView, semanticsNode.id, bounds);
}

AccessibilityNodeInfo result = AccessibilityNodeInfo.obtain(rootAccessibilityView, virtualViewId);
// Work around for https://github.com/flutter/flutter/issues/2101
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
Expand Down
Loading

0 comments on commit 06635d3

Please sign in to comment.