diff --git a/dev/integration_tests/android_semantics_testing/lib/main.dart b/dev/integration_tests/android_semantics_testing/lib/main.dart index 3e8002ba23..12afeada56 100644 --- a/dev/integration_tests/android_semantics_testing/lib/main.dart +++ b/dev/integration_tests/android_semantics_testing/lib/main.dart @@ -23,11 +23,11 @@ void main() { const MethodChannel kSemanticsChannel = MethodChannel('semantics'); -Future dataHandler(String message) async { - if (message.contains('getSemanticsNode')) { +Future dataHandler(String? message) async { + if (message != null && message.contains('getSemanticsNode')) { final Completer completer = Completer(); final int id = int.tryParse(message.split('#')[1]) ?? 0; - Future completeSemantics([Object _]) async { + Future completeSemantics([Object? _]) async { final dynamic result = await kSemanticsChannel.invokeMethod('getSemanticsNode', { 'id': id, }); @@ -40,10 +40,10 @@ Future dataHandler(String message) async { } return completer.future; } - if (message.contains('setClipboard')) { + if (message != null && message.contains('setClipboard')) { final Completer completer = Completer(); final String str = message.split('#')[1]; - Future completeSetClipboard([Object _]) async { + Future completeSetClipboard([Object? _]) async { await kSemanticsChannel.invokeMethod('setClipboard', { 'message': str, }); @@ -67,7 +67,7 @@ Map routes = { }; class TestApp extends StatelessWidget { - const TestApp({Key key}) : super(key: key); + const TestApp({super.key}); @override Widget build(BuildContext context) { diff --git a/dev/integration_tests/android_semantics_testing/lib/src/common.dart b/dev/integration_tests/android_semantics_testing/lib/src/common.dart index 9305855f49..3ff2528129 100644 --- a/dev/integration_tests/android_semantics_testing/lib/src/common.dart +++ b/dev/integration_tests/android_semantics_testing/lib/src/common.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// ignore_for_file: avoid_dynamic_calls + import 'dart:convert'; import 'package:meta/meta.dart'; @@ -53,19 +55,19 @@ class AndroidSemanticsNode { /// ] /// } factory AndroidSemanticsNode.deserialize(String value) { - return AndroidSemanticsNode._(json.decode(value) as Map); + return AndroidSemanticsNode._(json.decode(value)); } - final Map _values; + final dynamic _values; final List _children = []; - Map get _flags => _values['flags'] as Map; + dynamic get _flags => _values['flags']; /// The text value of the semantics node. /// /// This is produced by combining the value, label, and hint fields from /// the Flutter [SemanticsNode]. - String get text => _values['text'] as String; + String? get text => _values['text'] as String?; /// The contentDescription of the semantics node. /// @@ -75,7 +77,7 @@ class AndroidSemanticsNode { /// /// This is produced by combining the value, label, and hint fields from /// the Flutter [SemanticsNode]. - String get contentDescription => _values['contentDescription'] as String; + String? get contentDescription => _values['contentDescription'] as String?; /// The className of the semantics node. /// @@ -84,10 +86,10 @@ class AndroidSemanticsNode { /// /// If a more specific value isn't provided, it defaults to /// "android.view.View". - String get className => _values['className'] as String; + String? get className => _values['className'] as String?; /// The identifier for this semantics node. - int get id => _values['id'] as int; + int? get id => _values['id'] as int?; /// The children of this semantics node. List get children => _children; @@ -95,50 +97,52 @@ class AndroidSemanticsNode { /// Whether the node is currently in a checked state. /// /// Equivalent to [SemanticsFlag.isChecked]. - bool get isChecked => _flags['isChecked'] as bool; + bool? get isChecked => _flags['isChecked'] as bool?; /// Whether the node can be in a checked state. /// /// Equivalent to [SemanticsFlag.hasCheckedState] - bool get isCheckable => _flags['isCheckable'] as bool; + bool? get isCheckable => _flags['isCheckable'] as bool?; /// Whether the node is editable. /// /// This is usually only applied to text fields, which map /// to "android.widget.EditText". - bool get isEditable => _flags['isEditable'] as bool; + bool? get isEditable => _flags['isEditable'] as bool?; /// Whether the node is enabled. - bool get isEnabled => _flags['isEnabled'] as bool; + bool? get isEnabled => _flags['isEnabled'] as bool?; /// Whether the node is focusable. - bool get isFocusable => _flags['isFocusable'] as bool; + bool? get isFocusable => _flags['isFocusable'] as bool?; /// Whether the node is focused. - bool get isFocused => _flags['isFocused'] as bool; + bool? get isFocused => _flags['isFocused'] as bool?; /// Whether the node is considered a heading. - bool get isHeading => _flags['isHeading'] as bool; + bool? get isHeading => _flags['isHeading'] as bool?; /// Whether the node represents a password field. /// /// Equivalent to [SemanticsFlag.isObscured]. - bool get isPassword => _flags['isPassword'] as bool; + bool? get isPassword => _flags['isPassword'] as bool?; /// Whether the node is long clickable. /// /// Equivalent to having [SemanticsAction.longPress]. - bool get isLongClickable => _flags['isLongClickable'] as bool; + bool? get isLongClickable => _flags['isLongClickable'] as bool?; /// Gets a [Rect] which defines the position and size of the semantics node. Rect getRect() { - final Map rawRect = _values['rect'] as Map; - final Map rect = rawRect.cast(); + final dynamic rawRect = _values['rect']; + if (rawRect == null) { + return const Rect.fromLTRB(0.0, 0.0, 0.0, 0.0); + } return Rect.fromLTRB( - rect['left'].toDouble(), - rect['top'].toDouble(), - rect['right'].toDouble(), - rect['bottom'].toDouble(), + (rawRect['left']! as int).toDouble(), + (rawRect['top']! as int).toDouble(), + (rawRect['right']! as int).toDouble(), + (rawRect['bottom']! as int).toDouble(), ); } @@ -149,9 +153,20 @@ class AndroidSemanticsNode { } /// Gets a list of [AndroidSemanticsActions] which are defined for the node. - List getActions() => [ - for (final int id in (_values['actions'] as List).cast()) AndroidSemanticsAction.deserialize(id), - ]; + List getActions() { + final List? actions = (_values['actions'] as List?)?.cast(); + if (actions == null) { + return const []; + } + final List convertedActions = []; + for (final int id in actions) { + final AndroidSemanticsAction? action = AndroidSemanticsAction.deserialize(id); + if (action != null) { + convertedActions.add(action); + } + } + return convertedActions; + } @override String toString() { diff --git a/dev/integration_tests/android_semantics_testing/lib/src/constants.dart b/dev/integration_tests/android_semantics_testing/lib/src/constants.dart index 22f75bc7bd..ef8a863d9e 100644 --- a/dev/integration_tests/android_semantics_testing/lib/src/constants.dart +++ b/dev/integration_tests/android_semantics_testing/lib/src/constants.dart @@ -168,7 +168,7 @@ class AndroidSemanticsAction { case _kSetText: return 'AndroidSemanticsAction.setText'; default: - return null; + throw UnimplementedError(); } } @@ -211,7 +211,7 @@ class AndroidSemanticsAction { /// Creates a new [AndroidSemanticsAction] from an integer `value`. /// /// Returns `null` if the id is not a known Android accessibility action. - static AndroidSemanticsAction deserialize(int value) { + static AndroidSemanticsAction? deserialize(int value) { return _kActionById[value]; } } diff --git a/dev/integration_tests/android_semantics_testing/lib/src/matcher.dart b/dev/integration_tests/android_semantics_testing/lib/src/matcher.dart index ea36302266..15f43f7fd6 100644 --- a/dev/integration_tests/android_semantics_testing/lib/src/matcher.dart +++ b/dev/integration_tests/android_semantics_testing/lib/src/matcher.dart @@ -18,24 +18,24 @@ import 'constants.dart'; /// the Android accessibility bridge, and not the semantics object created by /// the Flutter framework. Matcher hasAndroidSemantics({ - String text, - String contentDescription, - String className, - int id, - Rect rect, - Size size, - List actions, - List ignoredActions, - List children, - bool isChecked, - bool isCheckable, - bool isEditable, - bool isEnabled, - bool isFocusable, - bool isFocused, - bool isHeading, - bool isPassword, - bool isLongClickable, + String? text, + String? contentDescription, + String? className, + int? id, + Rect? rect, + Size? size, + List? actions, + List? ignoredActions, + List? children, + bool? isChecked, + bool? isCheckable, + bool? isEditable, + bool? isEnabled, + bool? isFocusable, + bool? isFocused, + bool? isHeading, + bool? isPassword, + bool? isLongClickable, }) { return _AndroidSemanticsMatcher( text: text, @@ -79,23 +79,23 @@ class _AndroidSemanticsMatcher extends Matcher { this.isLongClickable, }); - final String text; - final String className; - final String contentDescription; - final int id; - final List actions; - final List ignoredActions; - final Rect rect; - final Size size; - final bool isChecked; - final bool isCheckable; - final bool isEditable; - final bool isEnabled; - final bool isFocusable; - final bool isFocused; - final bool isHeading; - final bool isPassword; - final bool isLongClickable; + final String? text; + final String? className; + final String? contentDescription; + final int? id; + final List? actions; + final List? ignoredActions; + final Rect? rect; + final Size? size; + final bool? isChecked; + final bool? isCheckable; + final bool? isEditable; + final bool? isEnabled; + final bool? isFocusable; + final bool? isFocused; + final bool? isHeading; + final bool? isPassword; + final bool? isLongClickable; @override Description describe(Description description) { @@ -149,7 +149,7 @@ class _AndroidSemanticsMatcher extends Matcher { } @override - bool matches(covariant AndroidSemanticsNode item, Map matchState) { + bool matches(covariant AndroidSemanticsNode item, Map matchState) { if (text != null && text != item.text) { return _failWithMessage('Expected text: $text', matchState); } @@ -170,13 +170,13 @@ class _AndroidSemanticsMatcher extends Matcher { } if (actions != null) { final List itemActions = item.getActions(); - if (!unorderedEquals(actions).matches(itemActions, matchState)) { - final List actionsString = actions.map((AndroidSemanticsAction action) => action.toString()).toList()..sort(); + if (!unorderedEquals(actions!).matches(itemActions, matchState)) { + final List actionsString = actions!.map((AndroidSemanticsAction action) => action.toString()).toList()..sort(); final List itemActionsString = itemActions.map((AndroidSemanticsAction action) => action.toString()).toList()..sort(); - final Set unexpected = itemActions.toSet().difference(actions.toSet()); + final Set unexpected = itemActions.toSet().difference(actions!.toSet()); final Set unexpectedInString = itemActionsString.toSet().difference(actionsString.toSet()); final Set missingInString = actionsString.toSet().difference(itemActionsString.toSet()); - if (missingInString.isEmpty && ignoredActions != null && unexpected.every(ignoredActions.contains)) { + if (missingInString.isEmpty && ignoredActions != null && unexpected.every(ignoredActions!.contains)) { return true; } return _failWithMessage('Expected actions: $actionsString\nActual actions: $itemActionsString\nUnexpected: $unexpectedInString\nMissing: $missingInString', matchState); @@ -214,9 +214,13 @@ class _AndroidSemanticsMatcher extends Matcher { } @override - Description describeMismatch(Object item, Description mismatchDescription, - Map matchState, bool verbose) { - return mismatchDescription.add(matchState['failure'] as String); + Description describeMismatch(dynamic item, Description mismatchDescription, + Map matchState, bool verbose) { + final String? failure = matchState['failure'] as String?; + if (failure == null) { + return mismatchDescription.add('hasAndroidSemantics matcher does not complete successfully'); + } + return mismatchDescription.add(failure); } bool _failWithMessage(String value, Map matchState) { diff --git a/dev/integration_tests/android_semantics_testing/lib/src/tests/controls_page.dart b/dev/integration_tests/android_semantics_testing/lib/src/tests/controls_page.dart index 3d4cda7e57..fad7ff4eba 100644 --- a/dev/integration_tests/android_semantics_testing/lib/src/tests/controls_page.dart +++ b/dev/integration_tests/android_semantics_testing/lib/src/tests/controls_page.dart @@ -9,7 +9,7 @@ export 'controls_constants.dart'; /// A test page with a checkbox, three radio buttons, and a switch. class SelectionControlsPage extends StatefulWidget { - const SelectionControlsPage({Key key}) : super(key: key); + const SelectionControlsPage({super.key}); @override State createState() => _SelectionControlsPageState(); @@ -28,15 +28,15 @@ class _SelectionControlsPageState extends State { bool _isLabeledOn = false; int _radio = 0; - void _updateCheckbox(bool newValue) { + void _updateCheckbox(bool? newValue) { setState(() { - _isChecked = newValue; + _isChecked = newValue!; }); } - void _updateRadio(int newValue) { + void _updateRadio(int? newValue) { setState(() { - _radio = newValue; + _radio = newValue!; }); } diff --git a/dev/integration_tests/android_semantics_testing/lib/src/tests/headings_page.dart b/dev/integration_tests/android_semantics_testing/lib/src/tests/headings_page.dart index b08ebdbbe4..a4ccf0b748 100644 --- a/dev/integration_tests/android_semantics_testing/lib/src/tests/headings_page.dart +++ b/dev/integration_tests/android_semantics_testing/lib/src/tests/headings_page.dart @@ -9,7 +9,7 @@ export 'headings_constants.dart'; /// A test page with an app bar and some body text for testing heading flags. class HeadingsPage extends StatelessWidget { - const HeadingsPage({Key key}) : super(key: key); + const HeadingsPage({super.key}); static const ValueKey _appBarTitleKey = ValueKey(appBarTitleKeyValue); static const ValueKey _bodyTextKey = ValueKey(bodyTextKeyValue); diff --git a/dev/integration_tests/android_semantics_testing/lib/src/tests/popup_page.dart b/dev/integration_tests/android_semantics_testing/lib/src/tests/popup_page.dart index 6f0b064fc0..3ede08eac3 100644 --- a/dev/integration_tests/android_semantics_testing/lib/src/tests/popup_page.dart +++ b/dev/integration_tests/android_semantics_testing/lib/src/tests/popup_page.dart @@ -10,7 +10,7 @@ export 'popup_constants.dart'; /// A page with a popup menu, a dropdown menu, and a modal alert. class PopupControlsPage extends StatefulWidget { - const PopupControlsPage({Key key}) : super(key: key); + const PopupControlsPage({super.key}); @override State createState() => _PopupControlsPageState(); @@ -60,9 +60,9 @@ class _PopupControlsPageState extends State { child: Text(item), ); }).toList(), - onChanged: (String value) { + onChanged: (String? value) { setState(() { - dropdownValue = value; + dropdownValue = value!; }); }, ), diff --git a/dev/integration_tests/android_semantics_testing/lib/src/tests/text_field_page.dart b/dev/integration_tests/android_semantics_testing/lib/src/tests/text_field_page.dart index 20a22449aa..fcd6368c0b 100644 --- a/dev/integration_tests/android_semantics_testing/lib/src/tests/text_field_page.dart +++ b/dev/integration_tests/android_semantics_testing/lib/src/tests/text_field_page.dart @@ -10,7 +10,7 @@ export 'text_field_constants.dart'; /// A page with a normal text field and a password field. class TextFieldPage extends StatefulWidget { - const TextFieldPage({Key key}) : super(key: key); + const TextFieldPage({super.key}); @override State createState() => _TextFieldPageState(); diff --git a/dev/integration_tests/android_semantics_testing/pubspec.yaml b/dev/integration_tests/android_semantics_testing/pubspec.yaml index 4801d409fc..417b4ce1ab 100644 --- a/dev/integration_tests/android_semantics_testing/pubspec.yaml +++ b/dev/integration_tests/android_semantics_testing/pubspec.yaml @@ -1,7 +1,7 @@ name: android_semantics_testing description: Integration testing library for Android semantics environment: - sdk: '>=2.9.0 <3.0.0' + sdk: '>=2.17.0-0 <3.0.0' dependencies: flutter: diff --git a/dev/integration_tests/android_semantics_testing/test_driver/main_test.dart b/dev/integration_tests/android_semantics_testing/test_driver/main_test.dart index e4faad917f..df0f14f263 100644 --- a/dev/integration_tests/android_semantics_testing/test_driver/main_test.dart +++ b/dev/integration_tests/android_semantics_testing/test_driver/main_test.dart @@ -20,7 +20,7 @@ const List ignoredAccessibilityFocusActions = getSemantics(SerializableFinder finder) async { final int id = await driver.getSemanticsId(finder); final String data = await driver.requestData('getSemanticsNode#$id'); @@ -38,7 +38,7 @@ void main() { } // The version of TalkBack running on the device. - Version talkbackVersion; + Version? talkbackVersion; Future getTalkbackVersion() async { final io.ProcessResult result = await io.Process.run(adbPath(), const [ @@ -51,7 +51,7 @@ void main() { throw Exception('Failed to get TalkBack version: ${result.stdout as String}\n${result.stderr as String}'); } final List lines = (result.stdout as String).split('\n'); - String version; + String? version; for (final String line in lines) { if (line.contains('versionName')) { version = line.replaceAll(RegExp(r'\s*versionName='), ''); @@ -64,14 +64,14 @@ void main() { // Android doesn't quite use semver, so convert the version string to semver form. final RegExp startVersion = RegExp(r'(?\d+)\.(?\d+)\.(?\d+)(\.(?\d+))?'); - final RegExpMatch match = startVersion.firstMatch(version); + final RegExpMatch? match = startVersion.firstMatch(version); if (match == null) { return Version(0, 0, 0); } return Version( - int.parse(match.namedGroup('major')), - int.parse(match.namedGroup('minor')), - int.parse(match.namedGroup('patch')), + int.parse(match.namedGroup('major')!), + int.parse(match.namedGroup('minor')!), + int.parse(match.namedGroup('patch')!), build: match.namedGroup('build'), ); } @@ -104,7 +104,7 @@ void main() { 'null', ]); await run.exitCode; - driver?.close(); + driver.close(); }); group('TextField', () {