Skip to content

Commit 9b4bb20

Browse files
authored
makes android semanticsnode to ignore hittest if it is not focusable (flutter#22205)
1 parent d3182bc commit 9b4bb20

File tree

2 files changed

+70
-1
lines changed

2 files changed

+70
-1
lines changed

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,11 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
207207
// beneath a stylus or mouse cursor.
208208
@Nullable private SemanticsNode hoveredObject;
209209

210+
@VisibleForTesting
211+
public int getHoveredObjectId() {
212+
return hoveredObject.id;
213+
}
214+
210215
// A Java/Android cached representation of the Flutter app's navigation stack. The Flutter
211216
// navigation stack is tracked so that accessibility announcements can be made during Flutter's
212217
// navigation changes.
@@ -2180,7 +2185,7 @@ private SemanticsNode hitTest(float[] point) {
21802185
return result;
21812186
}
21822187
}
2183-
return this;
2188+
return isFocusable() ? this : null;
21842189
}
21852190

21862191
// TODO(goderbauer): This should be decided by the framework once we have more information

shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,70 @@ public void itAnnouncesRouteNameWhenAddingNewRoute() {
204204
assertEquals(sentences.get(0).toString(), "new_node2");
205205
}
206206

207+
@Test
208+
public void itIgnoresUnfocusableNodeDuringHitTest() {
209+
AccessibilityViewEmbedder mockViewEmbedder = mock(AccessibilityViewEmbedder.class);
210+
AccessibilityManager mockManager = mock(AccessibilityManager.class);
211+
View mockRootView = mock(View.class);
212+
Context context = mock(Context.class);
213+
when(mockRootView.getContext()).thenReturn(context);
214+
when(context.getPackageName()).thenReturn("test");
215+
AccessibilityBridge accessibilityBridge =
216+
setUpBridge(mockRootView, mockManager, mockViewEmbedder);
217+
ViewParent mockParent = mock(ViewParent.class);
218+
when(mockRootView.getParent()).thenReturn(mockParent);
219+
when(mockManager.isEnabled()).thenReturn(true);
220+
when(mockManager.isTouchExplorationEnabled()).thenReturn(true);
221+
222+
TestSemanticsNode root = new TestSemanticsNode();
223+
root.id = 0;
224+
root.left = 0;
225+
root.top = 0;
226+
root.bottom = 20;
227+
root.right = 20;
228+
TestSemanticsNode ignored = new TestSemanticsNode();
229+
ignored.id = 1;
230+
ignored.addFlag(AccessibilityBridge.Flag.SCOPES_ROUTE);
231+
ignored.left = 0;
232+
ignored.top = 0;
233+
ignored.bottom = 20;
234+
ignored.right = 20;
235+
root.children.add(ignored);
236+
TestSemanticsNode child = new TestSemanticsNode();
237+
child.id = 2;
238+
child.label = "label";
239+
child.left = 0;
240+
child.top = 0;
241+
child.bottom = 20;
242+
child.right = 20;
243+
root.children.add(child);
244+
TestSemanticsUpdate testSemanticsUpdate = root.toUpdate();
245+
accessibilityBridge.updateSemantics(testSemanticsUpdate.buffer, testSemanticsUpdate.strings);
246+
247+
ArgumentCaptor<AccessibilityEvent> eventCaptor =
248+
ArgumentCaptor.forClass(AccessibilityEvent.class);
249+
verify(mockParent, times(2))
250+
.requestSendAccessibilityEvent(eq(mockRootView), eventCaptor.capture());
251+
AccessibilityEvent event = eventCaptor.getAllValues().get(0);
252+
assertEquals(event.getEventType(), AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
253+
254+
// Synthesize an accessibility hit test event.
255+
MotionEvent mockEvent = mock(MotionEvent.class);
256+
when(mockEvent.getX()).thenReturn(10.0f);
257+
when(mockEvent.getY()).thenReturn(10.0f);
258+
when(mockEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
259+
boolean hit = accessibilityBridge.onAccessibilityHoverEvent(mockEvent);
260+
261+
assertEquals(hit, true);
262+
263+
eventCaptor = ArgumentCaptor.forClass(AccessibilityEvent.class);
264+
verify(mockParent, times(3))
265+
.requestSendAccessibilityEvent(eq(mockRootView), eventCaptor.capture());
266+
event = eventCaptor.getAllValues().get(2);
267+
assertEquals(event.getEventType(), AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
268+
assertEquals(accessibilityBridge.getHoveredObjectId(), 2);
269+
}
270+
207271
@Test
208272
public void itAnnouncesRouteNameWhenRemoveARoute() {
209273
AccessibilityViewEmbedder mockViewEmbedder = mock(AccessibilityViewEmbedder.class);

0 commit comments

Comments
 (0)