binding.dart 7.46 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

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 11
import 'debug.dart';

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 17 18 19
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
20
    _accessibilityFeatures = platformDispatcher.accessibilityFeatures;
21 22
    platformDispatcher
      ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
23
      ..onSemanticsActionEvent = _handleSemanticsActionEvent
24 25
      ..onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
    _handleSemanticsEnabledChanged();
26 27
  }

28 29 30 31 32 33 34 35
  /// The current [SemanticsBinding], if one has been created.
  ///
  /// Provides access to the features exposed by this mixin. The binding must
  /// be initialized before using this getter; this is typically done by calling
  /// [runApp] or [WidgetsFlutterBinding.ensureInitialized].
  static SemanticsBinding get instance => BindingBase.checkInstance(_instance);
  static SemanticsBinding? _instance;

36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
  /// Whether semantics information must be collected.
  ///
  /// Returns true if either the platform has requested semantics information
  /// to be generated or if [ensureSemantics] has been called otherwise.
  ///
  /// To get notified when this value changes register a listener with
  /// [addSemanticsEnabledListener].
  bool get semanticsEnabled {
    assert(_semanticsEnabled.value == (_outstandingHandles > 0));
    return _semanticsEnabled.value;
  }
  late final ValueNotifier<bool> _semanticsEnabled = ValueNotifier<bool>(platformDispatcher.semanticsEnabled);

  /// Adds a `listener` to be called when [semanticsEnabled] changes.
  ///
  /// See also:
  ///
  ///  * [removeSemanticsEnabledListener] to remove the listener again.
  ///  * [ValueNotifier.addListener], which documents how and when listeners are
  ///    called.
  void addSemanticsEnabledListener(VoidCallback listener) {
    _semanticsEnabled.addListener(listener);
  }

  /// Removes a `listener` added by [addSemanticsEnabledListener].
  ///
  /// See also:
  ///
  ///  * [ValueNotifier.removeListener], which documents how listeners are
  ///    removed.
  void removeSemanticsEnabledListener(VoidCallback listener) {
    _semanticsEnabled.removeListener(listener);
  }

70 71 72 73 74
  /// The number of clients registered to listen for semantics.
  ///
  /// The number is increased whenever [ensureSemantics] is called and decreased
  /// when [SemanticsHandle.dispose] is called.
  int get debugOutstandingSemanticsHandles => _outstandingHandles;
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
  int _outstandingHandles = 0;

  /// Creates a new [SemanticsHandle] and requests the collection of semantics
  /// information.
  ///
  /// Semantics information are only collected when there are clients interested
  /// in them. These clients express their interest by holding a
  /// [SemanticsHandle].
  ///
  /// Clients can close their [SemanticsHandle] by calling
  /// [SemanticsHandle.dispose]. Once all outstanding [SemanticsHandle] objects
  /// are closed, semantics information are no longer collected.
  SemanticsHandle ensureSemantics() {
    assert(_outstandingHandles >= 0);
    _outstandingHandles++;
    assert(_outstandingHandles > 0);
    _semanticsEnabled.value = true;
    return SemanticsHandle._(_didDisposeSemanticsHandle);
  }

  void _didDisposeSemanticsHandle() {
    assert(_outstandingHandles > 0);
    _outstandingHandles--;
    assert(_outstandingHandles >= 0);
    _semanticsEnabled.value = _outstandingHandles > 0;
  }

  // Handle for semantics request from the platform.
  SemanticsHandle? _semanticsHandle;

  void _handleSemanticsEnabledChanged() {
    if (platformDispatcher.semanticsEnabled) {
      _semanticsHandle ??= ensureSemantics();
    } else {
      _semanticsHandle?.dispose();
      _semanticsHandle = null;
    }
  }

114 115 116 117 118 119
  void _handleSemanticsActionEvent(ui.SemanticsActionEvent action) {
    final Object? arguments = action.arguments;
    final ui.SemanticsActionEvent decodedAction = arguments is ByteData
      ? action.copyWith(arguments: const StandardMessageCodec().decodeMessage(arguments))
      : action;
    performSemanticsAction(decodedAction);
120 121 122 123 124 125 126 127 128 129 130 131 132
  }

  /// Called whenever the platform requests an action to be performed on a
  /// [SemanticsNode].
  ///
  /// This callback is invoked when a user interacts with the app via an
  /// accessibility service (e.g. TalkBack and VoiceOver) and initiates an
  /// action on the focused node.
  ///
  /// Bindings that mixin the [SemanticsBinding] must implement this method and
  /// perform the given `action` on the [SemanticsNode] specified by
  /// [SemanticsActionEvent.nodeId].
  ///
133
  /// See [dart:ui.PlatformDispatcher.onSemanticsActionEvent].
134
  @protected
135
  void performSemanticsAction(ui.SemanticsActionEvent action);
136 137 138 139 140 141 142 143 144 145 146 147

  /// The currently active set of [AccessibilityFeatures].
  ///
  /// This is set when the binding is first initialized and updated whenever a
  /// flag is changed.
  ///
  /// To listen to changes to accessibility features, create a
  /// [WidgetsBindingObserver] and listen to
  /// [WidgetsBindingObserver.didChangeAccessibilityFeatures].
  ui.AccessibilityFeatures get accessibilityFeatures => _accessibilityFeatures;
  late ui.AccessibilityFeatures _accessibilityFeatures;

148 149
  /// Called when the platform accessibility features change.
  ///
150
  /// See [dart:ui.PlatformDispatcher.onAccessibilityFeaturesChanged].
151
  @protected
152
  @mustCallSuper
153
  void handleAccessibilityFeaturesChanged() {
154
    _accessibilityFeatures = platformDispatcher.accessibilityFeatures;
155 156
  }

157 158 159 160 161 162 163 164 165 166
  /// Creates an empty semantics update builder.
  ///
  /// The caller is responsible for filling out the semantics node updates.
  ///
  /// This method is used by the [SemanticsOwner] to create builder for all its
  /// semantics updates.
  ui.SemanticsUpdateBuilder createSemanticsUpdateBuilder() {
    return ui.SemanticsUpdateBuilder();
  }

167 168
  /// The platform is requesting that animations be disabled or simplified.
  ///
169
  /// This setting can be overridden for testing or debugging by setting
170 171 172 173
  /// [debugSemanticsDisableAnimations].
  bool get disableAnimations {
    bool value = _accessibilityFeatures.disableAnimations;
    assert(() {
174
      if (debugSemanticsDisableAnimations != null) {
175
        value = debugSemanticsDisableAnimations!;
176
      }
177 178 179 180 181
      return true;
    }());
    return value;
  }
}
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206

/// A reference to the semantics information generated by the framework.
///
/// Semantics information are only collected when there are clients interested
/// in them. These clients express their interest by holding a
/// [SemanticsHandle]. When the client no longer needs the
/// semantics information, it must call [dispose] on the [SemanticsHandle] to
/// close it. When all open [SemanticsHandle]s are disposed, the framework will
/// stop updating the semantics information.
///
/// To obtain a [SemanticsHandle], call [SemanticsBinding.ensureSemantics].
class SemanticsHandle {
  SemanticsHandle._(this._onDispose);

  final VoidCallback _onDispose;

  /// Closes the semantics handle.
  ///
  /// When all the outstanding [SemanticsHandle] objects are closed, the
  /// framework will stop generating semantics information.
  @mustCallSuper
  void dispose() {
    _onDispose();
  }
}