1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | import 'dart:ui' as ui show AccessibilityFeatures, SemanticsActionEvent, SemanticsUpdateBuilder; |
6 | |
7 | import 'package:flutter/foundation.dart'; |
8 | import 'package:flutter/services.dart'; |
9 | |
10 | import 'debug.dart'; |
11 | |
12 | export 'dart:ui' show AccessibilityFeatures, SemanticsActionEvent, SemanticsUpdateBuilder; |
13 | |
14 | /// The glue between the semantics layer and the Flutter engine. |
15 | mixin SemanticsBinding on BindingBase { |
16 | @override |
17 | void initInstances() { |
18 | super.initInstances(); |
19 | _instance = this; |
20 | _accessibilityFeatures = platformDispatcher.accessibilityFeatures; |
21 | platformDispatcher |
22 | ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged |
23 | ..onSemanticsActionEvent = _handleSemanticsActionEvent |
24 | ..onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged; |
25 | _handleSemanticsEnabledChanged(); |
26 | } |
27 | |
28 | /// The current [SemanticsBinding], if one has been created. |
29 | /// |
30 | /// Provides access to the features exposed by this mixin. The binding must |
31 | /// be initialized before using this getter; this is typically done by calling |
32 | /// [runApp] or [WidgetsFlutterBinding.ensureInitialized]. |
33 | static SemanticsBinding get instance => BindingBase.checkInstance(_instance); |
34 | static SemanticsBinding? _instance; |
35 | |
36 | /// Whether semantics information must be collected. |
37 | /// |
38 | /// Returns true if either the platform has requested semantics information |
39 | /// to be generated or if [ensureSemantics] has been called otherwise. |
40 | /// |
41 | /// To get notified when this value changes register a listener with |
42 | /// [addSemanticsEnabledListener]. |
43 | bool get semanticsEnabled { |
44 | assert(_semanticsEnabled.value == (_outstandingHandles > 0)); |
45 | return _semanticsEnabled.value; |
46 | } |
47 | late final ValueNotifier<bool> _semanticsEnabled = ValueNotifier<bool>(platformDispatcher.semanticsEnabled); |
48 | |
49 | /// Adds a `listener` to be called when [semanticsEnabled] changes. |
50 | /// |
51 | /// See also: |
52 | /// |
53 | /// * [removeSemanticsEnabledListener] to remove the listener again. |
54 | /// * [ValueNotifier.addListener], which documents how and when listeners are |
55 | /// called. |
56 | void addSemanticsEnabledListener(VoidCallback listener) { |
57 | _semanticsEnabled.addListener(listener); |
58 | } |
59 | |
60 | /// Removes a `listener` added by [addSemanticsEnabledListener]. |
61 | /// |
62 | /// See also: |
63 | /// |
64 | /// * [ValueNotifier.removeListener], which documents how listeners are |
65 | /// removed. |
66 | void removeSemanticsEnabledListener(VoidCallback listener) { |
67 | _semanticsEnabled.removeListener(listener); |
68 | } |
69 | |
70 | /// The number of clients registered to listen for semantics. |
71 | /// |
72 | /// The number is increased whenever [ensureSemantics] is called and decreased |
73 | /// when [SemanticsHandle.dispose] is called. |
74 | int get debugOutstandingSemanticsHandles => _outstandingHandles; |
75 | int _outstandingHandles = 0; |
76 | |
77 | /// Creates a new [SemanticsHandle] and requests the collection of semantics |
78 | /// information. |
79 | /// |
80 | /// Semantics information are only collected when there are clients interested |
81 | /// in them. These clients express their interest by holding a |
82 | /// [SemanticsHandle]. |
83 | /// |
84 | /// Clients can close their [SemanticsHandle] by calling |
85 | /// [SemanticsHandle.dispose]. Once all outstanding [SemanticsHandle] objects |
86 | /// are closed, semantics information are no longer collected. |
87 | SemanticsHandle ensureSemantics() { |
88 | assert(_outstandingHandles >= 0); |
89 | _outstandingHandles++; |
90 | assert(_outstandingHandles > 0); |
91 | _semanticsEnabled.value = true; |
92 | return SemanticsHandle._(_didDisposeSemanticsHandle); |
93 | } |
94 | |
95 | void _didDisposeSemanticsHandle() { |
96 | assert(_outstandingHandles > 0); |
97 | _outstandingHandles--; |
98 | assert(_outstandingHandles >= 0); |
99 | _semanticsEnabled.value = _outstandingHandles > 0; |
100 | } |
101 | |
102 | // Handle for semantics request from the platform. |
103 | SemanticsHandle? _semanticsHandle; |
104 | |
105 | void _handleSemanticsEnabledChanged() { |
106 | if (platformDispatcher.semanticsEnabled) { |
107 | _semanticsHandle ??= ensureSemantics(); |
108 | } else { |
109 | _semanticsHandle?.dispose(); |
110 | _semanticsHandle = null; |
111 | } |
112 | } |
113 | |
114 | void _handleSemanticsActionEvent(ui.SemanticsActionEvent action) { |
115 | final Object? arguments = action.arguments; |
116 | final ui.SemanticsActionEvent decodedAction = arguments is ByteData |
117 | ? action.copyWith(arguments: const StandardMessageCodec().decodeMessage(arguments)) |
118 | : action; |
119 | performSemanticsAction(decodedAction); |
120 | } |
121 | |
122 | /// Called whenever the platform requests an action to be performed on a |
123 | /// [SemanticsNode]. |
124 | /// |
125 | /// This callback is invoked when a user interacts with the app via an |
126 | /// accessibility service (e.g. TalkBack and VoiceOver) and initiates an |
127 | /// action on the focused node. |
128 | /// |
129 | /// Bindings that mixin the [SemanticsBinding] must implement this method and |
130 | /// perform the given `action` on the [SemanticsNode] specified by |
131 | /// [SemanticsActionEvent.nodeId]. |
132 | /// |
133 | /// See [dart:ui.PlatformDispatcher.onSemanticsActionEvent]. |
134 | @protected |
135 | void performSemanticsAction(ui.SemanticsActionEvent action); |
136 | |
137 | /// The currently active set of [AccessibilityFeatures]. |
138 | /// |
139 | /// This is set when the binding is first initialized and updated whenever a |
140 | /// flag is changed. |
141 | /// |
142 | /// To listen to changes to accessibility features, create a |
143 | /// [WidgetsBindingObserver] and listen to |
144 | /// [WidgetsBindingObserver.didChangeAccessibilityFeatures]. |
145 | ui.AccessibilityFeatures get accessibilityFeatures => _accessibilityFeatures; |
146 | late ui.AccessibilityFeatures _accessibilityFeatures; |
147 | |
148 | /// Called when the platform accessibility features change. |
149 | /// |
150 | /// See [dart:ui.PlatformDispatcher.onAccessibilityFeaturesChanged]. |
151 | @protected |
152 | @mustCallSuper |
153 | void handleAccessibilityFeaturesChanged() { |
154 | _accessibilityFeatures = platformDispatcher.accessibilityFeatures; |
155 | } |
156 | |
157 | /// Creates an empty semantics update builder. |
158 | /// |
159 | /// The caller is responsible for filling out the semantics node updates. |
160 | /// |
161 | /// This method is used by the [SemanticsOwner] to create builder for all its |
162 | /// semantics updates. |
163 | ui.SemanticsUpdateBuilder createSemanticsUpdateBuilder() { |
164 | return ui.SemanticsUpdateBuilder(); |
165 | } |
166 | |
167 | /// The platform is requesting that animations be disabled or simplified. |
168 | /// |
169 | /// This setting can be overridden for testing or debugging by setting |
170 | /// [debugSemanticsDisableAnimations]. |
171 | bool get disableAnimations { |
172 | bool value = _accessibilityFeatures.disableAnimations; |
173 | assert(() { |
174 | if (debugSemanticsDisableAnimations != null) { |
175 | value = debugSemanticsDisableAnimations!; |
176 | } |
177 | return true; |
178 | }()); |
179 | return value; |
180 | } |
181 | } |
182 | |
183 | /// A reference to the semantics information generated by the framework. |
184 | /// |
185 | /// Semantics information are only collected when there are clients interested |
186 | /// in them. These clients express their interest by holding a |
187 | /// [SemanticsHandle]. When the client no longer needs the |
188 | /// semantics information, it must call [dispose] on the [SemanticsHandle] to |
189 | /// close it. When all open [SemanticsHandle]s are disposed, the framework will |
190 | /// stop updating the semantics information. |
191 | /// |
192 | /// To obtain a [SemanticsHandle], call [SemanticsBinding.ensureSemantics]. |
193 | class SemanticsHandle { |
194 | SemanticsHandle._(this._onDispose) { |
195 | // TODO(polina-c): stop duplicating code across disposables |
196 | // https://github.com/flutter/flutter/issues/137435 |
197 | if (kFlutterMemoryAllocationsEnabled) { |
198 | FlutterMemoryAllocations.instance.dispatchObjectCreated( |
199 | library: 'package:flutter/semantics.dart' , |
200 | className: ' $SemanticsHandle' , |
201 | object: this, |
202 | ); |
203 | } |
204 | } |
205 | |
206 | final VoidCallback _onDispose; |
207 | |
208 | /// Closes the semantics handle. |
209 | /// |
210 | /// When all the outstanding [SemanticsHandle] objects are closed, the |
211 | /// framework will stop generating semantics information. |
212 | @mustCallSuper |
213 | void dispose() { |
214 | // TODO(polina-c): stop duplicating code across disposables |
215 | // https://github.com/flutter/flutter/issues/137435 |
216 | if (kFlutterMemoryAllocationsEnabled) { |
217 | FlutterMemoryAllocations.instance.dispatchObjectDisposed(object: this); |
218 | } |
219 | |
220 | _onDispose(); |
221 | } |
222 | } |
223 | |