Flutter Engine
flutter::AccessibilityBridge Class Referencefinal

#include <accessibility_bridge.h>

Inheritance diagram for flutter::AccessibilityBridge:
flutter::AccessibilityBridgeIos

Classes

class  IosDelegate
 

Public Member Functions

 AccessibilityBridge (FlutterViewController *view_controller, PlatformViewIOS *platform_view, std::shared_ptr< FlutterPlatformViewsController > platform_views_controller, std::unique_ptr< IosDelegate > ios_delegate=nullptr)
 
 ~AccessibilityBridge ()
 
void UpdateSemantics (flutter::SemanticsNodeUpdates nodes, flutter::CustomAccessibilityActionUpdates actions)
 
void DispatchSemanticsAction (int32_t id, flutter::SemanticsAction action) override
 
void DispatchSemanticsAction (int32_t id, flutter::SemanticsAction action, std::vector< uint8_t > args) override
 
void AccessibilityObjectDidBecomeFocused (int32_t id) override
 
void AccessibilityObjectDidLoseFocus (int32_t id) override
 
UIView< UITextInput > * textInputView () override
 
UIView * view () const override
 
fml::WeakPtr< AccessibilityBridgeGetWeakPtr ()
 
std::shared_ptr< FlutterPlatformViewsControllerGetPlatformViewsController () const override
 
void clearState ()
 
- Public Member Functions inherited from flutter::AccessibilityBridgeIos
virtual ~AccessibilityBridgeIos ()=default
 

Detailed Description

An accessibility instance is bound to one FlutterViewController and FlutterView instance.

It helps populate the UIView's accessibilityElements property from Flutter's semantics nodes.

Definition at line 38 of file accessibility_bridge.h.

Constructor & Destructor Documentation

◆ AccessibilityBridge()

flutter::AccessibilityBridge::AccessibilityBridge ( FlutterViewController view_controller,
PlatformViewIOS platform_view,
std::shared_ptr< FlutterPlatformViewsController platform_views_controller,
std::unique_ptr< IosDelegate ios_delegate = nullptr 
)

Definition at line 39 of file accessibility_bridge.mm.

References FlutterReply, fml::scoped_nsprotocol< NST >::get(), and fml::scoped_nsprotocol< NST >::reset().

44  : view_controller_(view_controller),
45  platform_view_(platform_view),
46  platform_views_controller_(platform_views_controller),
47  last_focused_semantics_object_id_(kSemanticObjectIdInvalid),
48  objects_([[NSMutableDictionary alloc] init]),
49  weak_factory_(this),
50  previous_route_id_(0),
51  previous_routes_({}),
52  ios_delegate_(ios_delegate ? std::move(ios_delegate)
53  : std::make_unique<DefaultIosDelegate>()) {
54  accessibility_channel_.reset([[FlutterBasicMessageChannel alloc]
55  initWithName:@"flutter/accessibility"
56  binaryMessenger:platform_view->GetOwnerViewController().get().engine.binaryMessenger
57  codec:[FlutterStandardMessageCodec sharedInstance]]);
58  [accessibility_channel_.get() setMessageHandler:^(id message, FlutterReply reply) {
59  HandleEvent((NSDictionary*)message);
60  }];
61 }
void reset(NST object=nil)
NS_ASSUME_NONNULL_BEGIN typedef void(^ FlutterReply)(id _Nullable reply)

◆ ~AccessibilityBridge()

flutter::AccessibilityBridge::~AccessibilityBridge ( )

Definition at line 63 of file accessibility_bridge.mm.

References clearState(), fml::scoped_nsprotocol< NST >::get(), and flutter::FlutterViewController::view().

63  {
64  [accessibility_channel_.get() setMessageHandler:nil];
65  clearState();
66  view_controller_.view.accessibilityElements = nil;
67 }

Member Function Documentation

◆ AccessibilityObjectDidBecomeFocused()

void flutter::AccessibilityBridge::AccessibilityObjectDidBecomeFocused ( int32_t  id)
overridevirtual

A callback that is called when a SemanticObject receives focus.

The input id is the uid of the newly focused SemanticObject.

Implements flutter::AccessibilityBridgeIos.

Definition at line 73 of file accessibility_bridge.mm.

References id.

73  {
74  last_focused_semantics_object_id_ = id;
75 }
int32_t id

◆ AccessibilityObjectDidLoseFocus()

void flutter::AccessibilityBridge::AccessibilityObjectDidLoseFocus ( int32_t  id)
overridevirtual

A callback that is called when a SemanticObject loses focus

The input id is the uid of the newly focused SemanticObject.

Implements flutter::AccessibilityBridgeIos.

Definition at line 77 of file accessibility_bridge.mm.

77  {
78  if (last_focused_semantics_object_id_ == id) {
79  last_focused_semantics_object_id_ = kSemanticObjectIdInvalid;
80  }
81 }

◆ clearState()

void flutter::AccessibilityBridge::clearState ( )

Definition at line 352 of file accessibility_bridge.mm.

Referenced by GetPlatformViewsController(), and ~AccessibilityBridge().

352  {
353  [objects_ removeAllObjects];
354  previous_route_id_ = 0;
355  previous_routes_.clear();
356 }

◆ DispatchSemanticsAction() [1/2]

void flutter::AccessibilityBridge::DispatchSemanticsAction ( int32_t  id,
flutter::SemanticsAction  action 
)
overridevirtual

Implements flutter::AccessibilityBridgeIos.

Definition at line 245 of file accessibility_bridge.mm.

References flutter::PlatformView::DispatchSemanticsAction().

245  {
246  platform_view_->DispatchSemanticsAction(uid, action, {});
247 }
SemanticsAction action
void DispatchSemanticsAction(int32_t id, SemanticsAction action, std::vector< uint8_t > args)
Used by embedders to dispatch an accessibility action to a running isolate hosted by the engine...

◆ DispatchSemanticsAction() [2/2]

void flutter::AccessibilityBridge::DispatchSemanticsAction ( int32_t  id,
flutter::SemanticsAction  action,
std::vector< uint8_t >  args 
)
overridevirtual

Implements flutter::AccessibilityBridgeIos.

Definition at line 249 of file accessibility_bridge.mm.

References flutter::PlatformView::DispatchSemanticsAction().

251  {
252  platform_view_->DispatchSemanticsAction(uid, action, std::move(args));
253 }
G_BEGIN_DECLS FlValue * args
SemanticsAction action
void DispatchSemanticsAction(int32_t id, SemanticsAction action, std::vector< uint8_t > args)
Used by embedders to dispatch an accessibility action to a running isolate hosted by the engine...

◆ GetPlatformViewsController()

std::shared_ptr<FlutterPlatformViewsController> flutter::AccessibilityBridge::GetPlatformViewsController ( ) const
inlineoverridevirtual

Implements flutter::AccessibilityBridgeIos.

Definition at line 73 of file accessibility_bridge.h.

References clearState(), and FML_DISALLOW_COPY_AND_ASSIGN.

Referenced by UpdateSemantics().

73  {
74  return platform_views_controller_;
75  };

◆ GetWeakPtr()

fml::WeakPtr< AccessibilityBridge > flutter::AccessibilityBridge::GetWeakPtr ( )

Definition at line 348 of file accessibility_bridge.mm.

Referenced by flutter::DidFlagChange(), and view().

348  {
349  return weak_factory_.GetWeakPtr();
350 }

◆ textInputView()

UIView< UITextInput > * flutter::AccessibilityBridge::textInputView ( )
overridevirtual

Implements flutter::AccessibilityBridgeIos.

Definition at line 69 of file accessibility_bridge.mm.

References flutter::PlatformViewIOS::GetOwnerViewController(), and textInputPlugin.

69  {
70  return [[platform_view_->GetOwnerViewController().get().engine textInputPlugin] textInputView];
71 }
FlutterTextInputPlugin * textInputPlugin
UIView< UITextInput > * textInputView() override
fml::WeakPtr< FlutterViewController > GetOwnerViewController() const

◆ UpdateSemantics()

void flutter::AccessibilityBridge::UpdateSemantics ( flutter::SemanticsNodeUpdates  nodes,
flutter::CustomAccessibilityActionUpdates  actions 
)

Definition at line 83 of file accessibility_bridge.mm.

References action, flutter::SemanticsNode::childrenInTraversalOrder, flutter::SemanticsNode::customAccessibilityActions, fml::scoped_nsprotocol< NST >::get(), GetPlatformViewsController(), flutter::CustomAccessibilityAction::id, flutter::SemanticsNode::id, flutter::SemanticsNode::IsPlatformViewNode(), kRootNodeId, flutter::CustomAccessibilityAction::label, flutter::CustomAccessibilityAction::overrideId, FlutterCustomAccessibilityAction::uid, and flutter::FlutterViewController::view().

84  {
85  BOOL layoutChanged = NO;
86  BOOL scrollOccured = NO;
87  BOOL needsAnnouncement = NO;
88  for (const auto& entry : actions) {
89  const flutter::CustomAccessibilityAction& action = entry.second;
90  actions_[action.id] = action;
91  }
92  for (const auto& entry : nodes) {
93  const flutter::SemanticsNode& node = entry.second;
94  SemanticsObject* object = GetOrCreateObject(node.id, nodes);
95  layoutChanged = layoutChanged || [object nodeWillCauseLayoutChange:&node];
96  scrollOccured = scrollOccured || [object nodeWillCauseScroll:&node];
97  needsAnnouncement = [object nodeShouldTriggerAnnouncement:&node];
98  [object setSemanticsNode:&node];
99  NSUInteger newChildCount = node.childrenInTraversalOrder.size();
100  NSMutableArray* newChildren =
101  [[[NSMutableArray alloc] initWithCapacity:newChildCount] autorelease];
102  for (NSUInteger i = 0; i < newChildCount; ++i) {
103  SemanticsObject* child = GetOrCreateObject(node.childrenInTraversalOrder[i], nodes);
104  [newChildren addObject:child];
105  }
106  object.children = newChildren;
107  if (node.customAccessibilityActions.size() > 0) {
108  NSMutableArray<FlutterCustomAccessibilityAction*>* accessibilityCustomActions =
109  [[[NSMutableArray alloc] init] autorelease];
110  for (int32_t action_id : node.customAccessibilityActions) {
111  flutter::CustomAccessibilityAction& action = actions_[action_id];
112  if (action.overrideId != -1) {
113  // iOS does not support overriding standard actions, so we ignore any
114  // custom actions that have an override id provided.
115  continue;
116  }
117  NSString* label = @(action.label.data());
118  SEL selector = @selector(onCustomAccessibilityAction:);
119  FlutterCustomAccessibilityAction* customAction =
120  [[[FlutterCustomAccessibilityAction alloc] initWithName:label
121  target:object
122  selector:selector] autorelease];
123  customAction.uid = action_id;
124  [accessibilityCustomActions addObject:customAction];
125  }
126  object.accessibilityCustomActions = accessibilityCustomActions;
127  }
128 
129  if (object.node.IsPlatformViewNode()) {
130  auto controller = GetPlatformViewsController();
131  if (controller) {
132  object.platformViewSemanticsContainer = [[[FlutterPlatformViewSemanticsContainer alloc]
133  initWithSemanticsObject:object] autorelease];
134  }
135  } else if (object.platformViewSemanticsContainer) {
136  object.platformViewSemanticsContainer = nil;
137  }
138  if (needsAnnouncement) {
139  // Try to be more polite - iOS 11+ supports
140  // UIAccessibilitySpeechAttributeQueueAnnouncement which should avoid
141  // interrupting system notifications or other elements.
142  // Expectation: roughly match the behavior of polite announcements on
143  // Android.
144  NSString* announcement =
145  [[[NSString alloc] initWithUTF8String:object.node.label.c_str()] autorelease];
146  if (@available(iOS 11.0, *)) {
147  UIAccessibilityPostNotification(
148  UIAccessibilityAnnouncementNotification,
149  [[[NSAttributedString alloc]
150  initWithString:announcement
151  attributes:@{
152  UIAccessibilitySpeechAttributeQueueAnnouncement : @YES
153  }] autorelease]);
154  } else {
155  UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, announcement);
156  }
157  }
158  }
159 
160  SemanticsObject* root = objects_.get()[@(kRootNodeId)];
161 
162  bool routeChanged = false;
163  SemanticsObject* lastAdded = nil;
164 
165  if (root) {
166  if (!view_controller_.view.accessibilityElements) {
167  view_controller_.view.accessibilityElements = @[ [root accessibilityContainer] ];
168  }
169  NSMutableArray<SemanticsObject*>* newRoutes = [[[NSMutableArray alloc] init] autorelease];
170  [root collectRoutes:newRoutes];
171  // Finds the last route that is not in the previous routes.
172  for (SemanticsObject* route in newRoutes) {
173  if (std::find(previous_routes_.begin(), previous_routes_.end(), [route uid]) ==
174  previous_routes_.end()) {
175  lastAdded = route;
176  }
177  }
178  // If all the routes are in the previous route, get the last route.
179  if (lastAdded == nil && [newRoutes count] > 0) {
180  int index = [newRoutes count] - 1;
181  lastAdded = [newRoutes objectAtIndex:index];
182  }
183  // There are two cases if lastAdded != nil
184  // 1. lastAdded is not in previous routes. In this case,
185  // [lastAdded uid] != previous_route_id_
186  // 2. All new routes are in previous routes and
187  // lastAdded = newRoutes.last.
188  // In the first case, we need to announce new route. In the second case,
189  // we need to announce if one list is shorter than the other.
190  if (lastAdded != nil &&
191  ([lastAdded uid] != previous_route_id_ || [newRoutes count] != previous_routes_.size())) {
192  previous_route_id_ = [lastAdded uid];
193  routeChanged = true;
194  }
195  previous_routes_.clear();
196  for (SemanticsObject* route in newRoutes) {
197  previous_routes_.push_back([route uid]);
198  }
199  } else {
200  view_controller_.view.accessibilityElements = nil;
201  }
202 
203  NSMutableArray<NSNumber*>* doomed_uids = [NSMutableArray arrayWithArray:[objects_.get() allKeys]];
204  if (root)
205  VisitObjectsRecursivelyAndRemove(root, doomed_uids);
206  [objects_ removeObjectsForKeys:doomed_uids];
207 
208  layoutChanged = layoutChanged || [doomed_uids count] > 0;
209  // We should send out only one notification per semantics update.
210  if (routeChanged) {
211  if (!ios_delegate_->IsFlutterViewControllerPresentingModalViewController(view_controller_)) {
212  NSString* routeName = [lastAdded routeName];
213  ios_delegate_->PostAccessibilityNotification(UIAccessibilityScreenChangedNotification,
214  routeName);
215  }
216  } else if (layoutChanged) {
217  SemanticsObject* nextToFocus = nil;
218  // This property will be -1 if the focus is outside of the flutter
219  // application. In this case, we should not refocus anything.
220  if (last_focused_semantics_object_id_ != kSemanticObjectIdInvalid) {
221  // Tries to refocus the previous focused semantics object to avoid random jumps.
222  nextToFocus = [objects_.get() objectForKey:@(last_focused_semantics_object_id_)];
223  if (!nextToFocus && root) {
224  nextToFocus = FindFirstFocusable(root);
225  }
226  }
227  ios_delegate_->PostAccessibilityNotification(UIAccessibilityLayoutChangedNotification,
228  nextToFocus);
229  } else if (scrollOccured) {
230  // TODO(chunhtai): figure out what string to use for notification. At this
231  // point, it is guarantee the previous focused object is still in the tree
232  // so that we don't need to worry about focus lost. (e.g. "Screen 0 of 3")
233  SemanticsObject* nextToFocus = nil;
234  if (last_focused_semantics_object_id_ != kSemanticObjectIdInvalid) {
235  nextToFocus = [objects_.get() objectForKey:@(last_focused_semantics_object_id_)];
236  if (!nextToFocus && root) {
237  nextToFocus = FindFirstFocusable(root);
238  }
239  }
240  ios_delegate_->PostAccessibilityNotification(UIAccessibilityPageScrolledNotification,
241  nextToFocus);
242  }
243 }
bool IsPlatformViewNode() const
std::vector< int32_t > customAccessibilityActions
std::vector< int32_t > childrenInTraversalOrder
std::shared_ptr< FlutterPlatformViewsController > GetPlatformViewsController() const override
SemanticsAction action
constexpr int32_t kRootNodeId

◆ view()

UIView* flutter::AccessibilityBridge::view ( ) const
inlineoverridevirtual

Implements flutter::AccessibilityBridgeIos.

Definition at line 69 of file accessibility_bridge.h.

References GetWeakPtr().

69 { return view_controller_.view; }

The documentation for this class was generated from the following files: