From 340d6c67fd0268dc1aadaca09a01c7538f92faf2 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 13 Jun 2022 16:13:52 -0700 Subject: [PATCH 1/9] Fix TalkBack in non-virtual a11y hierarchies --- .../mutatorsstack/FlutterMutatorView.java | 9 ++++++ .../plugin/platform/PlatformViewWrapper.java | 9 ++++++ .../platform/PlatformViewsController.java | 30 +++++++++++++++++-- 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java b/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java index 9be312cc53baf..287aa6d4033f3 100644 --- a/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java +++ b/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java @@ -10,6 +10,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewTreeObserver; +import android.view.accessibility.AccessibilityEvent; import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -160,6 +161,14 @@ public boolean onInterceptTouchEvent(MotionEvent event) { return true; } + @Override + public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) { + if (getChildCount() == 1 && getChildAt(0).isImportantForAccessibility()) { + return super.requestSendAccessibilityEvent(child, event); + } + return false; + } + @Override @SuppressLint("ClickableViewAccessibility") public boolean onTouchEvent(MotionEvent event) { diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java index d21782181c454..e98addb59de2a 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java @@ -21,6 +21,7 @@ import android.view.View; import android.view.ViewParent; import android.view.ViewTreeObserver; +import android.view.accessibility.AccessibilityEvent; import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -242,6 +243,14 @@ public boolean onInterceptTouchEvent(@NonNull MotionEvent event) { return true; } + @Override + public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) { + if (getChildCount() == 1 && getChildAt(0).isImportantForAccessibility()) { + return super.requestSendAccessibilityEvent(child, event); + } + return false; + } + /** Used on Android O+, {@link invalidateChildInParent} used for previous versions. */ @SuppressLint("NewApi") @Override diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index 80e270821ed19..c9fbfd8769487 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -233,6 +233,18 @@ public long createForTextureLayer( } view.setLayoutParams(new FrameLayout.LayoutParams(physicalWidth, physicalHeight)); view.setLayoutDirection(request.direction); + + // Accessibility is initially disabled, and it's e-enabled by AccessibilityBridge after + // the + // framework populates the SemanticsNode. + // If there's no SemanticsNode for a platform view, then the platform view remains + // inaccessible to TalkBack. + // For example, if you wrap a platform view widget with a ExcludeSemantics widget, no + // SemanticsNode is populated. + // To prevent races, the framework populate the SemanticsNode after the platform view has + // been created. + view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); + wrapperView.addView(view); wrapperView.setOnDescendantFocusChangeListener( (v, hasFocus) -> { @@ -747,11 +759,12 @@ void initializePlatformViewIfNeeded(int viewId) { if (platformViewParent.get(viewId) != null) { return; } - if (platformView.getView() == null) { + final View embeddedView = platformView.getView(); + if (embeddedView == null) { throw new IllegalStateException( "PlatformView#getView() returned null, but an Android view reference was expected."); } - if (platformView.getView().getParent() != null) { + if (embeddedView.getParent() != null) { throw new IllegalStateException( "The Android view returned from PlatformView#getView() was already added to a parent view."); } @@ -769,7 +782,18 @@ void initializePlatformViewIfNeeded(int viewId) { }); platformViewParent.put(viewId, parentView); - parentView.addView(platformView.getView()); + + // Accessibility is initially disabled, and it's e-enabled by AccessibilityBridge after the + // framework populates the SemanticsNode. + // If there's no SemanticsNode for a platform view, then the platform view remains inaccessible + // to TalkBack. + // For example, if you wrap a platform view widget with a ExcludeSemantics widget, no + // SemanticsNode is populated. + // To prevent races, the framework populate the SemanticsNode after the platform view has been + // created. + embeddedView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); + + parentView.addView(embeddedView); flutterView.addView(parentView); } From f2b987e28c4f134046d9f451c2cd538e18d794ca Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 13 Jun 2022 16:22:32 -0700 Subject: [PATCH 2/9] view -> embedded view --- .../plugin/platform/PlatformViewsController.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index c9fbfd8769487..3e3c6a6eb4e75 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -223,16 +223,16 @@ public long createForTextureLayer( layoutParams.leftMargin = physicalLeft; wrapperView.setLayoutParams(layoutParams); - final View view = platformView.getView(); - if (view == null) { + final View embeddedView = platformView.getView(); + if (embeddedView == null) { throw new IllegalStateException( "PlatformView#getView() returned null, but an Android view reference was expected."); } else if (view.getParent() != null) { throw new IllegalStateException( "The Android view returned from PlatformView#getView() was already added to a parent view."); } - view.setLayoutParams(new FrameLayout.LayoutParams(physicalWidth, physicalHeight)); - view.setLayoutDirection(request.direction); + embeddedView.setLayoutParams(new FrameLayout.LayoutParams(physicalWidth, physicalHeight)); + embeddedView.setLayoutDirection(request.direction); // Accessibility is initially disabled, and it's e-enabled by AccessibilityBridge after // the @@ -243,9 +243,10 @@ public long createForTextureLayer( // SemanticsNode is populated. // To prevent races, the framework populate the SemanticsNode after the platform view has // been created. - view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); + embeddedView.setImportantForAccessibility( + View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); - wrapperView.addView(view); + wrapperView.addView(embeddedView); wrapperView.setOnDescendantFocusChangeListener( (v, hasFocus) -> { if (hasFocus) { From 228aa67d3f78cb8cd90890dc2b16c0ebb8d82920 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 13 Jun 2022 16:25:08 -0700 Subject: [PATCH 3/9] format --- .../io/flutter/plugin/platform/PlatformViewsController.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index 3e3c6a6eb4e75..24f51df363dc9 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -235,8 +235,7 @@ public long createForTextureLayer( embeddedView.setLayoutDirection(request.direction); // Accessibility is initially disabled, and it's e-enabled by AccessibilityBridge after - // the - // framework populates the SemanticsNode. + // the framework populates the SemanticsNode. // If there's no SemanticsNode for a platform view, then the platform view remains // inaccessible to TalkBack. // For example, if you wrap a platform view widget with a ExcludeSemantics widget, no From abb6a9159934fcb3255f5293ece61e29ead4845f Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 13 Jun 2022 17:35:59 -0700 Subject: [PATCH 4/9] simplify --- .../embedding/engine/mutatorsstack/FlutterMutatorView.java | 6 +++++- .../io/flutter/plugin/platform/PlatformViewWrapper.java | 6 +++++- .../io/flutter/plugin/platform/PlatformViewsController.java | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java b/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java index 287aa6d4033f3..d056712298ef1 100644 --- a/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java +++ b/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java @@ -163,7 +163,11 @@ public boolean onInterceptTouchEvent(MotionEvent event) { @Override public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) { - if (getChildCount() == 1 && getChildAt(0).isImportantForAccessibility()) { + if (child != null && child.isImportantForAccessibility()) { + // Forward the request only if the child view is in the Flutter accessibility tree. + // The embedded view may be ignored when the framework doesn't populate a SemanticNode + // for the current platform view. + // See AccessibilityBridge for more. return super.requestSendAccessibilityEvent(child, event); } return false; diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java index e98addb59de2a..83ca667966563 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java @@ -245,7 +245,11 @@ public boolean onInterceptTouchEvent(@NonNull MotionEvent event) { @Override public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) { - if (getChildCount() == 1 && getChildAt(0).isImportantForAccessibility()) { + if (child != null && child.isImportantForAccessibility()) { + // Forward the request only if the child view is in the Flutter accessibility tree. + // The embedded view may be ignored when the framework doesn't populate a SemanticNode + // for the current platform view. + // See AccessibilityBridge for more. return super.requestSendAccessibilityEvent(child, event); } return false; diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index 24f51df363dc9..80d77f9fb8a0e 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -227,7 +227,7 @@ public long createForTextureLayer( if (embeddedView == null) { throw new IllegalStateException( "PlatformView#getView() returned null, but an Android view reference was expected."); - } else if (view.getParent() != null) { + } else if (embeddedView.getParent() != null) { throw new IllegalStateException( "The Android view returned from PlatformView#getView() was already added to a parent view."); } From d6ad3cc6a96cf5fa2402612ad21e6b2df4e8179c Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 13 Jun 2022 19:16:35 -0700 Subject: [PATCH 5/9] support for API level 19 --- .../mutatorsstack/FlutterMutatorView.java | 19 ++++--- .../plugin/platform/PlatformViewWrapper.java | 22 +++++--- .../platform/PlatformViewsController.java | 1 + .../mutatorsstack/FlutterMutatorViewTest.java | 55 +++++++++++++++++++ .../platform/PlatformViewWrapperTest.java | 53 ++++++++++++++++++ 5 files changed, 136 insertions(+), 14 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java b/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java index d056712298ef1..ed76185969106 100644 --- a/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java +++ b/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java @@ -3,6 +3,7 @@ import static android.view.View.OnFocusChangeListener; import android.annotation.SuppressLint; +import android.annotation.TargetApi; import android.content.Context; import android.graphics.Canvas; import android.graphics.Matrix; @@ -162,15 +163,19 @@ public boolean onInterceptTouchEvent(MotionEvent event) { } @Override + @TargetApi(21) public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) { - if (child != null && child.isImportantForAccessibility()) { - // Forward the request only if the child view is in the Flutter accessibility tree. - // The embedded view may be ignored when the framework doesn't populate a SemanticNode - // for the current platform view. - // See AccessibilityBridge for more. - return super.requestSendAccessibilityEvent(child, event); + final View embeddedView = getChildAt(0); + if (embeddedView != null + && embeddedView.getImportantForAccessibility() + == View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) { + return false; } - return false; + // Forward the request only if the embedded view is in the Flutter accessibility tree. + // The embedded view may be ignored when the framework doesn't populate a SemanticNode + // for the current platform view. + // See AccessibilityBridge for more. + return super.requestSendAccessibilityEvent(child, event); } @Override diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java index 83ca667966563..7887d6b21fa58 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java @@ -245,14 +245,22 @@ public boolean onInterceptTouchEvent(@NonNull MotionEvent event) { @Override public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) { - if (child != null && child.isImportantForAccessibility()) { - // Forward the request only if the child view is in the Flutter accessibility tree. - // The embedded view may be ignored when the framework doesn't populate a SemanticNode - // for the current platform view. - // See AccessibilityBridge for more. - return super.requestSendAccessibilityEvent(child, event); + final View embeddedView = getChildAt(0); + if (embeddedView != null) { + io.flutter.Log.e( + "flutter", "getImportantForAccessibility=" + embeddedView.getImportantForAccessibility()); } - return false; + + if (embeddedView != null + && embeddedView.getImportantForAccessibility() + == View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) { + return false; + } + // Forward the request only if the embedded view is in the Flutter accessibility tree. + // The embedded view may be ignored when the framework doesn't populate a SemanticNode + // for the current platform view. + // See AccessibilityBridge for more. + return super.requestSendAccessibilityEvent(child, event); } /** Used on Android O+, {@link invalidateChildInParent} used for previous versions. */ diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index 80d77f9fb8a0e..ff24a694c7aeb 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -750,6 +750,7 @@ private void initializeRootImageViewIfNeeded() { * testing. */ @VisibleForTesting + @TargetApi(Build.VERSION_CODES.KITKAT) void initializePlatformViewIfNeeded(int viewId) { final PlatformView platformView = platformViews.get(viewId); if (platformView == null) { diff --git a/shell/platform/android/test/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorViewTest.java b/shell/platform/android/test/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorViewTest.java index 03624c1b509f2..2f3091f9baa4c 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorViewTest.java @@ -7,7 +7,10 @@ import android.content.Context; import android.graphics.Matrix; import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; import android.view.ViewTreeObserver; +import android.view.accessibility.AccessibilityEvent; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import io.flutter.embedding.android.AndroidTouchProcessor; @@ -15,6 +18,8 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.robolectric.annotation.Config; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; @Config(manifest = Config.NONE) @RunWith(AndroidJUnit4.class) @@ -206,4 +211,54 @@ public ViewTreeObserver getViewTreeObserver() { view.unsetOnDescendantFocusChangeListener(); verify(viewTreeObserver, times(1)).removeOnGlobalFocusChangeListener(activeFocusListener); } + + @Test + @Config( + shadows = { + ShadowViewGroup.class, + }) + public void ignoreAccessibilityEvents() { + final FlutterMutatorView wrapperView = new FlutterMutatorView(ctx); + + final View embeddedView = mock(View.class); + wrapperView.addView(embeddedView); + + when(embeddedView.getImportantForAccessibility()) + .thenReturn(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); + final boolean eventSent = + wrapperView.requestSendAccessibilityEvent(embeddedView, mock(AccessibilityEvent.class)); + assertFalse(eventSent); + } + + @Test + @Config( + shadows = { + ShadowViewGroup.class, + }) + public void sendAccessibilityEvents() { + final FlutterMutatorView wrapperView = new FlutterMutatorView(ctx); + + final View embeddedView = mock(View.class); + wrapperView.addView(embeddedView); + + when(embeddedView.getImportantForAccessibility()) + .thenReturn(View.IMPORTANT_FOR_ACCESSIBILITY_YES); + boolean eventSent = + wrapperView.requestSendAccessibilityEvent(embeddedView, mock(AccessibilityEvent.class)); + assertTrue(eventSent); + + when(embeddedView.getImportantForAccessibility()) + .thenReturn(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO); + eventSent = + wrapperView.requestSendAccessibilityEvent(embeddedView, mock(AccessibilityEvent.class)); + assertTrue(eventSent); + } + + @Implements(ViewGroup.class) + public static class ShadowViewGroup extends org.robolectric.shadows.ShadowView { + @Implementation + public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) { + return true; + } + } } diff --git a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewWrapperTest.java b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewWrapperTest.java index f1dffb24aa6c1..2893df22834f5 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewWrapperTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewWrapperTest.java @@ -13,7 +13,9 @@ import android.graphics.SurfaceTexture; import android.view.Surface; import android.view.View; +import android.view.ViewGroup; import android.view.ViewTreeObserver; +import android.view.accessibility.AccessibilityEvent; import androidx.annotation.NonNull; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -21,6 +23,7 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.robolectric.annotation.Config; +import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; @TargetApi(31) @@ -295,6 +298,56 @@ public ViewTreeObserver getViewTreeObserver() { verify(viewTreeObserver, times(1)).removeOnGlobalFocusChangeListener(activeFocusListener); } + @Test + @Config( + shadows = { + ShadowViewGroup.class, + }) + public void ignoreAccessibilityEvents() { + final PlatformViewWrapper wrapperView = new PlatformViewWrapper(ctx); + + final View embeddedView = mock(View.class); + wrapperView.addView(embeddedView); + + when(embeddedView.getImportantForAccessibility()) + .thenReturn(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); + final boolean eventSent = + wrapperView.requestSendAccessibilityEvent(embeddedView, mock(AccessibilityEvent.class)); + assertFalse(eventSent); + } + + @Test + @Config( + shadows = { + ShadowViewGroup.class, + }) + public void sendAccessibilityEvents() { + final PlatformViewWrapper wrapperView = new PlatformViewWrapper(ctx); + + final View embeddedView = mock(View.class); + wrapperView.addView(embeddedView); + + when(embeddedView.getImportantForAccessibility()) + .thenReturn(View.IMPORTANT_FOR_ACCESSIBILITY_YES); + boolean eventSent = + wrapperView.requestSendAccessibilityEvent(embeddedView, mock(AccessibilityEvent.class)); + assertTrue(eventSent); + + when(embeddedView.getImportantForAccessibility()) + .thenReturn(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO); + eventSent = + wrapperView.requestSendAccessibilityEvent(embeddedView, mock(AccessibilityEvent.class)); + assertTrue(eventSent); + } + @Implements(View.class) public static class ShadowView {} + + @Implements(ViewGroup.class) + public static class ShadowViewGroup extends org.robolectric.shadows.ShadowView { + @Implementation + public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) { + return true; + } + } } From e9c1f3104e18c87b32820eede9dcf79f210e7ab8 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 13 Jun 2022 19:17:51 -0700 Subject: [PATCH 6/9] Move TargetAPI --- .../embedding/engine/mutatorsstack/FlutterMutatorView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java b/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java index ed76185969106..c195b28728e9b 100644 --- a/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java +++ b/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java @@ -23,6 +23,7 @@ * A view that applies the {@link io.flutter.embedding.engine.mutatorsstack.FlutterMutatorsStack} to * its children. */ +@TargetApi(19) public class FlutterMutatorView extends FrameLayout { private FlutterMutatorsStack mutatorsStack; private float screenDensity; @@ -163,7 +164,6 @@ public boolean onInterceptTouchEvent(MotionEvent event) { } @Override - @TargetApi(21) public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) { final View embeddedView = getChildAt(0); if (embeddedView != null From 6cc5133b658a52c70129d01968e5e48103083266 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 13 Jun 2022 19:27:00 -0700 Subject: [PATCH 7/9] one more test --- .../platform/PlatformViewsControllerTest.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java index 0b6e4b8edd8e3..d80e59186998b 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java @@ -211,6 +211,33 @@ public void createPlatformViewMessage__setsAndroidViewSize() { assertEquals(capturedLayoutParams.get(0).height, 1); } + @Test + @Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class}) + public void createPlatformViewMessage__disablesAccessibility() { + PlatformViewsController platformViewsController = new PlatformViewsController(); + platformViewsController.setSoftwareRendering(true); + + int platformViewId = 0; + assertNull(platformViewsController.getPlatformViewById(platformViewId)); + + PlatformViewFactory viewFactory = mock(PlatformViewFactory.class); + PlatformView platformView = mock(PlatformView.class); + + View androidView = mock(View.class); + when(platformView.getView()).thenReturn(androidView); + when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView); + platformViewsController.getRegistry().registerViewFactory("testType", viewFactory); + + FlutterJNI jni = new FlutterJNI(); + attach(jni, platformViewsController); + + // Simulate create call from the framework. + createPlatformView( + jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ false); + verify(androidView, times(1)) + .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); + } + @Test @Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class}) public void createPlatformViewMessage__throwsIfViewIsNull() { From db670432e8127acc857de783054289e24e033ce8 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 13 Jun 2022 19:30:42 -0700 Subject: [PATCH 8/9] remove log --- .../io/flutter/plugin/platform/PlatformViewWrapper.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java index 7887d6b21fa58..c57d90f0162c1 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java @@ -246,11 +246,6 @@ public boolean onInterceptTouchEvent(@NonNull MotionEvent event) { @Override public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) { final View embeddedView = getChildAt(0); - if (embeddedView != null) { - io.flutter.Log.e( - "flutter", "getImportantForAccessibility=" + embeddedView.getImportantForAccessibility()); - } - if (embeddedView != null && embeddedView.getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) { From 4ab0dc28f7a12020771ba08f3fda382911a2656d Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Tue, 14 Jun 2022 10:51:25 -0700 Subject: [PATCH 9/9] comment --- .../platform/PlatformViewsController.java | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index ff24a694c7aeb..8008da1a77b28 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -234,12 +234,11 @@ public long createForTextureLayer( embeddedView.setLayoutParams(new FrameLayout.LayoutParams(physicalWidth, physicalHeight)); embeddedView.setLayoutDirection(request.direction); - // Accessibility is initially disabled, and it's e-enabled by AccessibilityBridge after - // the framework populates the SemanticsNode. - // If there's no SemanticsNode for a platform view, then the platform view remains - // inaccessible to TalkBack. - // For example, if you wrap a platform view widget with a ExcludeSemantics widget, no - // SemanticsNode is populated. + // Accessibility in the embedded view is initially disabled because if a Flutter app + // disabled accessibility in the first frame, the embedding won't receive an update to + // disable accessibility since the embedding never received an update to enable it. + // The AccessibilityBridge keeps track of the accessibility nodes, and handles the deltas + // when the framework sends a new a11y tree to the embedding. // To prevent races, the framework populate the SemanticsNode after the platform view has // been created. embeddedView.setImportantForAccessibility( @@ -784,12 +783,11 @@ void initializePlatformViewIfNeeded(int viewId) { platformViewParent.put(viewId, parentView); - // Accessibility is initially disabled, and it's e-enabled by AccessibilityBridge after the - // framework populates the SemanticsNode. - // If there's no SemanticsNode for a platform view, then the platform view remains inaccessible - // to TalkBack. - // For example, if you wrap a platform view widget with a ExcludeSemantics widget, no - // SemanticsNode is populated. + // Accessibility in the embedded view is initially disabled because if a Flutter app disabled + // accessibility in the first frame, the embedding won't receive an update to disable + // accessibility since the embedding never received an update to enable it. + // The AccessibilityBridge keeps track of the accessibility nodes, and handles the deltas when + // the framework sends a new a11y tree to the embedding. // To prevent races, the framework populate the SemanticsNode after the platform view has been // created. embeddedView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);