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
Show all changes
34 commits
Select commit Hold shift + click to select a range
4858609
Update AccessibilityBridge.java
hannah-hyj May 4, 2023
45b8b13
Update AccessibilityBridge.java
hannah-hyj May 5, 2023
7860617
Update AccessibilityChannelTest.java
hannah-hyj May 9, 2023
7d44ee2
Update accessibility_bridge_test.mm
hannah-hyj May 10, 2023
ec80ed3
test
hannah-hyj May 11, 2023
72803e1
Update accessibility_bridge.mm
hannah-hyj May 11, 2023
7218a2f
Update AccessibilityChannelTest.java
hannah-hyj May 11, 2023
fbde877
comment
hannah-hyj May 11, 2023
6d2eb06
Update AccessibilityChannel.java
hannah-hyj May 11, 2023
b7628cb
test
hannah-hyj May 12, 2023
804ee5b
Update AccessibilityBridgeTest.java
hannah-hyj May 12, 2023
1e45108
Update AccessibilityBridgeTest.java
hannah-hyj May 12, 2023
052e7fa
Update AccessibilityBridgeTest.java
hannah-hyj May 12, 2023
b9456ca
Update AccessibilityChannel.java
hannah-hyj May 12, 2023
9da2c8c
Update AccessibilityChannel.java
hannah-hyj May 12, 2023
82cf3e2
Update AccessibilityBridgeTest.java
hannah-hyj May 12, 2023
f3a3b27
Update AccessibilityBridgeTest.java
hannah-hyj May 12, 2023
958c1af
Update AccessibilityBridgeTest.java
hannah-hyj May 12, 2023
6a12f87
Update AccessibilityBridgeTest.java
hannah-hyj May 12, 2023
a9167ba
Update AccessibilityBridgeTest.java
hannah-hyj May 17, 2023
e0ab00a
Update AccessibilityBridgeTest.java
hannah-hyj May 18, 2023
156ebca
Update AccessibilityBridgeTest.java
hannah-hyj May 18, 2023
7f829e8
Update AccessibilityBridgeTest.java
hannah-hyj May 18, 2023
f18b6e1
Update AccessibilityBridgeTest.java
hannah-hyj May 18, 2023
423d556
Update AccessibilityBridgeTest.java
hannah-hyj May 18, 2023
dd5191e
Update AccessibilityBridgeTest.java
hannah-hyj May 18, 2023
3a8bb9e
Update AccessibilityBridgeTest.java
hannah-hyj May 18, 2023
242aeba
update tests
hannah-hyj May 30, 2023
73fb81a
lint
hannah-hyj May 30, 2023
751180d
test
hannah-hyj May 30, 2023
bf97dba
update tests
hannah-hyj May 30, 2023
78a0708
lint
hannah-hyj May 30, 2023
8e85983
Update AccessibilityBridge.java
hannah-hyj May 30, 2023
2e06b28
Update accessibility_bridge_test.mm
hannah-hyj May 30, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import io.flutter.Log;
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.embedding.engine.dart.DartExecutor;
Expand All @@ -24,8 +23,7 @@ public class AccessibilityChannel {
@NonNull public final FlutterJNI flutterJNI;
@Nullable private AccessibilityMessageHandler handler;

@VisibleForTesting
final BasicMessageChannel.MessageHandler<Object> parsingMessageHandler =
public final BasicMessageChannel.MessageHandler<Object> parsingMessageHandler =
new BasicMessageChannel.MessageHandler<Object>() {
@Override
public void onMessage(
Expand Down Expand Up @@ -67,6 +65,14 @@ public void onMessage(
}
break;
}
case "focus":
{
Integer nodeId = (Integer) annotatedEvent.get("nodeId");
if (nodeId != null) {
handler.onFocus(nodeId);
}
break;
}
case "tooltip":
{
String tooltipMessage = (String) data.get("message");
Expand Down Expand Up @@ -170,12 +176,15 @@ public interface AccessibilityMessageHandler extends FlutterJNI.AccessibilityDel
/** The Dart application would like the given {@code message} to be announced. */
void announce(@NonNull String message);

/** The user has tapped on the widget with the given {@code nodeId}. */
/** The user has tapped on the semantics node with the given {@code nodeId}. */
void onTap(int nodeId);

/** The user has long pressed on the widget with the given {@code nodeId}. */
/** The user has long pressed on the semantics node with the given {@code nodeId}. */
void onLongPress(int nodeId);

/** The framework has requested focus on the semantics node with the given {@code nodeId}. */
void onFocus(int nodeId);

/** The user has opened a tooltip. */
void onTooltip(@NonNull String message);
}
Expand Down
16 changes: 14 additions & 2 deletions shell/platform/android/io/flutter/view/AccessibilityBridge.java
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,12 @@ public void onLongPress(int nodeId) {
sendAccessibilityEvent(nodeId, AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
}

/** The framework has requested focus on the given {@code nodeId}. */
@Override
public void onFocus(int nodeId) {
sendAccessibilityEvent(nodeId, AccessibilityEvent.TYPE_VIEW_FOCUSED);
}

/** The user has opened a tooltip. */
@Override
public void onTooltip(@NonNull String message) {
Expand Down Expand Up @@ -1883,7 +1889,8 @@ private AccessibilityEvent createTextChangedEvent(int id, String oldValue, Strin
* <p>The given {@code viewId} may either belong to {@link #rootAccessibilityView}, or any Flutter
* {@link SemanticsNode}.
*/
private void sendAccessibilityEvent(int viewId, int eventType) {
@VisibleForTesting
public void sendAccessibilityEvent(int viewId, int eventType) {
if (!accessibilityManager.isEnabled()) {
return;
}
Expand Down Expand Up @@ -1976,12 +1983,17 @@ private void sendWindowContentChangeEvent(int virtualViewId) {
* invoked to create an {@link AccessibilityEvent} for the {@link #rootAccessibilityView}.
*/
private AccessibilityEvent obtainAccessibilityEvent(int virtualViewId, int eventType) {
AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
AccessibilityEvent event = obtainAccessibilityEvent(eventType);
event.setPackageName(rootAccessibilityView.getContext().getPackageName());
event.setSource(rootAccessibilityView, virtualViewId);
return event;
}

@VisibleForTesting
public AccessibilityEvent obtainAccessibilityEvent(int eventType) {
return AccessibilityEvent.obtain(eventType);
}

/**
* Reads the {@code layoutInDisplayCutoutMode} value from the window attribute and returns whether
* a left cutout inset is required.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.plugin.common.BasicMessageChannel;
import java.util.HashMap;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Test;
Expand All @@ -30,4 +31,19 @@ public void repliesWhenNoAccessibilityHandler() throws JSONException {
accessibilityChannel.parsingMessageHandler.onMessage(arguments, reply);
verify(reply).reply(null);
}

@Test
public void handleFocus() throws JSONException {
AccessibilityChannel accessibilityChannel =
new AccessibilityChannel(mock(DartExecutor.class), mock(FlutterJNI.class));
HashMap<String, Object> arguments = new HashMap<>();
arguments.put("type", "focus");
arguments.put("nodeId", 123);
AccessibilityChannel.AccessibilityMessageHandler handler =
mock(AccessibilityChannel.AccessibilityMessageHandler.class);
accessibilityChannel.setAccessibilityMessageHandler(handler);
BasicMessageChannel.Reply reply = mock(BasicMessageChannel.Reply.class);
accessibilityChannel.parsingMessageHandler.onMessage(arguments, reply);
verify(handler).onFocus(123);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,16 @@
import android.view.accessibility.AccessibilityNodeInfo;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.embedding.engine.systemchannels.AccessibilityChannel;
import io.flutter.plugin.common.BasicMessageChannel;
import io.flutter.plugin.platform.PlatformViewsAccessibilityDelegate;
import io.flutter.view.AccessibilityBridge.Flag;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
Expand Down Expand Up @@ -1827,6 +1831,66 @@ public void releaseDropsChannelMessageHandler() {
verify(mockChannel, never()).setAccessibilityFeatures(anyInt());
}

@Test
public void sendFocusAccessibilityEvent() {
AccessibilityManager mockManager = mock(AccessibilityManager.class);
AccessibilityChannel accessibilityChannel =
new AccessibilityChannel(mock(DartExecutor.class), mock(FlutterJNI.class));

ContentResolver mockContentResolver = mock(ContentResolver.class);
View mockRootView = mock(View.class);
Context context = mock(Context.class);
when(mockRootView.getContext()).thenReturn(context);
when(context.getPackageName()).thenReturn("test");
ViewParent mockParent = mock(ViewParent.class);
when(mockRootView.getParent()).thenReturn(mockParent);
when(mockManager.isEnabled()).thenReturn(true);

AccessibilityBridge accessibilityBridge =
setUpBridge(mockRootView, accessibilityChannel, mockManager, null, null, null);

HashMap<String, Object> arguments = new HashMap<>();
arguments.put("type", "focus");
arguments.put("nodeId", 123);
BasicMessageChannel.Reply reply = mock(BasicMessageChannel.Reply.class);
accessibilityChannel.parsingMessageHandler.onMessage(arguments, reply);

// Check that focus event was sent.
ArgumentCaptor<AccessibilityEvent> eventCaptor =
ArgumentCaptor.forClass(AccessibilityEvent.class);
verify(mockParent).requestSendAccessibilityEvent(eq(mockRootView), eventCaptor.capture());
AccessibilityEvent event = eventCaptor.getAllValues().get(0);
assertEquals(event.getEventType(), AccessibilityEvent.TYPE_VIEW_FOCUSED);
Copy link
Contributor

Choose a reason for hiding this comment

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

also check for id?

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with this!

Copy link
Member Author

Choose a reason for hiding this comment

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

I tested nodeId in AccessibilityChannelTest.java
And there is no public api to get id from AccessibilityEvent.

Copy link
Contributor

@chunhtai chunhtai May 25, 2023

Choose a reason for hiding this comment

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

This seems to not be addressed yet

per our offline discussion we talked previously, we can create a mocked event spy on the setSource

assertEquals(event.getSource(), null);
}

@Test
public void SetSourceAndPackageNameForAccessibilityEvent() {
AccessibilityManager mockManager = mock(AccessibilityManager.class);
ContentResolver mockContentResolver = mock(ContentResolver.class);
View mockRootView = mock(View.class);
Context context = mock(Context.class);
when(mockRootView.getContext()).thenReturn(context);
when(context.getPackageName()).thenReturn("test");
when(mockManager.isEnabled()).thenReturn(true);
ViewParent mockParent = mock(ViewParent.class);
when(mockRootView.getParent()).thenReturn(mockParent);
AccessibilityEvent mockEvent = mock(AccessibilityEvent.class);

AccessibilityBridge accessibilityBridge =
setUpBridge(mockRootView, null, mockManager, null, null, null);

AccessibilityBridge spyAccessibilityBridge = spy(accessibilityBridge);

when(spyAccessibilityBridge.obtainAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED))
.thenReturn(mockEvent);

spyAccessibilityBridge.sendAccessibilityEvent(123, AccessibilityEvent.TYPE_VIEW_FOCUSED);

verify(mockEvent).setPackageName("test");
verify(mockEvent).setSource(eq(mockRootView), eq(123));
}

AccessibilityBridge setUpBridge() {
return setUpBridge(null, null, null, null, null, null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class AccessibilityBridge final : public AccessibilityBridgeIos {

void UpdateSemantics(flutter::SemanticsNodeUpdates nodes,
const flutter::CustomAccessibilityActionUpdates& actions);
void HandleEvent(NSDictionary<NSString*, id>* annotatedEvent);
void DispatchSemanticsAction(int32_t id, flutter::SemanticsAction action) override;
void DispatchSemanticsAction(int32_t id,
flutter::SemanticsAction action,
Expand Down Expand Up @@ -88,7 +89,6 @@ class AccessibilityBridge final : public AccessibilityBridgeIos {
SemanticsObject* FindFirstFocusable(SemanticsObject* parent);
void VisitObjectsRecursivelyAndRemove(SemanticsObject* object,
NSMutableArray<NSNumber*>* doomed_uids);
void HandleEvent(NSDictionary<NSString*, id>* annotatedEvent);

FlutterViewController* view_controller_;
PlatformViewIOS* platform_view_;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,10 @@ static bool DidFlagChange(const flutter::SemanticsNode& oldNode,
NSString* message = annotatedEvent[@"data"][@"message"];
ios_delegate_->PostAccessibilityNotification(UIAccessibilityAnnouncementNotification, message);
}
if ([type isEqualToString:@"focus"]) {
SemanticsObject* node = objects_.get()[annotatedEvent[@"nodeId"]];
ios_delegate_->PostAccessibilityNotification(UIAccessibilityLayoutChangedNotification, node);
Copy link
Contributor

Choose a reason for hiding this comment

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

What happens if node is nil? Seems like nil is an OK arg, but just double checking

Copy link
Member Author

Choose a reason for hiding this comment

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

Tested in my device, if node is nil, nothing will happen, the focus will not be changed

}
}

fml::WeakPtr<AccessibilityBridge> AccessibilityBridge::GetWeakPtr() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1289,6 +1289,50 @@ - (void)testAnnouncesRouteChangesRemoveRouteInMiddle {
UIAccessibilityScreenChangedNotification);
}

- (void)testHandleEvent {
flutter::MockDelegate mock_delegate;
auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
/*platform=*/thread_task_runner,
/*raster=*/thread_task_runner,
/*ui=*/thread_task_runner,
/*io=*/thread_task_runner);
auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
/*delegate=*/mock_delegate,
/*rendering_api=*/flutter::IOSRenderingAPI::kSoftware,
/*platform_views_controller=*/nil,
/*task_runners=*/runners,
/*worker_task_runner=*/nil,
/*is_gpu_disabled_sync_switch=*/nil);
id mockFlutterView = OCMClassMock([FlutterView class]);
id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);

NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
[[[NSMutableArray alloc] init] autorelease];
auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
ios_delegate->on_PostAccessibilityNotification_ =
[accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
[accessibility_notifications addObject:@{
@"notification" : @(notification),
@"argument" : argument ? argument : [NSNull null],
}];
};
__block auto bridge =
std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
/*platform_view=*/platform_view.get(),
/*platform_views_controller=*/nil,
/*ios_delegate=*/std::move(ios_delegate));

NSDictionary<NSString*, id>* annotatedEvent = @{@"type" : @"focus", @"nodeId" : @123};

bridge->HandleEvent(annotatedEvent);

XCTAssertEqual([accessibility_notifications count], 1ul);
XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue],
UIAccessibilityLayoutChangedNotification);
}

- (void)testAnnouncesRouteChangesWhenNoNamesRoute {
flutter::MockDelegate mock_delegate;
auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
Expand Down