From 3393072a1890f53df8aaac7045e890ba3a64d015 Mon Sep 17 00:00:00 2001 From: eggfly Date: Wed, 21 Apr 2021 00:37:25 +0800 Subject: [PATCH] Fix: add Android accessibility bounds offset when FlutterView's location is not left top corner (#25670) --- .../io/flutter/view/AccessibilityBridge.java | 17 ++++++++- .../flutter/view/AccessibilityBridgeTest.java | 36 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/shell/platform/android/io/flutter/view/AccessibilityBridge.java b/shell/platform/android/io/flutter/view/AccessibilityBridge.java index 9616d444ecbf4..7ac46d7a14797 100644 --- a/shell/platform/android/io/flutter/view/AccessibilityBridge.java +++ b/shell/platform/android/io/flutter/view/AccessibilityBridge.java @@ -693,7 +693,8 @@ public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) { } else { result.setBoundsInParent(bounds); } - result.setBoundsInScreen(bounds); + final Rect boundsInScreen = getBoundsInScreen(bounds); + result.setBoundsInScreen(boundsInScreen); result.setVisibleToUser(true); result.setEnabled( !semanticsNode.hasFlag(Flag.HAS_ENABLED_STATE) || semanticsNode.hasFlag(Flag.IS_ENABLED)); @@ -869,6 +870,20 @@ && shouldSetCollectionInfo(semanticsNode)) { return result; } + /** + * Get the bounds in screen with root FlutterView's offset. + * + * @param bounds the bounds in FlutterView + * @return the bounds with offset + */ + private Rect getBoundsInScreen(Rect bounds) { + Rect boundsInScreen = new Rect(bounds); + int[] locationOnScreen = new int[2]; + rootAccessibilityView.getLocationOnScreen(locationOnScreen); + boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]); + return boundsInScreen; + } + /** * Instructs the view represented by {@code virtualViewId} to carry out the desired {@code * accessibilityAction}, perhaps configured by additional {@code arguments}. diff --git a/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java b/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java index ca9c01d2c1509..3c50399c7605e 100644 --- a/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java +++ b/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java @@ -10,6 +10,7 @@ import static org.mockito.Matchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; @@ -76,6 +77,41 @@ public void itDescribesTextFieldsWithText() { assertEquals(nodeInfo.getText(), "Hello, World"); } + @Test + public void itTakesGlobalCoordinatesOfFlutterViewIntoAccount() { + AccessibilityViewEmbedder mockViewEmbedder = mock(AccessibilityViewEmbedder.class); + AccessibilityManager mockManager = mock(AccessibilityManager.class); + View mockRootView = mock(View.class); + Context context = mock(Context.class); + when(mockRootView.getContext()).thenReturn(context); + final int position = 88; + // The getBoundsInScreen() in createAccessibilityNodeInfo() needs View.getLocationOnScreen() + doAnswer( + invocation -> { + int[] outLocation = (int[]) invocation.getArguments()[0]; + outLocation[0] = position; + outLocation[1] = position; + return null; + }) + .when(mockRootView) + .getLocationOnScreen(any(int[].class)); + + when(context.getPackageName()).thenReturn("test"); + AccessibilityBridge accessibilityBridge = + setUpBridge(mockRootView, mockManager, mockViewEmbedder); + + TestSemanticsNode testSemanticsNode = new TestSemanticsNode(); + TestSemanticsUpdate testSemanticsUpdate = testSemanticsNode.toUpdate(); + + accessibilityBridge.updateSemantics(testSemanticsUpdate.buffer, testSemanticsUpdate.strings); + AccessibilityNodeInfo nodeInfo = accessibilityBridge.createAccessibilityNodeInfo(0); + + Rect outBoundsInScreen = new Rect(); + nodeInfo.getBoundsInScreen(outBoundsInScreen); + assertEquals(position, outBoundsInScreen.left); + assertEquals(position, outBoundsInScreen.top); + } + @Test public void itDoesNotContainADescriptionIfScopesRoute() { AccessibilityBridge accessibilityBridge = setUpBridge();