Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 22 additions & 8 deletions shell/platform/android/io/flutter/view/AccessibilityBridge.java
Original file line number Diff line number Diff line change
Expand Up @@ -1566,7 +1566,7 @@ void updateSemantics(
if (lastAdded != null
&& (lastAdded.id != previousRouteId || newRoutes.size() != flutterNavigationStack.size())) {
previousRouteId = lastAdded.id;
sendWindowChangeEvent(lastAdded);
onWindowNameChange(lastAdded);
}
flutterNavigationStack.clear();
for (SemanticsNode semanticsNode : newRoutes) {
Expand Down Expand Up @@ -1778,15 +1778,17 @@ private void sendAccessibilityEvent(@NonNull AccessibilityEvent event) {
}

/**
* Creates a {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED} and sends the event to Android's
* accessibility system.
* Informs the TalkBack user about window name changes.
*
* <p>This method sets accessibility panel title if the API level >= 28, otherwise, it creates a
* {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED} and sends the event to Android's
* accessibility system. In both cases, TalkBack announces the label of the route and re-addjusts
* the accessibility focus.
*
* <p>The given {@code route} should be a {@link SemanticsNode} that represents a navigation route
* in the Flutter app.
*/
private void sendWindowChangeEvent(@NonNull SemanticsNode route) {
AccessibilityEvent event =
obtainAccessibilityEvent(route.id, AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
private void onWindowNameChange(@NonNull SemanticsNode route) {
String routeName = route.getRouteName();
if (routeName == null) {
// The routeName will be null when there is no semantics node that represnets namesRoute in
Expand All @@ -1799,8 +1801,20 @@ private void sendWindowChangeEvent(@NonNull SemanticsNode route) {
// next.
routeName = " ";
}
event.getText().add(routeName);
sendAccessibilityEvent(event);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
setAccessibilityPaneTitle(routeName);
} else {
AccessibilityEvent event =
obtainAccessibilityEvent(route.id, AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
event.getText().add(routeName);
sendAccessibilityEvent(event);
}
}

@TargetApi(28)
@RequiresApi(28)
Comment on lines +1814 to +1815
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@TargetApi(28)
@RequiresApi(28)
@TargetApi(Build.VERSION_CODES.P)
@RequiresApi(Build.VERSION_CODES.P)

private void setAccessibilityPaneTitle(String title) {
rootAccessibilityView.setAccessibilityPaneTitle(title);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,15 +210,7 @@ public void itAnnouncesRouteNameWhenAddingNewRoute() {
TestSemanticsUpdate testSemanticsUpdate = root.toUpdate();
testSemanticsUpdate.sendUpdateToBridge(accessibilityBridge);

ArgumentCaptor<AccessibilityEvent> eventCaptor =
ArgumentCaptor.forClass(AccessibilityEvent.class);
verify(mockParent, times(2))
.requestSendAccessibilityEvent(eq(mockRootView), eventCaptor.capture());
AccessibilityEvent event = eventCaptor.getAllValues().get(0);
assertEquals(event.getEventType(), AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
List<CharSequence> sentences = event.getText();
assertEquals(sentences.size(), 1);
assertEquals(sentences.get(0).toString(), "node1");
verify(mockRootView, times(1)).setAccessibilityPaneTitle(eq("node1"));

TestSemanticsNode new_root = new TestSemanticsNode();
new_root.id = 0;
Expand All @@ -237,14 +229,7 @@ public void itAnnouncesRouteNameWhenAddingNewRoute() {
testSemanticsUpdate = new_root.toUpdate();
testSemanticsUpdate.sendUpdateToBridge(accessibilityBridge);

eventCaptor = ArgumentCaptor.forClass(AccessibilityEvent.class);
verify(mockParent, times(4))
.requestSendAccessibilityEvent(eq(mockRootView), eventCaptor.capture());
event = eventCaptor.getAllValues().get(2);
assertEquals(event.getEventType(), AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
sentences = event.getText();
assertEquals(sentences.size(), 1);
assertEquals(sentences.get(0).toString(), "new_node2");
verify(mockRootView, times(1)).setAccessibilityPaneTitle(eq("new_node2"));
}

@Test
Expand Down Expand Up @@ -517,12 +502,7 @@ public void itIgnoresUnfocusableNodeDuringHitTest() {
TestSemanticsUpdate testSemanticsUpdate = root.toUpdate();
testSemanticsUpdate.sendUpdateToBridge(accessibilityBridge);

ArgumentCaptor<AccessibilityEvent> eventCaptor =
ArgumentCaptor.forClass(AccessibilityEvent.class);
verify(mockParent, times(2))
.requestSendAccessibilityEvent(eq(mockRootView), eventCaptor.capture());
AccessibilityEvent event = eventCaptor.getAllValues().get(0);
assertEquals(event.getEventType(), AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
verify(mockRootView, times(1)).setAccessibilityPaneTitle(eq(" "));

// Synthesize an accessibility hit test event.
MotionEvent mockEvent = mock(MotionEvent.class);
Expand All @@ -533,10 +513,11 @@ public void itIgnoresUnfocusableNodeDuringHitTest() {

assertEquals(hit, true);

eventCaptor = ArgumentCaptor.forClass(AccessibilityEvent.class);
verify(mockParent, times(3))
ArgumentCaptor<AccessibilityEvent> eventCaptor =
ArgumentCaptor.forClass(AccessibilityEvent.class);
verify(mockParent, times(2))
.requestSendAccessibilityEvent(eq(mockRootView), eventCaptor.capture());
event = eventCaptor.getAllValues().get(2);
AccessibilityEvent event = eventCaptor.getAllValues().get(1);
assertEquals(event.getEventType(), AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
assertEquals(accessibilityBridge.getHoveredObjectId(), 2);
}
Expand Down Expand Up @@ -572,15 +553,7 @@ public void itAnnouncesRouteNameWhenRemoveARoute() {
TestSemanticsUpdate testSemanticsUpdate = root.toUpdate();
testSemanticsUpdate.sendUpdateToBridge(accessibilityBridge);

ArgumentCaptor<AccessibilityEvent> eventCaptor =
ArgumentCaptor.forClass(AccessibilityEvent.class);
verify(mockParent, times(2))
.requestSendAccessibilityEvent(eq(mockRootView), eventCaptor.capture());
AccessibilityEvent event = eventCaptor.getAllValues().get(0);
assertEquals(event.getEventType(), AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
List<CharSequence> sentences = event.getText();
assertEquals(sentences.size(), 1);
assertEquals(sentences.get(0).toString(), "node2");
verify(mockRootView, times(1)).setAccessibilityPaneTitle(eq("node2"));

TestSemanticsNode new_root = new TestSemanticsNode();
new_root.id = 0;
Expand All @@ -597,14 +570,7 @@ public void itAnnouncesRouteNameWhenRemoveARoute() {
testSemanticsUpdate = new_root.toUpdate();
testSemanticsUpdate.sendUpdateToBridge(accessibilityBridge);

eventCaptor = ArgumentCaptor.forClass(AccessibilityEvent.class);
verify(mockParent, times(4))
.requestSendAccessibilityEvent(eq(mockRootView), eventCaptor.capture());
event = eventCaptor.getAllValues().get(2);
assertEquals(event.getEventType(), AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
sentences = event.getText();
assertEquals(sentences.size(), 1);
assertEquals(sentences.get(0).toString(), "new_node2");
verify(mockRootView, times(1)).setAccessibilityPaneTitle(eq("new_node2"));
}

@TargetApi(21)
Expand Down Expand Up @@ -1087,15 +1053,7 @@ public void itAnnouncesWhiteSpaceWhenNoNamesRoute() {
TestSemanticsUpdate testSemanticsUpdate = root.toUpdate();
testSemanticsUpdate.sendUpdateToBridge(accessibilityBridge);

ArgumentCaptor<AccessibilityEvent> eventCaptor =
ArgumentCaptor.forClass(AccessibilityEvent.class);
verify(mockParent, times(2))
.requestSendAccessibilityEvent(eq(mockRootView), eventCaptor.capture());
AccessibilityEvent event = eventCaptor.getAllValues().get(0);
assertEquals(event.getEventType(), AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
List<CharSequence> sentences = event.getText();
assertEquals(sentences.size(), 1);
assertEquals(sentences.get(0).toString(), " ");
verify(mockRootView, times(1)).setAccessibilityPaneTitle(eq(" "));
}

@Test
Expand Down