Merge branch 'dwds-integration' of github.com:flutter/flutter into dwds-integration
diff --git a/AUTHORS b/AUTHORS
index 0fe69e4..fb6ebe3 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -68,3 +68,4 @@
 Terrence Addison Tandijono(flotilla) <terrenceaddison32@gmail.com>
 YeungKC <flutter@yeungkc.com>
 Nobuhiro Tabuki <japanese.around30@gmail.com>
+nt4f04uNd <nt4f04und@gmail.com>
diff --git a/bin/internal/engine.version b/bin/internal/engine.version
index 70f67b9..deba2ee 100644
--- a/bin/internal/engine.version
+++ b/bin/internal/engine.version
@@ -1 +1 @@
-5355f270ba99d23020307d5d2906b93338e9a60e
+defa8be2b10650dad50dfee9324ed8d16eeec13f
diff --git a/bin/internal/fuchsia-linux.version b/bin/internal/fuchsia-linux.version
index f39d43e..f1a032b 100644
--- a/bin/internal/fuchsia-linux.version
+++ b/bin/internal/fuchsia-linux.version
@@ -1 +1 @@
-ZJHmp3INUrLtYTJzHkJ-mTGQ7F59bfv1usLDP7xS-XgC
+lPMs_KwnUp27LASOqwbTfKnX1rXsDfbnstsgVJYi-zEC
diff --git a/bin/internal/fuchsia-mac.version b/bin/internal/fuchsia-mac.version
index af29bcc..7f9034b 100644
--- a/bin/internal/fuchsia-mac.version
+++ b/bin/internal/fuchsia-mac.version
@@ -1 +1 @@
-_FaRRt69ZlrCACiYukNB6u_zVpyECR3W1gPgS__sm-kC
+pZ9FgVZTKe6ZwrCLTXs-s6HRYReW0XqmPJy6dnITrPcC
diff --git a/dev/benchmarks/macrobenchmarks/lib/src/web/gallery/gallery_recorder.dart b/dev/benchmarks/macrobenchmarks/lib/src/web/gallery/gallery_recorder.dart
deleted file mode 100644
index 5de16c5..0000000
--- a/dev/benchmarks/macrobenchmarks/lib/src/web/gallery/gallery_recorder.dart
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2014 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:flutter/material.dart';
-
-import 'package:gallery/benchmarks/gallery_automator.dart';
-
-import 'package:macrobenchmarks/src/web/recorder.dart';
-
-/// A recorder that measures frame building durations for the Gallery.
-class GalleryRecorder extends WidgetRecorder {
-  GalleryRecorder({
-    @required this.benchmarkName,
-    this.shouldRunPredicate,
-    this.testScrollsOnly = false,
-  }) : assert(testScrollsOnly || shouldRunPredicate != null),
-       super(name: benchmarkName, useCustomWarmUp: true);
-
-  /// The name of the gallery benchmark to be run.
-  final String benchmarkName;
-
-  /// A function that accepts the name of a demo and returns whether we should
-  /// run this demo in this benchmark.
-  final bool Function(String) shouldRunPredicate;
-
-  /// Whether this benchmark only tests scrolling.
-  final bool testScrollsOnly;
-
-  /// Whether we should continue recording.
-  @override
-  bool shouldContinue() => !_finished || profile.shouldContinue();
-
-  GalleryAutomator _galleryAutomator;
-  bool get _finished => _galleryAutomator?.finished ?? false;
-
-  /// Creates the [GalleryAutomator] widget.
-  @override
-  Widget createWidget() {
-    _galleryAutomator = GalleryAutomator(
-      benchmarkName: benchmarkName,
-      shouldRunPredicate: shouldRunPredicate,
-      testScrollsOnly: testScrollsOnly,
-      stopWarmingUpCallback: profile.stopWarmingUp,
-    );
-    return _galleryAutomator.createWidget();
-  }
-}
diff --git a/dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart b/dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart
index e07c920..cff1edf 100644
--- a/dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart
+++ b/dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart
@@ -10,8 +10,6 @@
 import 'package:macrobenchmarks/src/web/bench_text_layout.dart';
 import 'package:macrobenchmarks/src/web/bench_text_out_of_picture_bounds.dart';
 
-import 'package:gallery/benchmarks/gallery_automator.dart' show DemoType, typeOfDemo;
-
 import 'src/web/bench_build_image.dart';
 import 'src/web/bench_build_material_checkbox.dart';
 import 'src/web/bench_card_infinite_scroll.dart';
@@ -26,15 +24,12 @@
 import 'src/web/bench_picture_recording.dart';
 import 'src/web/bench_simple_lazy_text_scroll.dart';
 import 'src/web/bench_text_out_of_picture_bounds.dart';
-import 'src/web/gallery/gallery_recorder.dart';
 import 'src/web/recorder.dart';
 
 typedef RecorderFactory = Recorder Function();
 
 const bool isCanvasKit = bool.fromEnvironment('FLUTTER_WEB_USE_SKIA', defaultValue: false);
 
-const String _galleryBenchmarkPrefix = 'gallery_v2';
-
 /// List of all benchmarks that run in the devicelab.
 ///
 /// When adding a new benchmark, add it to this map. Make sure that the name
@@ -67,27 +62,6 @@
     BenchTextCachedLayout.canvasBenchmarkName: () => BenchTextCachedLayout(useCanvas: true),
     BenchBuildColorsGrid.domBenchmarkName: () => BenchBuildColorsGrid.dom(),
     BenchBuildColorsGrid.canvasBenchmarkName: () => BenchBuildColorsGrid.canvas(),
-
-    // The following benchmark is for the Flutter Gallery.
-    // This benchmark is failing when run with CanvasKit, so we skip it
-    // for now.
-    // TODO(yjbanov): https://github.com/flutter/flutter/issues/59082
-    '${_galleryBenchmarkPrefix}_studies_perf': () => GalleryRecorder(
-      benchmarkName: '${_galleryBenchmarkPrefix}_studies_perf',
-      shouldRunPredicate: (String demo) => typeOfDemo(demo) == DemoType.study,
-    ),
-    '${_galleryBenchmarkPrefix}_unanimated_perf': () => GalleryRecorder(
-      benchmarkName: '${_galleryBenchmarkPrefix}_unanimated_perf',
-      shouldRunPredicate: (String demo) => typeOfDemo(demo) == DemoType.unanimatedWidget,
-    ),
-    '${_galleryBenchmarkPrefix}_animated_perf': () => GalleryRecorder(
-      benchmarkName: '${_galleryBenchmarkPrefix}_animated_perf',
-      shouldRunPredicate: (String demo) => typeOfDemo(demo) == DemoType.animatedWidget,
-    ),
-    '${_galleryBenchmarkPrefix}_scroll_perf': () => GalleryRecorder(
-      benchmarkName: '${_galleryBenchmarkPrefix}_scroll_perf',
-      testScrollsOnly: true,
-    ),
   },
 };
 
diff --git a/dev/benchmarks/macrobenchmarks/pubspec.yaml b/dev/benchmarks/macrobenchmarks/pubspec.yaml
index 8773d29..51ccdcc 100644
--- a/dev/benchmarks/macrobenchmarks/pubspec.yaml
+++ b/dev/benchmarks/macrobenchmarks/pubspec.yaml
@@ -17,13 +17,6 @@
   #   flutter update-packages --force-upgrade
   flutter_gallery_assets: 0.2.6
 
-  # This is needed for web_benchmarks.
-  gallery:
-    git:
-      url: https://github.com/flutter/gallery.git
-      ref: d8b4858f88ab9006c15a5c9e197386623ea2e12c
-
-  animations: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   archive: 2.0.13 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   args: 1.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   async: 2.5.0-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -32,67 +25,28 @@
   collection: 1.15.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   convert: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   crypto: 2.1.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  cupertino_icons: 0.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  ffi: 0.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   file: 6.0.0-nullsafety.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  flare_dart: 2.3.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  flare_flutter: 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  flutter_localized_locales: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  flutter_staggered_grid_view: 0.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  google_fonts: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  http: 0.12.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  http_parser: 3.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  intl: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  js: 0.6.3-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   json_rpc_2: 2.2.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   matcher: 0.12.10-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   meta: 1.3.0-nullsafety.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  package_info: 0.4.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   path: 1.8.0-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  path_provider: 1.6.14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  path_provider_linux: 0.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  path_provider_macos: 0.0.4+4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  path_provider_platform_interface: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  path_provider_windows: 0.0.4+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  pedantic: 1.10.0-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  platform: 3.0.0-nullsafety.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  plugin_platform_interface: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  process: 4.0.0-nullsafety.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   pub_semver: 1.4.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  rally_assets: 2.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  scoped_model: 1.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  shared_preferences: 0.5.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  shared_preferences_linux: 0.0.2+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  shared_preferences_macos: 0.0.1+10 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  shared_preferences_platform_interface: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  shared_preferences_web: 0.1.2+7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  shared_preferences_windows: 0.0.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  shrine_images: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   source_span: 1.8.0-nullsafety.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   stack_trace: 1.10.0-nullsafety.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   stream_channel: 2.1.0-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  string_scanner: 1.1.0-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   sync_http: 0.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   term_glyph: 1.2.0-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   typed_data: 1.3.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  url_launcher: 5.7.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  url_launcher_linux: 0.0.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  url_launcher_macos: 0.0.1+8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  url_launcher_platform_interface: 1.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  url_launcher_web: 0.1.4+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  url_launcher_windows: 0.0.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   vector_math: 2.1.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   vm_service_client: 0.2.6+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   web_socket_channel: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   webdriver: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  win32: 1.7.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-  xdg_directories: 0.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
 
 dev_dependencies:
   flutter_test:
     sdk: flutter
   test: 1.16.0-nullsafety.5
-  e2e: 0.7.0+1
+  integration_test: 0.9.2+1
 
   _fe_analyzer_shared: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   analyzer: 0.39.17 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -104,14 +58,18 @@
   fake_async: 1.2.0-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   glob: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   html: 0.14.0+4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  http: 0.12.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   http_multi_server: 2.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  http_parser: 3.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   io: 0.3.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  js: 0.6.3-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   logging: 0.11.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   mime: 0.9.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   node_interop: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   node_io: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   node_preamble: 1.4.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  pedantic: 1.10.0-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   pool: 1.5.0-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   shelf: 0.7.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   shelf_packages_handler: 2.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -119,6 +77,7 @@
   shelf_web_socket: 0.2.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   source_map_stack_trace: 2.1.0-nullsafety.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   source_maps: 0.10.10-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  string_scanner: 1.1.0-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   test_api: 0.2.19-nullsafety.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   test_core: 0.3.12-nullsafety.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   vm_service: 4.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -249,48 +208,6 @@
     - packages/flutter_gallery_assets/splash_effects/splash_effect_8.gif
     - packages/flutter_gallery_assets/splash_effects/splash_effect_9.gif
     - packages/flutter_gallery_assets/splash_effects/splash_effect_10.gif
-    - packages/rally_assets/logo.png
-    - packages/rally_assets/thumb.png
-    - packages/shrine_images/diamond.png
-    - packages/shrine_images/slanted_menu.png
-    - packages/shrine_images/0-0.jpg
-    - packages/shrine_images/1-0.jpg
-    - packages/shrine_images/2-0.jpg
-    - packages/shrine_images/3-0.jpg
-    - packages/shrine_images/4-0.jpg
-    - packages/shrine_images/5-0.jpg
-    - packages/shrine_images/6-0.jpg
-    - packages/shrine_images/7-0.jpg
-    - packages/shrine_images/8-0.jpg
-    - packages/shrine_images/9-0.jpg
-    - packages/shrine_images/10-0.jpg
-    - packages/shrine_images/11-0.jpg
-    - packages/shrine_images/12-0.jpg
-    - packages/shrine_images/13-0.jpg
-    - packages/shrine_images/14-0.jpg
-    - packages/shrine_images/15-0.jpg
-    - packages/shrine_images/16-0.jpg
-    - packages/shrine_images/17-0.jpg
-    - packages/shrine_images/18-0.jpg
-    - packages/shrine_images/19-0.jpg
-    - packages/shrine_images/20-0.jpg
-    - packages/shrine_images/21-0.jpg
-    - packages/shrine_images/22-0.jpg
-    - packages/shrine_images/23-0.jpg
-    - packages/shrine_images/24-0.jpg
-    - packages/shrine_images/25-0.jpg
-    - packages/shrine_images/26-0.jpg
-    - packages/shrine_images/27-0.jpg
-    - packages/shrine_images/28-0.jpg
-    - packages/shrine_images/29-0.jpg
-    - packages/shrine_images/30-0.jpg
-    - packages/shrine_images/31-0.jpg
-    - packages/shrine_images/32-0.jpg
-    - packages/shrine_images/33-0.jpg
-    - packages/shrine_images/34-0.jpg
-    - packages/shrine_images/35-0.jpg
-    - packages/shrine_images/36-0.jpg
-    - packages/shrine_images/37-0.jpg
 
   # The following font is required for running Flutter Gallery benchmarks.
   fonts:
@@ -298,4 +215,4 @@
       fonts:
         - asset: packages/flutter_gallery_assets/fonts/GalleryIcons.ttf
 
-# PUBSPEC CHECKSUM: 7d7b
+# PUBSPEC CHECKSUM: c81b
diff --git a/dev/benchmarks/macrobenchmarks/test/frame_policy.dart b/dev/benchmarks/macrobenchmarks/test/frame_policy.dart
index 93e4a0a..1290185 100644
--- a/dev/benchmarks/macrobenchmarks/test/frame_policy.dart
+++ b/dev/benchmarks/macrobenchmarks/test/frame_policy.dart
@@ -7,13 +7,13 @@
 import 'package:flutter/gestures.dart';
 import 'package:flutter/scheduler.dart';
 import 'package:flutter_test/flutter_test.dart';
-import 'package:e2e/e2e.dart';
+import 'package:integration_test/integration_test.dart';
 
 import 'package:macrobenchmarks/src/simple_scroll.dart';
 
 void main() {
-  final E2EWidgetsFlutterBinding binding =
-      E2EWidgetsFlutterBinding.ensureInitialized() as E2EWidgetsFlutterBinding;
+  final IntegrationTestWidgetsFlutterBinding binding =
+      IntegrationTestWidgetsFlutterBinding.ensureInitialized() as IntegrationTestWidgetsFlutterBinding;
   testWidgets(
     'Frame Counter and Input Delay for benchmarkLive',
     (WidgetTester tester) async {
diff --git a/dev/benchmarks/macrobenchmarks/test/util.dart b/dev/benchmarks/macrobenchmarks/test/util.dart
index ce236b0..f3c5b8a 100644
--- a/dev/benchmarks/macrobenchmarks/test/util.dart
+++ b/dev/benchmarks/macrobenchmarks/test/util.dart
@@ -2,14 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:ui';
-
-import 'package:flutter/scheduler.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:flutter/widgets.dart';
 
 import 'package:macrobenchmarks/common.dart';
-import 'package:e2e/e2e.dart';
+import 'package:integration_test/integration_test.dart';
 import 'package:macrobenchmarks/main.dart' as app;
 
 typedef ControlCallback = Future<void> Function(WidgetController controller);
@@ -23,9 +20,9 @@
   ControlCallback body,
   ControlCallback setup,
 }) {
-  final WidgetsBinding _binding = E2EWidgetsFlutterBinding.ensureInitialized();
-  assert(_binding is E2EWidgetsFlutterBinding);
-  final E2EWidgetsFlutterBinding binding = _binding as E2EWidgetsFlutterBinding;
+  final WidgetsBinding _binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+  assert(_binding is IntegrationTestWidgetsFlutterBinding);
+  final IntegrationTestWidgetsFlutterBinding binding = _binding as IntegrationTestWidgetsFlutterBinding;
   binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.benchmarkLive;
 
   testWidgets(testName, (WidgetTester tester) async {
@@ -60,7 +57,7 @@
       await setup(tester);
     }
 
-    await watchPerformance(binding, () async {
+    await binding.watchPerformance(() async {
       final Future<void> durationFuture = tester.binding.delayed(duration);
       if (body != null) {
         await body(tester);
@@ -69,29 +66,3 @@
     });
   }, semanticsEnabled: false, timeout: Timeout(timeout));
 }
-
-bool _firstRun = true;
-
-// TODO(CareF): move this to e2e after FrameTimingSummarizer goes into stable
-// branch (#63537)
-/// watches the [FrameTiming] of `action` and report it to the e2e binding.
-Future<void> watchPerformance(
-  E2EWidgetsFlutterBinding binding,
-  Future<void> action(), {
-  String reportKey = 'performance',
-}) async {
-  assert(() {
-    if (_firstRun) {
-      debugPrint(kDebugWarning);
-      _firstRun = false;
-    }
-    return true;
-  }());
-  final List<FrameTiming> frameTimings = <FrameTiming>[];
-  final TimingsCallback watcher = frameTimings.addAll;
-  binding.addTimingsCallback(watcher);
-  await action();
-  binding.removeTimingsCallback(watcher);
-  final FrameTimingSummarizer frameTimes = FrameTimingSummarizer(frameTimings);
-  binding.reportData = <String, dynamic>{reportKey: frameTimes.summary};
-}
diff --git a/dev/benchmarks/macrobenchmarks/test_driver/e2e_test.dart b/dev/benchmarks/macrobenchmarks/test_driver/e2e_test.dart
index a7d388e..d931eff 100644
--- a/dev/benchmarks/macrobenchmarks/test_driver/e2e_test.dart
+++ b/dev/benchmarks/macrobenchmarks/test_driver/e2e_test.dart
@@ -2,9 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'package:e2e/e2e_driver.dart' as driver;
+import 'package:integration_test/integration_test_driver.dart' as driver;
 
-Future<void> main() => driver.e2eDriver(
+Future<void> main() => driver.integrationDriver(
   timeout: const Duration(minutes: 5),
   responseDataCallback: (Map<String, dynamic> data) async {
     await driver.writeResponseData(
diff --git a/dev/benchmarks/macrobenchmarks/test_driver/frame_policy_test.dart b/dev/benchmarks/macrobenchmarks/test_driver/frame_policy_test.dart
index 4e93f34..1d1318e 100644
--- a/dev/benchmarks/macrobenchmarks/test_driver/frame_policy_test.dart
+++ b/dev/benchmarks/macrobenchmarks/test_driver/frame_policy_test.dart
@@ -2,29 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:convert';
 import 'dart:io';
 
-import 'package:e2e/common.dart' as e2e;
-import 'package:flutter_driver/flutter_driver.dart';
+import 'package:integration_test/integration_test_driver.dart' as driver;
 
-import 'package:path/path.dart' as path;
+Future<void> main() => driver.integrationDriver(
+  timeout: const Duration(minutes: 1),
+  responseDataCallback: (Map<String, dynamic> data) async {
+    final Map<String, dynamic> benchmarkLiveResult =
+        data['benchmarkLive'] as Map<String,dynamic>;
+    final Map<String, dynamic> fullyLiveResult =
+        data['fullyLive'] as Map<String,dynamic>;
 
-Future<void> main() async {
-  const Duration timeout = Duration(minutes: 1);
-  const String testName = 'frame_policy';
-
-  final FlutterDriver driver = await FlutterDriver.connect();
-  String jsonResult;
-  jsonResult = await driver.requestData(null, timeout: timeout);
-  final e2e.Response response = e2e.Response.fromJson(jsonResult);
-  await driver.close();
-  final Map<String, dynamic> benchmarkLiveResult =
-      response.data['benchmarkLive'] as Map<String,dynamic>;
-  final Map<String, dynamic> fullyLiveResult =
-      response.data['fullyLive'] as Map<String,dynamic>;
-
-  if (response.allTestsPassed) {
     if(benchmarkLiveResult['frame_count'] as int < 10
        || fullyLiveResult['frame_count'] as int < 10) {
       print('Failure Details:\nNot Enough frames collected:'
@@ -32,22 +21,12 @@
             '${fullyLiveResult['frameCount']}.');
       exit(1);
     }
-    print('All tests passed.');
-    const String destinationDirectory = 'build';
-    await fs.directory(destinationDirectory).create(recursive: true);
-    final File file = fs.file(path.join(
-      destinationDirectory,
-      '${testName}_event_delay.json'
-    ));
-    await file.writeAsString(const JsonEncoder.withIndent('  ').convert(
+    await driver.writeResponseData(
       <String, dynamic>{
         'benchmarkLive': benchmarkLiveResult,
         'fullyLive': fullyLiveResult,
       },
-    ));
-    exit(0);
-  } else {
-    print('Failure Details:\n${response.formattedFailureDetails}');
-    exit(1);
+      testOutputFilename: 'frame_policy_event_delay',
+    );
   }
-}
+);
diff --git a/dev/bots/docs.sh b/dev/bots/docs.sh
index 21dbde1..a22faa2 100755
--- a/dev/bots/docs.sh
+++ b/dev/bots/docs.sh
@@ -20,7 +20,7 @@
     # Install and activate dartdoc.
     # NOTE: When updating to a new dartdoc version, please also update
     # `dartdoc_options.yaml` to include newly introduced error and warning types.
-    "$PUB" global activate dartdoc 0.35.0
+    "$PUB" global activate dartdoc 0.36.0
 
     # This script generates a unified doc set, and creates
     # a custom index.html, placing everything into dev/docs/doc.
diff --git a/dev/bots/run_command.dart b/dev/bots/run_command.dart
index 413fdd4..bc04450 100644
--- a/dev/bots/run_command.dart
+++ b/dev/bots/run_command.dart
@@ -2,92 +2,113 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'dart:async';
 import 'dart:convert';
 import 'dart:core' hide print;
-import 'dart:io' hide exit;
+import 'dart:io' as io;
 
 import 'package:path/path.dart' as path;
 
 import 'utils.dart';
 
-// TODO(ianh): These two functions should be refactored into something that avoids all this code duplication.
-
+/// Runs the `executable` and returns standard output as a stream of lines.
+///
+/// The returned stream reaches its end immediately after the command exits.
+///
+/// If `expectNonZeroExit` is false and the process exits with a non-zero exit
+/// code fails the test immediately by exiting the test process with exit code
+/// 1.
 Stream<String> runAndGetStdout(String executable, List<String> arguments, {
   String workingDirectory,
   Map<String, String> environment,
   bool expectNonZeroExit = false,
-  int expectedExitCode,
-  String failureMessage,
-  bool skip = false,
 }) async* {
-  final String commandDescription = '${path.relative(executable, from: workingDirectory)} ${arguments.join(' ')}';
-  final String relativeWorkingDir = path.relative(workingDirectory);
-  if (skip) {
-    printProgress('SKIPPING', relativeWorkingDir, commandDescription);
-    return;
-  }
-  printProgress('RUNNING', relativeWorkingDir, commandDescription);
-
-  final Stopwatch time = Stopwatch()..start();
-  final Process process = await Process.start(executable, arguments,
+  final StreamController<String> output = StreamController<String>();
+  final Future<CommandResult> command = runCommand(
+    executable,
+    arguments,
     workingDirectory: workingDirectory,
     environment: environment,
+    expectNonZeroExit: expectNonZeroExit,
+    // Capture the output so it's not printed to the console by default.
+    outputMode: OutputMode.capture,
+    outputListener: (String line, io.Process process) {
+      output.add(line);
+    },
   );
 
-  stderr.addStream(process.stderr);
-  final Stream<String> lines = process.stdout.transform(utf8.decoder).transform(const LineSplitter());
-  yield* lines;
+  // Close the stream controller after the command is complete. Otherwise,
+  // the yield* will never finish.
+  command.whenComplete(output.close);
 
-  final int exitCode = await process.exitCode;
-  if ((exitCode == 0) == expectNonZeroExit || (expectedExitCode != null && exitCode != expectedExitCode)) {
-    exitWithError(<String>[
-      if (failureMessage != null)
-        failureMessage
-      else
-        '${bold}ERROR: ${red}Last command exited with $exitCode (expected: ${expectNonZeroExit ? (expectedExitCode ?? 'non-zero') : 'zero'}).$reset',
-      '${bold}Command: $green$commandDescription$reset',
-      '${bold}Relative working directory: $cyan$relativeWorkingDir$reset',
-    ]);
-  }
-  print('$clock ELAPSED TIME: ${prettyPrintDuration(time.elapsed)} for $green$commandDescription$reset in $cyan$relativeWorkingDir$reset');
+  yield* output.stream;
 }
 
-/// Runs the `executable` and waits until the process exits.
-///
-/// If the process exits with a non-zero exit code, exits this process with
-/// exit code 1, unless `expectNonZeroExit` is set to true.
+/// Represents a running process launched using [startCommand].
+class Command {
+  Command._(this.process, this._time, this._savedStdout, this._savedStderr);
+
+  /// The raw process that was launched for this command.
+  final io.Process process;
+
+  final Stopwatch _time;
+  final Future<List<List<int>>> _savedStdout;
+  final Future<List<List<int>>> _savedStderr;
+
+  /// Evaluates when the [process] exits.
+  ///
+  /// Returns the result of running the command.
+  Future<CommandResult> get onExit async {
+    final int exitCode = await process.exitCode;
+    _time.stop();
+
+    // Saved output is null when OutputMode.print is used.
+    final String flattenedStdout = _savedStdout != null ? _flattenToString(await _savedStdout) : null;
+    final String flattenedStderr = _savedStderr != null ? _flattenToString(await _savedStderr) : null;
+    return CommandResult._(exitCode, _time.elapsed, flattenedStdout, flattenedStderr);
+  }
+}
+
+/// The result of running a command using [startCommand] and [runCommand];
+class CommandResult {
+  CommandResult._(this.exitCode, this.elapsedTime, this.flattenedStdout, this.flattenedStderr);
+
+  /// The exit code of the process.
+  final int exitCode;
+
+  /// The amount of time it took the process to complete.
+  final Duration elapsedTime;
+
+  /// Standard output decoded as a string using UTF8 decoder.
+  final String flattenedStdout;
+
+  /// Standard error output decoded as a string using UTF8 decoder.
+  final String flattenedStderr;
+}
+
+/// Starts the `executable` and returns a command object representing the
+/// running process.
 ///
 /// `outputListener` is called for every line of standard output from the
 /// process, and is given the [Process] object. This can be used to interrupt
 /// an indefinitely running process, for example, by waiting until the process
 /// emits certain output.
-Future<void> runCommand(String executable, List<String> arguments, {
+///
+/// `outputMode` controls where the standard output from the command process
+/// goes. See [OutputMode].
+Future<Command> startCommand(String executable, List<String> arguments, {
   String workingDirectory,
   Map<String, String> environment,
-  bool expectNonZeroExit = false,
-  int expectedExitCode,
-  String failureMessage,
   OutputMode outputMode = OutputMode.print,
-  CapturedOutput output,
-  bool skip = false,
   bool Function(String) removeLine,
-  void Function(String, Process) outputListener,
+  void Function(String, io.Process) outputListener,
 }) async {
-  assert(
-    (outputMode == OutputMode.capture) == (output != null),
-    'The output parameter must be non-null with and only with OutputMode.capture',
-  );
-
   final String commandDescription = '${path.relative(executable, from: workingDirectory)} ${arguments.join(' ')}';
-  final String relativeWorkingDir = path.relative(workingDirectory ?? Directory.current.path);
-  if (skip) {
-    printProgress('SKIPPING', relativeWorkingDir, commandDescription);
-    return;
-  }
+  final String relativeWorkingDir = path.relative(workingDirectory ?? io.Directory.current.path);
   printProgress('RUNNING', relativeWorkingDir, commandDescription);
 
   final Stopwatch time = Stopwatch()..start();
-  final Process process = await Process.start(executable, arguments,
+  final io.Process process = await io.Process.start(executable, arguments,
     workingDirectory: workingDirectory,
     environment: environment,
   );
@@ -108,56 +129,103 @@
   switch (outputMode) {
     case OutputMode.print:
       await Future.wait<void>(<Future<void>>[
-        stdout.addStream(stdoutSource),
-        stderr.addStream(process.stderr),
+        io.stdout.addStream(stdoutSource),
+        io.stderr.addStream(process.stderr),
       ]);
       break;
     case OutputMode.capture:
-    case OutputMode.discard:
       savedStdout = stdoutSource.toList();
       savedStderr = process.stderr.toList();
       break;
   }
 
-  final int exitCode = await process.exitCode;
-  if (output != null) {
-    output.stdout = _flattenToString(await savedStdout);
-    output.stderr = _flattenToString(await savedStderr);
+  return Command._(process, time, savedStdout, savedStderr);
+}
+
+/// Runs the `executable` and waits until the process exits.
+///
+/// If the process exits with a non-zero exit code, exits this process with
+/// exit code 1, unless `expectNonZeroExit` is set to true.
+///
+/// `outputListener` is called for every line of standard output from the
+/// process, and is given the [Process] object. This can be used to interrupt
+/// an indefinitely running process, for example, by waiting until the process
+/// emits certain output.
+///
+/// Returns the result of the finished process, or null if `skip` is true.
+///
+/// `outputMode` controls where the standard output from the command process
+/// goes. See [OutputMode].
+Future<CommandResult> runCommand(String executable, List<String> arguments, {
+  String workingDirectory,
+  Map<String, String> environment,
+  bool expectNonZeroExit = false,
+  int expectedExitCode,
+  String failureMessage,
+  OutputMode outputMode = OutputMode.print,
+  bool skip = false,
+  bool Function(String) removeLine,
+  void Function(String, io.Process) outputListener,
+}) async {
+  final String commandDescription = '${path.relative(executable, from: workingDirectory)} ${arguments.join(' ')}';
+  final String relativeWorkingDir = path.relative(workingDirectory ?? io.Directory.current.path);
+  if (skip) {
+    printProgress('SKIPPING', relativeWorkingDir, commandDescription);
+    return null;
   }
 
-  if ((exitCode == 0) == expectNonZeroExit || (expectedExitCode != null && exitCode != expectedExitCode)) {
+  final Command command = await startCommand(executable, arguments,
+    workingDirectory: workingDirectory,
+    environment: environment,
+    outputMode: outputMode,
+    removeLine: removeLine,
+    outputListener: outputListener,
+  );
+
+  final CommandResult result = await command.onExit;
+
+  if ((result.exitCode == 0) == expectNonZeroExit || (expectedExitCode != null && result.exitCode != expectedExitCode)) {
     // Print the output when we get unexpected results (unless output was
     // printed already).
     switch (outputMode) {
       case OutputMode.print:
         break;
       case OutputMode.capture:
-      case OutputMode.discard:
-        stdout.writeln(_flattenToString(await savedStdout));
-        stderr.writeln(_flattenToString(await savedStderr));
+        io.stdout.writeln(result.flattenedStdout);
+        io.stderr.writeln(result.flattenedStderr);
         break;
     }
     exitWithError(<String>[
       if (failureMessage != null)
         failureMessage
       else
-        '${bold}ERROR: ${red}Last command exited with $exitCode (expected: ${expectNonZeroExit ? (expectedExitCode ?? 'non-zero') : 'zero'}).$reset',
+        '${bold}ERROR: ${red}Last command exited with ${result.exitCode} (expected: ${expectNonZeroExit ? (expectedExitCode ?? 'non-zero') : 'zero'}).$reset',
       '${bold}Command: $green$commandDescription$reset',
       '${bold}Relative working directory: $cyan$relativeWorkingDir$reset',
     ]);
   }
-  print('$clock ELAPSED TIME: ${prettyPrintDuration(time.elapsed)} for $green$commandDescription$reset in $cyan$relativeWorkingDir$reset');
+  print('$clock ELAPSED TIME: ${prettyPrintDuration(result.elapsedTime)} for $green$commandDescription$reset in $cyan$relativeWorkingDir$reset');
+  return result;
 }
 
 /// Flattens a nested list of UTF-8 code units into a single string.
 String _flattenToString(List<List<int>> chunks) =>
   utf8.decode(chunks.expand<int>((List<int> ints) => ints).toList());
 
-/// Specifies what to do with command output from [runCommand].
-enum OutputMode { print, capture, discard }
+/// Specifies what to do with the command output from [runCommand] and [startCommand].
+enum OutputMode {
+  /// Forwards standard output and standard error streams to the test process'
+  /// respective standard streams.
+  ///
+  /// Use this mode if all you want is print the output of the command to the
+  /// console. The output is no longer available after the process exits.
+  print,
 
-/// Stores command output from [runCommand] when used with [OutputMode.capture].
-class CapturedOutput {
-  String stdout;
-  String stderr;
+  /// Saves standard output and standard error streams in memory.
+  ///
+  /// Captured output can be retrieved from the [CommandResult] object.
+  ///
+  /// Use this mode in tests that need to inspect the output of a command, or
+  /// when the output should not be printed to console.
+  capture,
 }
diff --git a/dev/bots/test.dart b/dev/bots/test.dart
index 84ba99a..b21dec1 100644
--- a/dev/bots/test.dart
+++ b/dev/bots/test.dart
@@ -26,7 +26,7 @@
 ///
 /// If the output does not match expectations, the function shall return an
 /// appropriate error message.
-typedef OutputChecker = String Function(CapturedOutput);
+typedef OutputChecker = String Function(CommandResult);
 
 final String exe = Platform.isWindows ? '.exe' : '';
 final String bat = Platform.isWindows ? '.bat' : '';
@@ -143,9 +143,8 @@
     return;
   }
   final String expectedVersion = File(engineVersionFile).readAsStringSync().trim();
-  final CapturedOutput flutterTesterOutput = CapturedOutput();
-  await runCommand(flutterTester, <String>['--help'], output: flutterTesterOutput, outputMode: OutputMode.capture);
-  final String actualVersion = flutterTesterOutput.stderr.split('\n').firstWhere((final String line) {
+  final CommandResult result = await runCommand(flutterTester, <String>['--help'], outputMode: OutputMode.capture);
+  final String actualVersion = result.flattenedStderr.split('\n').firstWhere((final String line) {
     return line.startsWith('Flutter Engine Version:');
   });
   if (!actualVersion.contains(expectedVersion)) {
@@ -190,8 +189,8 @@
     script: path.join('test_smoke_test', 'pending_timer_fail_test.dart'),
     expectFailure: true,
     printOutput: false,
-    outputChecker: (CapturedOutput output) {
-      return output.stdout.contains('failingPendingTimerTest')
+    outputChecker: (CommandResult result) {
+      return result.flattenedStdout.contains('failingPendingTimerTest')
         ? null
         : 'Failed to find the stack trace for the pending Timer.';
     }
@@ -230,7 +229,7 @@
         <String>['drive', '--use-existing-app', '-t', path.join('test_driver', 'failure.dart')],
         workingDirectory: path.join(flutterRoot, 'packages', 'flutter_driver'),
         expectNonZeroExit: true,
-        outputMode: OutputMode.discard,
+        outputMode: OutputMode.capture,
       ),
     ],
   );
@@ -287,7 +286,7 @@
       '--report-on=lib/'
     ],
     workingDirectory: toolRoot,
-    outputMode: OutputMode.discard,
+    outputMode: OutputMode.capture,
   );
 }
 
@@ -529,7 +528,8 @@
 
 Future<void> _runFrameworkTests() async {
   final bq.BigqueryApi bigqueryApi = await _getBigqueryApi();
-  final List<String> nullSafetyOptions = <String>['--null-assertions', '--no-sound-null-safety'];
+  final List<String> soundNullSafetyOptions     = <String>['--enable-experiment=non-nullable', '--null-assertions', '--sound-null-safety'];
+  final List<String> mixedModeNullSafetyOptions = <String>['--enable-experiment=non-nullable', '--null-assertions', '--no-sound-null-safety'];
   final List<String> trackWidgetCreationAlternatives = <String>['--track-widget-creation', '--no-track-widget-creation'];
 
   Future<void> runWidgets() async {
@@ -537,7 +537,7 @@
     for (final String trackWidgetCreationOption in trackWidgetCreationAlternatives) {
       await _runFlutterTest(
         path.join(flutterRoot, 'packages', 'flutter'),
-        options: <String>[trackWidgetCreationOption, ...nullSafetyOptions],
+        options: <String>[trackWidgetCreationOption, ...soundNullSafetyOptions],
         tableData: bigqueryApi?.tabledata,
         tests: <String>[ path.join('test', 'widgets') + path.separator ],
       );
@@ -563,7 +563,7 @@
     for (final String trackWidgetCreationOption in trackWidgetCreationAlternatives) {
       await _runFlutterTest(
         path.join(flutterRoot, 'packages', 'flutter'),
-        options: <String>[trackWidgetCreationOption, ...nullSafetyOptions],
+        options: <String>[trackWidgetCreationOption, ...soundNullSafetyOptions],
         tableData: bigqueryApi?.tabledata,
         tests: tests,
       );
@@ -614,14 +614,14 @@
     await _runFlutterTest(path.join(flutterRoot, 'dev', 'manual_tests'), tableData: bigqueryApi?.tabledata);
     await _runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'vitool'), tableData: bigqueryApi?.tabledata);
     await _runFlutterTest(path.join(flutterRoot, 'examples', 'hello_world'), tableData: bigqueryApi?.tabledata);
-    await _runFlutterTest(path.join(flutterRoot, 'examples', 'layers'), tableData: bigqueryApi?.tabledata);
+    await _runFlutterTest(path.join(flutterRoot, 'examples', 'layers'), tableData: bigqueryApi?.tabledata, options: mixedModeNullSafetyOptions);
     await _runFlutterTest(path.join(flutterRoot, 'dev', 'benchmarks', 'test_apps', 'stocks'), tableData: bigqueryApi?.tabledata);
     await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_driver'), tableData: bigqueryApi?.tabledata, tests: <String>[path.join('test', 'src', 'real_tests')]);
     await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_goldens'), tableData: bigqueryApi?.tabledata);
     await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_localizations'), tableData: bigqueryApi?.tabledata);
-    await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_test'), tableData: bigqueryApi?.tabledata, options: nullSafetyOptions);
+    await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_test'), tableData: bigqueryApi?.tabledata, options: soundNullSafetyOptions);
     await _runFlutterTest(path.join(flutterRoot, 'packages', 'fuchsia_remote_debug_protocol'), tableData: bigqueryApi?.tabledata);
-    await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'non_nullable'), options: nullSafetyOptions);
+    await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'non_nullable'), options: mixedModeNullSafetyOptions);
     await _runFlutterTest(
       path.join(flutterRoot, 'dev', 'tracing_tests'),
       options: <String>['--enable-vmservice'],
@@ -642,11 +642,11 @@
       script: path.join('test', 'bindings_test_failure.dart'),
       expectFailure: true,
       printOutput: false,
-      outputChecker: (CapturedOutput output) {
-        final Iterable<Match> matches = httpClientWarning.allMatches(output.stdout);
+      outputChecker: (CommandResult result) {
+        final Iterable<Match> matches = httpClientWarning.allMatches(result.flattenedStdout);
         if (matches == null || matches.isEmpty || matches.length > 1) {
           return 'Failed to print warning about HttpClientUsage, or printed it too many times.\n'
-                 'stdout:\n${output.stdout}';
+                 'stdout:\n${result.flattenedStdout}';
         }
         return null;
       },
@@ -868,9 +868,8 @@
   List<String> additionalArguments = const<String>[],
 }) async {
   final String testAppDirectory = path.join(flutterRoot, 'dev', 'integration_tests', 'web');
-  final CapturedOutput output = CapturedOutput();
   bool success = false;
-  await runCommand(
+  final CommandResult result = await runCommand(
     flutter,
     <String>[
       'run',
@@ -889,7 +888,6 @@
       '-t',
       target,
     ],
-    output: output,
     outputMode: OutputMode.capture,
     outputListener: (String line, Process process) {
       if (line.contains('--- TEST SUCCEEDED ---')) {
@@ -908,7 +906,8 @@
   if (success) {
     print('${green}Web stack trace integration test passed.$reset');
   } else {
-    print(output.stdout);
+    print(result.flattenedStdout);
+    print(result.flattenedStderr);
     print('${red}Web stack trace integration test failed.$reset');
     exit(1);
   }
@@ -1125,29 +1124,22 @@
   args.addAll(tests);
 
   if (!shouldProcessOutput) {
-    OutputMode outputMode = OutputMode.discard;
-    CapturedOutput output;
+    final OutputMode outputMode = outputChecker == null && printOutput
+      ? OutputMode.print
+      : OutputMode.capture;
 
-    if (outputChecker != null) {
-      outputMode = OutputMode.capture;
-      output = CapturedOutput();
-    } else if (printOutput) {
-      outputMode = OutputMode.print;
-    }
-
-    await runCommand(
+    final CommandResult result = await runCommand(
       flutter,
       args,
       workingDirectory: workingDirectory,
       expectNonZeroExit: expectFailure,
       outputMode: outputMode,
-      output: output,
       skip: skip,
       environment: environment,
     );
 
     if (outputChecker != null) {
-      final String message = outputChecker(output);
+      final String message = outputChecker(result);
       if (message != null)
         exitWithError(<String>[message]);
     }
diff --git a/dev/customer_testing/pubspec.yaml b/dev/customer_testing/pubspec.yaml
index 0471daa..8036429 100644
--- a/dev/customer_testing/pubspec.yaml
+++ b/dev/customer_testing/pubspec.yaml
@@ -2,7 +2,7 @@
 description: Tool to run the tests listed in the flutter/tests repository.
 
 environment:
-  sdk: any
+  sdk: '>=2.9.0 <3.0.0'
 
 dependencies:
   args: 1.6.0
diff --git a/dev/devicelab/bin/run.dart b/dev/devicelab/bin/run.dart
index 71badfd..2b34d5e 100644
--- a/dev/devicelab/bin/run.dart
+++ b/dev/devicelab/bin/run.dart
@@ -9,6 +9,7 @@
 import 'package:path/path.dart' as path;
 
 import 'package:flutter_devicelab/framework/ab.dart';
+import 'package:flutter_devicelab/framework/cocoon.dart';
 import 'package:flutter_devicelab/framework/manifest.dart';
 import 'package:flutter_devicelab/framework/runner.dart';
 import 'package:flutter_devicelab/framework/task_result.dart';
@@ -18,8 +19,8 @@
 
 List<String> _taskNames = <String>[];
 
-/// Suppresses standard output, prints only standard error output.
-bool silent;
+/// The device-id to run test on.
+String deviceId;
 
 /// The build of the local engine to use.
 ///
@@ -32,8 +33,13 @@
 /// Whether to exit on first test failure.
 bool exitOnFirstTestFailure;
 
-/// The device-id to run test on.
-String deviceId;
+/// File containing a service account token.
+///
+/// If passed, the test run results will be uploaded to Flutter infrastructure.
+String serviceAccountTokenFile;
+
+/// Suppresses standard output, prints only standard error output.
+bool silent;
 
 /// Runs tasks.
 ///
@@ -74,11 +80,12 @@
     return;
   }
 
-  silent = args['silent'] as bool;
+  deviceId = args['device-id'] as String;
   localEngine = args['local-engine'] as String;
   localEngineSrcPath = args['local-engine-src-path'] as String;
   exitOnFirstTestFailure = args['exit'] as bool;
-  deviceId = args['device-id'] as String;
+  serviceAccountTokenFile = args['service-account-token-file'] as String;
+  silent = args['silent'] as bool;
 
   if (args.wasParsed('ab')) {
     await _runABTest();
@@ -102,6 +109,11 @@
     print(const JsonEncoder.withIndent('  ').convert(result));
     section('Finished task "$taskName"');
 
+    if (serviceAccountTokenFile != null) {
+      final Cocoon cocoon = Cocoon(serviceAccountTokenPath: serviceAccountTokenFile);
+      await cocoon.sendTaskResult(taskName: taskName, result: result);
+    }
+
     if (!result.succeeded) {
       exitCode = 1;
       if (exitOnFirstTestFailure) {
@@ -335,6 +347,10 @@
           '`required_agent_capabilities`\nin the `manifest.yaml` file.',
   )
   ..addOption(
+    'service-account-token-file',
+    help: '[Flutter infrastructure] Authentication for uploading results.',
+  )
+  ..addOption(
     'stage',
     abbr: 's',
     help: 'Name of the stage. Runs all tasks for that stage. The tasks and\n'
diff --git a/dev/devicelab/bin/tasks/android_defines_test.dart b/dev/devicelab/bin/tasks/android_defines_test.dart
index ff4cdc7..c583efb 100644
--- a/dev/devicelab/bin/tasks/android_defines_test.dart
+++ b/dev/devicelab/bin/tasks/android_defines_test.dart
@@ -4,10 +4,10 @@
 
 import 'package:flutter_devicelab/framework/adb.dart';
 import 'package:flutter_devicelab/framework/framework.dart';
-import 'package:flutter_devicelab/tasks/defines_task.dart';
+import 'package:flutter_devicelab/tasks/integration_tests.dart';
 
 /// Verify that dart defines work on Android.
 Future<void> main() async {
   deviceOperatingSystem = DeviceOperatingSystem.android;
-  await task(runDartDefinesTask);
+  await task(dartDefinesTask());
 }
diff --git a/dev/devicelab/bin/tasks/commands_test.dart b/dev/devicelab/bin/tasks/commands_test.dart
deleted file mode 100644
index 057770c..0000000
--- a/dev/devicelab/bin/tasks/commands_test.dart
+++ /dev/null
@@ -1,134 +0,0 @@
-// Copyright 2014 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:async';
-import 'dart:convert';
-import 'dart:io';
-
-import 'package:path/path.dart' as path;
-import 'package:vm_service_client/vm_service_client.dart';
-
-import 'package:flutter_devicelab/framework/adb.dart';
-import 'package:flutter_devicelab/framework/framework.dart';
-import 'package:flutter_devicelab/framework/task_result.dart';
-import 'package:flutter_devicelab/framework/utils.dart';
-
-void main() {
-  task(() async {
-    int vmServicePort;
-
-    final Device device = await devices.workingDevice;
-    await device.unlock();
-    final Directory appDir = dir(path.join(flutterDirectory.path, 'dev/integration_tests/ui'));
-    await inDirectory(appDir, () async {
-      final Completer<void> ready = Completer<void>();
-      bool ok;
-      print('run: starting...');
-      final Process run = await startProcess(
-        path.join(flutterDirectory.path, 'bin', 'flutter'),
-        <String>['run', '--verbose', '--disable-service-auth-codes', '--no-fast-start', '-d', device.deviceId, 'lib/commands.dart'],
-      );
-      final StreamController<String> stdout = StreamController<String>.broadcast();
-      run.stdout
-        .transform<String>(utf8.decoder)
-        .transform<String>(const LineSplitter())
-        .listen((String line) {
-          print('run:stdout: $line');
-          stdout.add(line);
-          if (vmServicePort == null) {
-            vmServicePort = parseServicePort(line);
-            if (vmServicePort != null) {
-              print('service protocol connection available at port $vmServicePort');
-              print('run: ready!');
-              ready.complete();
-              ok ??= true;
-            }
-          }
-        });
-      run.stderr
-        .transform<String>(utf8.decoder)
-        .transform<String>(const LineSplitter())
-        .listen((String line) {
-          stderr.writeln('run:stderr: $line');
-        });
-      run.exitCode.then<void>((int exitCode) { ok = false; });
-      await Future.any<dynamic>(<Future<dynamic>>[ ready.future, run.exitCode ]);
-      if (!ok)
-        throw 'Failed to run test app.';
-
-      final VMServiceClient client = VMServiceClient.connect('ws://localhost:$vmServicePort/ws');
-
-      final DriveHelper driver = DriveHelper(vmServicePort);
-
-      await driver.drive('none');
-      print('test: pressing "p" to enable debugPaintSize...');
-      run.stdin.write('p');
-      await driver.drive('debug_paint');
-      print('test: pressing "p" again...');
-      run.stdin.write('p');
-      await driver.drive('none');
-      print('test: pressing "P" to enable performance overlay...');
-      run.stdin.write('P');
-      await driver.drive('performance_overlay');
-      print('test: pressing "P" again...');
-      run.stdin.write('P');
-      await driver.drive('none');
-      final Future<String> reloadStartingText =
-        stdout.stream.firstWhere((String line) => line.endsWith('] Performing hot reload...'));
-      final Future<String> reloadEndingText =
-        stdout.stream.firstWhere((String line) => line.contains('] Reloaded ') && line.endsWith('ms.'));
-      print('test: pressing "r" to perform a hot reload...');
-      run.stdin.write('r');
-      await reloadStartingText;
-      await reloadEndingText;
-      await driver.drive('none');
-      final Future<String> restartStartingText =
-        stdout.stream.firstWhere((String line) => line.endsWith('Performing hot restart...'));
-      final Future<String> restartEndingText =
-        stdout.stream.firstWhere((String line) => line.contains('] Restarted application in '));
-      print('test: pressing "R" to perform a full reload...');
-      run.stdin.write('R');
-      await restartStartingText;
-      await restartEndingText;
-      await driver.drive('none');
-      run.stdin.write('q');
-      final int result = await run.exitCode;
-      if (result != 0)
-        throw 'Received unexpected exit code $result from run process.';
-      print('test: validating that the app has in fact closed...');
-      await client.done.timeout(const Duration(seconds: 5));
-    });
-    return TaskResult.success(null);
-  });
-}
-
-class DriveHelper {
-  DriveHelper(this.vmServicePort);
-
-  final int vmServicePort;
-
-  Future<void> drive(String name) async {
-    print('drive: running commands_$name check...');
-    final Process drive = await startProcess(
-      path.join(flutterDirectory.path, 'bin', 'flutter'),
-      <String>['drive', '--use-existing-app', 'http://127.0.0.1:$vmServicePort/', '--keep-app-running', '--driver', 'test_driver/commands_${name}_test.dart'],
-    );
-    drive.stdout
-        .transform<String>(utf8.decoder)
-        .transform<String>(const LineSplitter())
-        .listen((String line) {
-      print('drive:stdout: $line');
-    });
-    drive.stderr
-        .transform<String>(utf8.decoder)
-        .transform<String>(const LineSplitter())
-        .listen((String line) {
-      stderr.writeln('drive:stderr: $line');
-    });
-    final int result = await drive.exitCode;
-    if (result != 0)
-      throw 'Failed to drive test app (exit code $result).';
-    print('drive: finished commands_$name check successfully.');
-  }
-}
diff --git a/dev/devicelab/bin/tasks/complex_layout_semantics_perf.dart b/dev/devicelab/bin/tasks/complex_layout_semantics_perf.dart
index 83316b7..ade3b84 100644
--- a/dev/devicelab/bin/tasks/complex_layout_semantics_perf.dart
+++ b/dev/devicelab/bin/tasks/complex_layout_semantics_perf.dart
@@ -21,6 +21,7 @@
 
     await inDirectory(complexLayoutPath, () async {
       await flutter('drive', options: <String>[
+        '--no-android-gradle-daemon',
         '-v',
         '--profile',
         '--trace-startup', // Enables "endless" timeline event buffering.
diff --git a/dev/devicelab/bin/tasks/flutter_gallery_sksl_warmup_ios32__transition_perf.dart b/dev/devicelab/bin/tasks/flutter_gallery_sksl_warmup_ios32__transition_perf.dart
deleted file mode 100644
index 60fc9b5..0000000
--- a/dev/devicelab/bin/tasks/flutter_gallery_sksl_warmup_ios32__transition_perf.dart
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright 2014 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:flutter_devicelab/tasks/perf_tests.dart';
-import 'package:flutter_devicelab/framework/adb.dart';
-import 'package:flutter_devicelab/framework/framework.dart';
-
-Future<void> main() async {
-  deviceOperatingSystem = DeviceOperatingSystem.ios;
-  await task(createFlutterGalleryTransitionsPerfSkSLWarmupTest());
-}
diff --git a/dev/devicelab/bin/tasks/gradle_plugin_bundle_test.dart b/dev/devicelab/bin/tasks/gradle_plugin_bundle_test.dart
index a525d30..c5add22 100644
--- a/dev/devicelab/bin/tasks/gradle_plugin_bundle_test.dart
+++ b/dev/devicelab/bin/tasks/gradle_plugin_bundle_test.dart
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:io';
-
 import 'package:flutter_devicelab/framework/apk_utils.dart';
 import 'package:flutter_devicelab/framework/framework.dart';
 import 'package:flutter_devicelab/framework/task_result.dart';
@@ -20,7 +18,12 @@
     try {
       await runProjectTest((FlutterProject project) async {
         section('App bundle content for task bundleRelease without explicit target platform');
-        await project.runGradleTask('bundleRelease');
+
+        await inDirectory(project.rootPath, () {
+          return flutter('build', options: <String>[
+            'appbundle',
+          ]);
+        });
 
         final String releaseBundle = path.join(
           project.rootPath,
@@ -42,10 +45,6 @@
       });
 
       await runProjectTest((FlutterProject project) async {
-        if (Platform.isWindows) {
-          // https://github.com/flutter/flutter/issues/42985
-          return;
-        }
         section('App bundle content using flavors without explicit target platform');
         // Add a few flavors.
         await project.addProductFlavors(<String> [
@@ -55,7 +54,13 @@
           'flavor_underscore', // https://github.com/flutter/flutter/issues/36067
         ]);
         // Build the production flavor in release mode.
-        await project.runGradleTask('bundleProductionRelease');
+        await inDirectory(project.rootPath, () {
+          return flutter('build', options: <String>[
+            'appbundle',
+            '--flavor',
+            'production',
+          ]);
+        });
 
         final String bundleFromGradlePath = path.join(
           project.rootPath,
@@ -77,9 +82,8 @@
 
         section('Build app bundle using the flutter tool - flavor: flavor_underscore');
 
-        int exitCode;
-        await inDirectory(project.rootPath, () async {
-          exitCode = await flutter(
+        int exitCode = await inDirectory(project.rootPath, ()  {
+          return flutter(
             'build',
             options: <String>[
               'appbundle',
@@ -113,8 +117,8 @@
 
         section('Build app bundle using the flutter tool - flavor: production');
 
-        await inDirectory(project.rootPath, () async {
-          exitCode = await flutter(
+        exitCode = await inDirectory(project.rootPath, () {
+          return flutter(
             'build',
             options: <String>[
               'appbundle',
@@ -149,8 +153,16 @@
 
       await runProjectTest((FlutterProject project) async {
         section('App bundle content for task bundleRelease with target platform = android-arm');
-        await project.runGradleTask('bundleRelease',
-            options: <String>['-Ptarget-platform=android-arm']);
+
+        await inDirectory(project.rootPath, () {
+          return flutter(
+            'build',
+            options: <String>[
+              'appbundle',
+              '--target-platform=android-arm',
+            ],
+          );
+        });
 
         final String releaseBundle = path.join(
           project.rootPath,
diff --git a/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart b/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart
index 634dd9f..af5f89f 100644
--- a/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart
+++ b/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart
@@ -15,7 +15,15 @@
     try {
       await runPluginProjectTest((FlutterPluginProject pluginProject) async {
         section('APK content for task assembleDebug without explicit target platform');
-        await pluginProject.runGradleTask('assembleDebug');
+        await inDirectory(pluginProject.exampleAndroidPath, () {
+          return flutter(
+            'build',
+            options: <String>[
+              'apk',
+              '--debug',
+            ],
+          );
+        });
 
         final Iterable<String> apkFiles = await getFilesInApk(pluginProject.debugApkPath);
 
@@ -40,7 +48,16 @@
 
       await runPluginProjectTest((FlutterPluginProject pluginProject) async {
         section('APK content for task assembleRelease without explicit target platform');
-        await pluginProject.runGradleTask('assembleRelease');
+
+        await inDirectory(pluginProject.exampleAndroidPath, () {
+          return flutter(
+            'build',
+            options: <String>[
+              'apk',
+              '--release',
+            ],
+          );
+        });
 
         final Iterable<String> apkFiles = await getFilesInApk(pluginProject.releaseApkPath);
 
@@ -60,8 +77,17 @@
 
       await runPluginProjectTest((FlutterPluginProject pluginProject) async {
         section('APK content for task assembleRelease with target platform = android-arm, android-arm64');
-        await pluginProject.runGradleTask('assembleRelease',
-            options: <String>['-Ptarget-platform=android-arm,android-arm64']);
+
+        await inDirectory(pluginProject.exampleAndroidPath, () {
+          return flutter(
+            'build',
+            options: <String>[
+              'apk',
+              '--release',
+              '--target-platform=android-arm,android-arm64'
+            ],
+          );
+        });
 
         final Iterable<String> apkFiles = await getFilesInApk(pluginProject.releaseApkPath);
 
@@ -80,8 +106,18 @@
       await runPluginProjectTest((FlutterPluginProject pluginProject) async {
         section('APK content for task assembleRelease with '
                 'target platform = android-arm, android-arm64 and split per ABI');
-        await pluginProject.runGradleTask('assembleRelease',
-            options: <String>['-Ptarget-platform=android-arm,android-arm64', '-Psplit-per-abi=true']);
+
+        await inDirectory(pluginProject.exampleAndroidPath, () {
+          return flutter(
+            'build',
+            options: <String>[
+              'apk',
+              '--release',
+              '--split-per-abi',
+              '--target-platform=android-arm,android-arm64',
+            ],
+          );
+        });
 
         final Iterable<String> armApkFiles = await getFilesInApk(pluginProject.releaseArmApkPath);
 
@@ -108,7 +144,16 @@
 
       await runProjectTest((FlutterProject project) async {
         section('gradlew assembleRelease');
-        await project.runGradleTask('assembleRelease');
+
+        await inDirectory(project.rootPath, () {
+          return flutter(
+            'build',
+            options: <String>[
+              'apk',
+              '--release',
+            ],
+          );
+        });
 
         // When the platform-target isn't specified, we generate the snapshots
         // for arm and arm64.
diff --git a/dev/devicelab/bin/tasks/gradle_plugin_light_apk_test.dart b/dev/devicelab/bin/tasks/gradle_plugin_light_apk_test.dart
index 35280f7..f33e133 100644
--- a/dev/devicelab/bin/tasks/gradle_plugin_light_apk_test.dart
+++ b/dev/devicelab/bin/tasks/gradle_plugin_light_apk_test.dart
@@ -14,8 +14,17 @@
     try {
       await runPluginProjectTest((FlutterPluginProject pluginProject) async {
         section('APK content for task assembleDebug with target platform = android-arm');
-        await pluginProject.runGradleTask('assembleDebug',
-            options: <String>['-Ptarget-platform=android-arm']);
+
+        await inDirectory(pluginProject.exampleAndroidPath, () {
+          return flutter(
+            'build',
+            options: <String>[
+              'apk',
+              '--debug',
+              '--target-platform=android-arm'
+            ],
+          );
+        });
 
         final Iterable<String> apkFiles = await getFilesInApk(pluginProject.debugApkPath);
 
@@ -40,8 +49,16 @@
       await runPluginProjectTest((FlutterPluginProject pluginProject) async {
         section('APK content for task assembleDebug with target platform = android-x86');
         // This is used by `flutter run`
-        await pluginProject.runGradleTask('assembleDebug',
-            options: <String>['-Ptarget-platform=android-x86']);
+        await inDirectory(pluginProject.exampleAndroidPath, () {
+          return flutter(
+            'build',
+            options: <String>[
+              'apk',
+              '--debug',
+              '--target-platform=android-x86'
+            ],
+          );
+        });
 
         final Iterable<String> apkFiles = await getFilesInApk(pluginProject.debugApkPath);
 
@@ -64,8 +81,17 @@
       await runPluginProjectTest((FlutterPluginProject pluginProject) async {
         section('APK content for task assembleDebug with target platform = android-x64');
         // This is used by `flutter run`
-        await pluginProject.runGradleTask('assembleDebug',
-            options: <String>['-Ptarget-platform=android-x64']);
+
+        await inDirectory(pluginProject.exampleAndroidPath, () {
+          return flutter(
+            'build',
+            options: <String>[
+              'apk',
+              '--debug',
+              '--target-platform=android-x64'
+            ],
+          );
+        });
 
         final Iterable<String> apkFiles = await getFilesInApk(pluginProject.debugApkPath);
 
@@ -87,8 +113,17 @@
 
       await runPluginProjectTest((FlutterPluginProject pluginProject) async {
         section('APK content for task assembleRelease with target platform = android-arm');
-        await pluginProject.runGradleTask('assembleRelease',
-            options: <String>['-Ptarget-platform=android-arm']);
+
+        await inDirectory(pluginProject.exampleAndroidPath, () {
+          return flutter(
+            'build',
+            options: <String>[
+              'apk',
+              '--release',
+              '--target-platform=android-arm'
+            ],
+          );
+        });
 
         final Iterable<String> apkFiles = await getFilesInApk(pluginProject.releaseApkPath);
 
@@ -108,8 +143,17 @@
 
       await runPluginProjectTest((FlutterPluginProject pluginProject) async {
         section('APK content for task assembleRelease with target platform = android-arm64');
-        await pluginProject.runGradleTask('assembleRelease',
-            options: <String>['-Ptarget-platform=android-arm64']);
+
+        await inDirectory(pluginProject.exampleAndroidPath, () {
+          return flutter(
+            'build',
+            options: <String>[
+              'apk',
+              '--release',
+              '--target-platform=android-arm64'
+            ],
+          );
+        });
 
         final Iterable<String> apkFiles = await getFilesInApk(pluginProject.releaseApkPath);
 
@@ -129,7 +173,15 @@
 
       await runProjectTest((FlutterProject project) async {
         section('gradlew assembleDebug');
-        await project.runGradleTask('assembleDebug');
+        await inDirectory(project.rootPath, () {
+          return flutter(
+            'build',
+            options: <String>[
+              'apk',
+              '--debug',
+            ],
+          );
+        });
         final String errorMessage = validateSnapshotDependency(project, 'kernel_blob.bin');
         if (errorMessage != null) {
           throw TaskResult.failure(errorMessage);
@@ -138,7 +190,15 @@
 
       await runProjectTest((FlutterProject project) async {
         section('gradlew assembleProfile');
-        await project.runGradleTask('assembleProfile');
+        await inDirectory(project.rootPath, () {
+          return flutter(
+            'build',
+            options: <String>[
+              'apk',
+              '--profile',
+            ],
+          );
+        });
       });
 
       await runProjectTest((FlutterProject project) async {
@@ -173,8 +233,13 @@
       await runProjectTest((FlutterProject project) async {
         section('gradlew on build script with error');
         await project.introduceError();
-        final ProcessResult result =
-            await project.resultOfGradleTask('assembleRelease');
+        final ProcessResult result = await inDirectory(project.rootPath, () {
+          return executeFlutter('build', options: <String>[
+            'apk',
+            '--release',
+          ]);
+        });
+
         if (result.exitCode == 0)
           throw failure(
               'Gradle did not exit with error as expected', result);
@@ -193,8 +258,12 @@
       await runProjectTest((FlutterProject project) async {
         section('gradlew assembleDebug forwards stderr');
         await project.introducePubspecError();
-                final ProcessResult result =
-            await project.resultOfGradleTask('assembleRelease');
+        final ProcessResult result = await inDirectory(project.rootPath, () {
+          return executeFlutter('build', options: <String>[
+            'apk',
+            '--release',
+          ]);
+        });
         if (result.exitCode == 0)
           throw failure(
               'Gradle did not exit with error as expected', result);
@@ -206,7 +275,12 @@
       await runProjectTest((FlutterProject project) async {
         section('flutter build apk on build script with error');
         await project.introduceError();
-        final ProcessResult result = await project.resultOfFlutterCommand('build', <String>['apk']);
+        final ProcessResult result = await inDirectory(project.rootPath, () {
+          return executeFlutter('build', options: <String>[
+            'apk',
+            '--release',
+          ]);
+        });
         if (result.exitCode == 0)
           throw failure(
               'flutter build apk should fail when Gradle does', result);
@@ -223,7 +297,15 @@
 
       await runPluginProjectTest((FlutterPluginProject pluginProject) async {
         section('gradlew assembleDebug on plugin example');
-        await pluginProject.runGradleTask('assembleDebug');
+        await inDirectory(pluginProject.exampleAndroidPath, () {
+          return flutter(
+            'build',
+            options: <String>[
+              'apk',
+              '--debug',
+            ],
+          );
+        });
         if (!File(pluginProject.debugApkPath).existsSync())
           throw TaskResult.failure(
               'Gradle did not produce an apk file at the expected place');
diff --git a/dev/devicelab/bin/tasks/integration_ui.dart b/dev/devicelab/bin/tasks/integration_ui.dart
deleted file mode 100644
index 53f8e6d..0000000
--- a/dev/devicelab/bin/tasks/integration_ui.dart
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright 2014 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:flutter_devicelab/tasks/integration_ui.dart';
-import 'package:flutter_devicelab/framework/adb.dart';
-import 'package:flutter_devicelab/framework/framework.dart';
-
-/// End to end tests for Android.
-Future<void> main() async {
-  deviceOperatingSystem = DeviceOperatingSystem.android;
-  await task(runEndToEndTests);
-}
diff --git a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart b/dev/devicelab/bin/tasks/integration_ui_driver.dart
similarity index 78%
copy from dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart
copy to dev/devicelab/bin/tasks/integration_ui_driver.dart
index f585b54..91588c8 100644
--- a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart
+++ b/dev/devicelab/bin/tasks/integration_ui_driver.dart
@@ -2,11 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'package:flutter_devicelab/tasks/integration_tests.dart';
 import 'package:flutter_devicelab/framework/adb.dart';
 import 'package:flutter_devicelab/framework/framework.dart';
-import 'package:flutter_devicelab/tasks/integration_tests.dart';
 
 Future<void> main() async {
-  deviceOperatingSystem = DeviceOperatingSystem.ios;
-  await task(createFlutterDriverScreenshotTest());
+  deviceOperatingSystem = DeviceOperatingSystem.android;
+  await task(createEndToEndDriverTest());
 }
diff --git a/dev/devicelab/bin/tasks/integration_ui_ios.dart b/dev/devicelab/bin/tasks/integration_ui_ios.dart
deleted file mode 100644
index b90310e..0000000
--- a/dev/devicelab/bin/tasks/integration_ui_ios.dart
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright 2014 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:flutter_devicelab/tasks/integration_ui.dart';
-import 'package:flutter_devicelab/framework/adb.dart';
-import 'package:flutter_devicelab/framework/framework.dart';
-
-/// End to end tests for iOS.
-Future<void> main() async {
-  deviceOperatingSystem = DeviceOperatingSystem.ios;
-  await task(runEndToEndTests);
-}
diff --git a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart b/dev/devicelab/bin/tasks/integration_ui_ios_driver.dart
similarity index 89%
rename from dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart
rename to dev/devicelab/bin/tasks/integration_ui_ios_driver.dart
index f585b54..af0d280 100644
--- a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart
+++ b/dev/devicelab/bin/tasks/integration_ui_ios_driver.dart
@@ -2,11 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'package:flutter_devicelab/tasks/integration_tests.dart';
 import 'package:flutter_devicelab/framework/adb.dart';
 import 'package:flutter_devicelab/framework/framework.dart';
-import 'package:flutter_devicelab/tasks/integration_tests.dart';
 
 Future<void> main() async {
   deviceOperatingSystem = DeviceOperatingSystem.ios;
-  await task(createFlutterDriverScreenshotTest());
+  await task(createEndToEndDriverTest());
 }
diff --git a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart b/dev/devicelab/bin/tasks/integration_ui_ios_keyboard_resize.dart
similarity index 89%
copy from dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart
copy to dev/devicelab/bin/tasks/integration_ui_ios_keyboard_resize.dart
index f585b54..24152b8 100644
--- a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart
+++ b/dev/devicelab/bin/tasks/integration_ui_ios_keyboard_resize.dart
@@ -2,11 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'package:flutter_devicelab/tasks/integration_tests.dart';
 import 'package:flutter_devicelab/framework/adb.dart';
 import 'package:flutter_devicelab/framework/framework.dart';
-import 'package:flutter_devicelab/tasks/integration_tests.dart';
 
 Future<void> main() async {
   deviceOperatingSystem = DeviceOperatingSystem.ios;
-  await task(createFlutterDriverScreenshotTest());
+  await task(createEndToEndKeyboardTest());
 }
diff --git a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart b/dev/devicelab/bin/tasks/integration_ui_ios_screenshot.dart
similarity index 89%
copy from dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart
copy to dev/devicelab/bin/tasks/integration_ui_ios_screenshot.dart
index f585b54..58c573a 100644
--- a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart
+++ b/dev/devicelab/bin/tasks/integration_ui_ios_screenshot.dart
@@ -2,11 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'package:flutter_devicelab/tasks/integration_tests.dart';
 import 'package:flutter_devicelab/framework/adb.dart';
 import 'package:flutter_devicelab/framework/framework.dart';
-import 'package:flutter_devicelab/tasks/integration_tests.dart';
 
 Future<void> main() async {
   deviceOperatingSystem = DeviceOperatingSystem.ios;
-  await task(createFlutterDriverScreenshotTest());
+  await task(createEndToEndScreenshotTest());
 }
diff --git a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart b/dev/devicelab/bin/tasks/integration_ui_ios_textfield.dart
similarity index 88%
copy from dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart
copy to dev/devicelab/bin/tasks/integration_ui_ios_textfield.dart
index f585b54..134dfb0 100644
--- a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart
+++ b/dev/devicelab/bin/tasks/integration_ui_ios_textfield.dart
@@ -2,11 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'package:flutter_devicelab/tasks/integration_tests.dart';
 import 'package:flutter_devicelab/framework/adb.dart';
 import 'package:flutter_devicelab/framework/framework.dart';
-import 'package:flutter_devicelab/tasks/integration_tests.dart';
 
 Future<void> main() async {
   deviceOperatingSystem = DeviceOperatingSystem.ios;
-  await task(createFlutterDriverScreenshotTest());
+  await task(createEndToEndKeyboardTextfieldTest());
 }
diff --git a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart b/dev/devicelab/bin/tasks/integration_ui_keyboard_resize.dart
similarity index 78%
copy from dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart
copy to dev/devicelab/bin/tasks/integration_ui_keyboard_resize.dart
index f585b54..f2db416 100644
--- a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart
+++ b/dev/devicelab/bin/tasks/integration_ui_keyboard_resize.dart
@@ -2,11 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'package:flutter_devicelab/tasks/integration_tests.dart';
 import 'package:flutter_devicelab/framework/adb.dart';
 import 'package:flutter_devicelab/framework/framework.dart';
-import 'package:flutter_devicelab/tasks/integration_tests.dart';
 
 Future<void> main() async {
-  deviceOperatingSystem = DeviceOperatingSystem.ios;
-  await task(createFlutterDriverScreenshotTest());
+  deviceOperatingSystem = DeviceOperatingSystem.android;
+  await task(createEndToEndKeyboardTest());
 }
diff --git a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart b/dev/devicelab/bin/tasks/integration_ui_screenshot.dart
similarity index 78%
copy from dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart
copy to dev/devicelab/bin/tasks/integration_ui_screenshot.dart
index f585b54..f3f9170 100644
--- a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart
+++ b/dev/devicelab/bin/tasks/integration_ui_screenshot.dart
@@ -2,11 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'package:flutter_devicelab/tasks/integration_tests.dart';
 import 'package:flutter_devicelab/framework/adb.dart';
 import 'package:flutter_devicelab/framework/framework.dart';
-import 'package:flutter_devicelab/tasks/integration_tests.dart';
 
 Future<void> main() async {
-  deviceOperatingSystem = DeviceOperatingSystem.ios;
-  await task(createFlutterDriverScreenshotTest());
+  deviceOperatingSystem = DeviceOperatingSystem.android;
+  await task(createEndToEndScreenshotTest());
 }
diff --git a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_fuchsia.dart b/dev/devicelab/bin/tasks/integration_ui_textfield.dart
similarity index 77%
rename from dev/devicelab/bin/tasks/flutter_driver_screenshot_test_fuchsia.dart
rename to dev/devicelab/bin/tasks/integration_ui_textfield.dart
index 1458908..228c134 100644
--- a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_fuchsia.dart
+++ b/dev/devicelab/bin/tasks/integration_ui_textfield.dart
@@ -2,11 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'package:flutter_devicelab/tasks/integration_tests.dart';
 import 'package:flutter_devicelab/framework/adb.dart';
 import 'package:flutter_devicelab/framework/framework.dart';
-import 'package:flutter_devicelab/tasks/integration_tests.dart';
 
 Future<void> main() async {
-  deviceOperatingSystem = DeviceOperatingSystem.fuchsia;
-  await task(createFlutterDriverScreenshotTest());
+  deviceOperatingSystem = DeviceOperatingSystem.android;
+  await task(createEndToEndKeyboardTextfieldTest());
 }
diff --git a/dev/devicelab/bin/tasks/ios_defines_test.dart b/dev/devicelab/bin/tasks/ios_defines_test.dart
index 79cb91d..5d73627 100644
--- a/dev/devicelab/bin/tasks/ios_defines_test.dart
+++ b/dev/devicelab/bin/tasks/ios_defines_test.dart
@@ -4,10 +4,10 @@
 
 import 'package:flutter_devicelab/framework/adb.dart';
 import 'package:flutter_devicelab/framework/framework.dart';
-import 'package:flutter_devicelab/tasks/defines_task.dart';
+import 'package:flutter_devicelab/tasks/integration_tests.dart';
 
 /// Verify that dart defines work on iOS.
 Future<void> main() async {
   deviceOperatingSystem = DeviceOperatingSystem.ios;
-  await task(runDartDefinesTask);
+  await task(dartDefinesTask());
 }
diff --git a/dev/devicelab/bin/tasks/module_host_with_custom_build_test.dart b/dev/devicelab/bin/tasks/module_host_with_custom_build_test.dart
index b1b6ad4..f26abcf 100644
--- a/dev/devicelab/bin/tasks/module_host_with_custom_build_test.dart
+++ b/dev/devicelab/bin/tasks/module_host_with_custom_build_test.dart
@@ -29,6 +29,11 @@
 
     section('Create Flutter module project');
 
+    await flutter(
+      'precache',
+      options: <String>['--android', '--no-ios'],
+    );
+
     final Directory tempDir = Directory.systemTemp.createTempSync('flutter_module_test.');
     final Directory projectDir = Directory(path.join(tempDir.path, 'hello'));
     try {
diff --git a/dev/devicelab/lib/framework/apk_utils.dart b/dev/devicelab/lib/framework/apk_utils.dart
index 18a8955..ae8db16 100644
--- a/dev/devicelab/lib/framework/apk_utils.dart
+++ b/dev/devicelab/lib/framework/apk_utils.dart
@@ -119,9 +119,23 @@
   String workingDirectory,
 }) async {
   final String javaHome = await findJavaHome();
+
+   final String apkAnalyzer = path
+     .join(_androidHome, 'cmdline-tools', 'latest', 'bin', Platform.isWindows ? 'apkanalyzer.bat' : 'apkanalyzer');
+   if (canRun(apkAnalyzer)) {
+     return eval(
+       apkAnalyzer,
+       args,
+       printStdout: printStdout,
+       workingDirectory: workingDirectory,
+       environment: <String, String>{
+         'JAVA_HOME': javaHome,
+       },
+     );
+   }
+
   final String javaBinary = path.join(javaHome, 'bin', 'java');
   assert(canRun(javaBinary));
-
   final String androidTools = path.join(_androidHome, 'tools');
   final String libs = path.join(androidTools, 'lib');
   assert(Directory(libs).existsSync());
@@ -365,10 +379,6 @@
   String get releaseArmApkPath => path.join(examplePath, 'build', 'app', 'outputs', 'flutter-apk','app-armeabi-v7a-release.apk');
   String get releaseArm64ApkPath => path.join(examplePath, 'build', 'app', 'outputs', 'flutter-apk', 'app-arm64-v8a-release.apk');
   String get releaseBundlePath => path.join(examplePath, 'build', 'app', 'outputs', 'bundle', 'release', 'app.aab');
-
-  Future<void> runGradleTask(String task, {List<String> options}) async {
-    return _runGradleTask(workingDirectory: exampleAndroidPath, task: task, options: options);
-  }
 }
 
 class FlutterModuleProject {
diff --git a/dev/devicelab/lib/framework/cocoon.dart b/dev/devicelab/lib/framework/cocoon.dart
new file mode 100644
index 0000000..d1cecf2
--- /dev/null
+++ b/dev/devicelab/lib/framework/cocoon.dart
@@ -0,0 +1,152 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:convert' show json;
+import 'dart:io';
+
+import 'package:file/file.dart';
+import 'package:file/local.dart';
+import 'package:http/http.dart';
+import 'package:logging/logging.dart';
+import 'package:meta/meta.dart';
+
+import 'task_result.dart';
+import 'utils.dart';
+
+/// Class for test runner to interact with Flutter's infrastructure service, Cocoon.
+///
+/// Cocoon assigns bots to run these devicelab tasks on real devices.
+/// To retrieve these results, the test runner needs to send results back so so the database can be updated.
+class Cocoon {
+  Cocoon({
+    String serviceAccountTokenPath,
+    @visibleForTesting Client httpClient,
+    @visibleForTesting FileSystem filesystem,
+  }) : _httpClient = AuthenticatedCocoonClient(serviceAccountTokenPath, httpClient: httpClient, filesystem: filesystem);
+
+
+  /// Client to make http requests to Cocoon.
+  final AuthenticatedCocoonClient _httpClient;
+
+  /// Url used to send results to.
+  static const String baseCocoonApiUrl = 'https://flutter-dashboard.appspot.com/api';
+
+  static final Logger logger = Logger('CocoonClient');
+
+  String get commitSha => _commitSha ?? _readCommitSha();
+  String _commitSha;
+
+  /// Parse the local repo for the current running commit.
+  String _readCommitSha() {
+    final ProcessResult result = Process.runSync('git', <String>['rev-parse', 'HEAD']);
+    if (result.exitCode != 0) {
+      throw Exception(result.stderr);
+    }
+
+    _commitSha = result.stdout as String;
+    return _commitSha;
+  }
+
+  /// Send [TaskResult] to Cocoon.
+  Future<void> sendTaskResult({String taskName, TaskResult result}) async {
+    // Skip logging on test runs
+    Logger.root.level = Level.ALL;
+    Logger.root.onRecord.listen((LogRecord rec) {
+      print('${rec.level.name}: ${rec.time}: ${rec.message}');
+    });
+
+    final Map<String, dynamic> status = <String, dynamic>{
+      'CommitSha': commitSha,
+      'TaskName': taskName,
+      'NewStatus': result.succeeded ? 'Succeeded' : 'Failed',
+    };
+
+    // Make a copy of result data because we may alter it for validation below.
+    status['ResultData'] = result.data;
+
+    final List<String> validScoreKeys = <String>[];
+    if (result.benchmarkScoreKeys != null) {
+      for (final String scoreKey in result.benchmarkScoreKeys) {
+        final Object score = result.data[scoreKey];
+        if (score is num) {
+          // Convert all metrics to double, which provide plenty of precision
+          // without having to add support for multiple numeric types in Cocoon.
+          result.data[scoreKey] = score.toDouble();
+          validScoreKeys.add(scoreKey);
+        }
+      }
+    }
+    status['BenchmarkScoreKeys'] = validScoreKeys;
+
+    final Map<String, dynamic> response = await _sendCocoonRequest('update-task-status', status);
+    if (response['Name'] != null) {
+      logger.info('Updated Cocoon with results from this task');
+    } else {
+      logger.info(response);
+      logger.severe('Failed to updated Cocoon with results from this task');
+    }
+  }
+
+  /// Make an API request to Cocoon.
+  Future<Map<String, dynamic>> _sendCocoonRequest(String apiPath, [dynamic jsonData]) async {
+    final String url = '$baseCocoonApiUrl/$apiPath';
+
+    /// Retry requests to Cocoon as sometimes there are issues with the servers, such
+    /// as version changes to the backend, datastore issues, or latency issues.
+    final Response response = await retry(
+      () => _httpClient.post(url, body: json.encode(jsonData)),
+      retryIf: (Exception e) => e is SocketException || e is TimeoutException || e is ClientException,
+      maxAttempts: 5,
+    );
+    return json.decode(response.body) as Map<String, dynamic>;
+  }
+}
+
+/// [HttpClient] for sending authenticated requests to Cocoon.
+class AuthenticatedCocoonClient extends BaseClient {
+  AuthenticatedCocoonClient(
+    this._serviceAccountTokenPath, {
+    @visibleForTesting Client httpClient,
+    @visibleForTesting FileSystem filesystem,
+  })  : _delegate = httpClient ?? Client(),
+        _fs = filesystem ?? const LocalFileSystem();
+
+  /// Authentication token to have the ability to upload and record test results.
+  ///
+  /// This is intended to only be passed on automated runs on LUCI post-submit.
+  final String _serviceAccountTokenPath;
+
+  /// Underlying [HttpClient] to send requests to.
+  final Client _delegate;
+
+  /// Underlying [FileSystem] to use.
+  final FileSystem _fs;
+
+  /// Value contained in the service account token file that can be used in http requests.
+  String get serviceAccountToken => _serviceAccountToken ?? _readServiceAccountTokenFile();
+  String _serviceAccountToken;
+
+  /// Get [serviceAccountToken] from the given service account file.
+  String _readServiceAccountTokenFile() {
+    return _serviceAccountToken = _fs.file(_serviceAccountTokenPath).readAsStringSync().trim();
+  }
+
+  @override
+  Future<StreamedResponse> send(BaseRequest request) async {
+    request.headers['Service-Account-Token'] = serviceAccountToken;
+    final StreamedResponse response = await _delegate.send(request);
+
+    if (response.statusCode != 200) {
+      throw ClientException(
+          'AuthenticatedClientError:\n'
+          '  URI: ${request.url}\n'
+          '  HTTP Status: ${response.statusCode}\n'
+          '  Response body:\n'
+          '${(await Response.fromStream(response)).body}',
+          request.url);
+    }
+    return response;
+  }
+}
diff --git a/dev/devicelab/lib/framework/runner.dart b/dev/devicelab/lib/framework/runner.dart
index 5fa5295..6b7cd7a 100644
--- a/dev/devicelab/lib/framework/runner.dart
+++ b/dev/devicelab/lib/framework/runner.dart
@@ -6,7 +6,6 @@
 import 'dart:convert';
 import 'dart:io';
 
-import 'package:path/path.dart' as path;
 import 'package:vm_service_client/vm_service_client.dart';
 
 import 'package:flutter_devicelab/framework/utils.dart';
@@ -88,7 +87,6 @@
   } finally {
     if (!runnerFinished)
       runner.kill(ProcessSignal.sigkill);
-    await cleanupSystem();
     await stdoutSub.cancel();
     await stderrSub.cancel();
   }
@@ -124,42 +122,3 @@
     }
   }
 }
-
-Future<void> cleanupSystem() async {
-  if (deviceOperatingSystem == null || deviceOperatingSystem == DeviceOperatingSystem.android) {
-    print('\n\nCleaning up system after task...');
-    final String javaHome = await findJavaHome();
-    if (javaHome != null) {
-      // To shut gradle down, we have to call "gradlew --stop".
-      // To call gradlew, we need to have a gradle-wrapper.properties file along
-      // with a shell script, a .jar file, etc. We get these from various places
-      // as you see in the code below, and we save them all into a temporary dir
-      // which we can then delete after.
-      // All the steps below are somewhat tolerant of errors, because it doesn't
-      // really matter if this succeeds every time or not.
-      print('\nTelling Gradle to shut down (JAVA_HOME=$javaHome)');
-      final String gradlewBinaryName = Platform.isWindows ? 'gradlew.bat' : 'gradlew';
-      final Directory tempDir = Directory.systemTemp.createTempSync('flutter_devicelab_shutdown_gradle.');
-      recursiveCopy(Directory(path.join(flutterDirectory.path, 'bin', 'cache', 'artifacts', 'gradle_wrapper')), tempDir);
-      copy(File(path.join(path.join(flutterDirectory.path, 'packages', 'flutter_tools'), 'templates', 'app', 'android.tmpl', 'gradle', 'wrapper', 'gradle-wrapper.properties')), Directory(path.join(tempDir.path, 'gradle', 'wrapper')));
-      if (!Platform.isWindows) {
-        await exec(
-          'chmod',
-          <String>['a+x', path.join(tempDir.path, gradlewBinaryName)],
-          canFail: true,
-        );
-      }
-      await exec(
-        path.join(tempDir.path, gradlewBinaryName),
-        <String>['--stop'],
-        environment: <String, String>{ 'JAVA_HOME': javaHome},
-        workingDirectory: tempDir.path,
-        canFail: true,
-      );
-      rmTree(tempDir);
-      print('\n');
-    } else {
-      print('Could not determine JAVA_HOME; not shutting down Gradle.');
-    }
-  }
-}
diff --git a/dev/devicelab/lib/framework/utils.dart b/dev/devicelab/lib/framework/utils.dart
index ad29d3f..097fb94 100644
--- a/dev/devicelab/lib/framework/utils.dart
+++ b/dev/devicelab/lib/framework/utils.dart
@@ -463,6 +463,16 @@
       canFail: canFail, environment: environment, stderr: stderr);
 }
 
+Future<ProcessResult> executeFlutter(String command, {
+  List<String> options = const <String>[],
+}) async {
+  final List<String> args = flutterCommandArgs(command, options);
+  return _processManager.run(
+    <String>[path.join(flutterDirectory.path, 'bin', 'flutter'), ...args],
+    workingDirectory: cwd,
+  );
+}
+
 String get dartBin =>
     path.join(flutterDirectory.path, 'bin', 'cache', 'dart-sdk', 'bin', 'dart');
 
@@ -726,3 +736,34 @@
         () => exec('git', <String>['clone', repo]),
   );
 }
+
+/// Call [fn] retrying so long as [retryIf] return `true` for the exception
+/// thrown and [maxAttempts] has not been reached.
+///
+/// If no [retryIf] function is given this will retry any for any [Exception]
+/// thrown. To retry on an [Error], the error must be caught and _rethrown_
+/// as an [Exception].
+///
+/// Waits a constant duration of [delayDuration] between every retry attempt.
+Future<T> retry<T>(
+  FutureOr<T> Function() fn, {
+  FutureOr<bool> Function(Exception) retryIf,
+  int maxAttempts = 5,
+  Duration delayDuration = const Duration(seconds: 3),
+}) async {
+  int attempt = 0;
+  while (true) {
+    attempt++; // first invocation is the first attempt
+    try {
+      return await fn();
+    } on Exception catch (e) {
+      if (attempt >= maxAttempts ||
+          (retryIf != null && !(await retryIf(e)))) {
+        rethrow;
+      }
+    }
+
+    // Sleep for a delay
+    await Future<void>.delayed(delayDuration);
+  }
+}
diff --git a/dev/devicelab/lib/tasks/defines_task.dart b/dev/devicelab/lib/tasks/defines_task.dart
deleted file mode 100644
index 044542f..0000000
--- a/dev/devicelab/lib/tasks/defines_task.dart
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2014 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:io';
-
-import '../framework/adb.dart';
-import '../framework/task_result.dart';
-import '../framework/utils.dart';
-
-Future<TaskResult> runDartDefinesTask() async {
-  final Device device = await devices.workingDevice;
-  await device.unlock();
-  final String deviceId = device.deviceId;
-  final Directory testDirectory = dir('${flutterDirectory.path}/dev/integration_tests/ui');
-  await inDirectory<void>(testDirectory, () async {
-    await flutter('packages', options: <String>['get']);
-
-    await flutter('drive', options: <String>[
-      '--verbose',
-      '-d',
-      deviceId,
-      '--dart-define=test.valueA=Example',
-      '--dart-define=test.valueB=Value',
-      'lib/defines.dart',
-    ]);
-  });
-
-  return TaskResult.success(<String, dynamic>{});
-}
diff --git a/dev/devicelab/lib/tasks/gallery.dart b/dev/devicelab/lib/tasks/gallery.dart
index b06beae..66b4ae0 100644
--- a/dev/devicelab/lib/tasks/gallery.dart
+++ b/dev/devicelab/lib/tasks/gallery.dart
@@ -71,6 +71,7 @@
           'build',
           options: <String>[
             'apk',
+            '--no-android-gradle-daemon',
             '--profile',
             '-t',
             'test_driver/$testFile.dart',
diff --git a/dev/devicelab/lib/tasks/hot_mode_tests.dart b/dev/devicelab/lib/tasks/hot_mode_tests.dart
index c5b76c0..fc3f721 100644
--- a/dev/devicelab/lib/tasks/hot_mode_tests.dart
+++ b/dev/devicelab/lib/tasks/hot_mode_tests.dart
@@ -26,7 +26,7 @@
     final File benchmarkFile = file(path.join(_editedFlutterGalleryDir.path, 'hot_benchmark.json'));
     rm(benchmarkFile);
     final List<String> options = <String>[
-      '--hot', '-d', deviceIdOverride, '--benchmark', '--verbose', '--resident',
+      '--hot', '-d', deviceIdOverride, '--benchmark', '--verbose', '--resident',  '--no-android-gradle-daemon',
     ];
     int hotReloadCount = 0;
     Map<String, dynamic> twoReloadsData;
diff --git a/dev/devicelab/lib/tasks/integration_tests.dart b/dev/devicelab/lib/tasks/integration_tests.dart
index bd0d087..30a5b4c 100644
--- a/dev/devicelab/lib/tasks/integration_tests.dart
+++ b/dev/devicelab/lib/tasks/integration_tests.dart
@@ -92,15 +92,6 @@
   );
 }
 
-/// Executes a driver test that takes a screenshot and compares it against a golden image.
-/// The golden image is served by Flutter Gold (https://flutter-gold.skia.org/).
-TaskFunction createFlutterDriverScreenshotTest() {
-  return DriverTest(
-    '${flutterDirectory.path}/dev/integration_tests/flutter_driver_screenshot_test',
-    'lib/main.dart',
-  );
-}
-
 TaskFunction createIOSPlatformViewTests() {
   return DriverTest(
     '${flutterDirectory.path}/dev/integration_tests/ios_platform_view_tests',
@@ -108,6 +99,44 @@
   );
 }
 
+TaskFunction createEndToEndKeyboardTest() {
+  return DriverTest(
+    '${flutterDirectory.path}/dev/integration_tests/ui',
+    'lib/keyboard_resize.dart',
+  );
+}
+
+TaskFunction createEndToEndDriverTest() {
+  return DriverTest(
+    '${flutterDirectory.path}/dev/integration_tests/ui',
+    'lib/driver.dart',
+  );
+}
+
+TaskFunction createEndToEndScreenshotTest() {
+  return DriverTest(
+    '${flutterDirectory.path}/dev/integration_tests/ui',
+    'lib/screenshot.dart',
+  );
+}
+
+TaskFunction createEndToEndKeyboardTextfieldTest() {
+  return DriverTest(
+    '${flutterDirectory.path}/dev/integration_tests/ui',
+    'lib/keyboard_textfield.dart',
+  );
+}
+
+TaskFunction dartDefinesTask() {
+  return DriverTest(
+    '${flutterDirectory.path}/dev/integration_tests/ui',
+    'lib/defines.dart', extraOptions: <String>[
+    '--dart-define=test.valueA=Example',
+    '--dart-define=test.valueB=Value',
+    ],
+  );
+}
+
 class DriverTest {
   DriverTest(
     this.testDirectory,
@@ -130,6 +159,7 @@
       await flutter('packages', options: <String>['get']);
 
       final List<String> options = <String>[
+        '--no-android-gradle-daemon',
         '-v',
         '-t',
         testTarget,
diff --git a/dev/devicelab/lib/tasks/integration_ui.dart b/dev/devicelab/lib/tasks/integration_ui.dart
deleted file mode 100644
index 6223e0b..0000000
--- a/dev/devicelab/lib/tasks/integration_ui.dart
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2014 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:io';
-
-import '../framework/adb.dart';
-import '../framework/task_result.dart';
-import '../framework/utils.dart';
-
-Future<TaskResult> runEndToEndTests() async {
-  final Device device = await devices.workingDevice;
-  await device.unlock();
-  final String deviceId = device.deviceId;
-  final Directory testDirectory = dir('${flutterDirectory.path}/dev/integration_tests/ui');
-  await inDirectory<void>(testDirectory, () async {
-    await flutter('packages', options: <String>['get']);
-
-    const List<String> entryPoints = <String>[
-      'lib/keyboard_resize.dart',
-      'lib/driver.dart',
-      'lib/screenshot.dart',
-      'lib/keyboard_textfield.dart',
-    ];
-
-    for (final String entryPoint in entryPoints) {
-      await flutter('drive', options: <String>['--verbose', '-d', deviceId, entryPoint]);
-    }
-  });
-
-  return TaskResult.success(<String, dynamic>{});
-}
diff --git a/dev/devicelab/lib/tasks/perf_tests.dart b/dev/devicelab/lib/tasks/perf_tests.dart
index bb8e19d..e3d735a 100644
--- a/dev/devicelab/lib/tasks/perf_tests.dart
+++ b/dev/devicelab/lib/tasks/perf_tests.dart
@@ -347,6 +347,7 @@
       await flutter('packages', options: <String>['get']);
 
       await flutter('drive', options: <String>[
+        '--no-android-gradle-daemon',
         '-v',
         '--verbose-system-logs',
         '--profile',
@@ -395,6 +396,7 @@
       await flutter('packages', options: <String>['get']);
 
       await flutter('drive', options: <String>[
+        '--no-android-gradle-daemon',
         '-v',
         '--verbose-system-logs',
         '--profile',
@@ -452,23 +454,56 @@
 
   Future<TaskResult> run() async {
     return await inDirectory<TaskResult>(testDirectory, () async {
-      final String deviceId = (await devices.workingDevice).deviceId;
-      await flutter('packages', options: <String>['get']);
-
-      const int iterations = 15;
+      final Device device = await devices.workingDevice;
+      const int iterations = 5;
       final List<Map<String, dynamic>> results = <Map<String, dynamic>>[];
-      for (int i = 0; i < iterations; ++i) {
+
+      section('Building application');
+      String applicationBinaryPath;
+      switch (deviceOperatingSystem) {
+        case DeviceOperatingSystem.android:
+          await flutter('build', options: <String>[
+            'apk',
+            '-v',
+            '--profile',
+            '--target-platform=android-arm,android-arm64',
+          ]);
+          applicationBinaryPath = '$testDirectory/build/app/outputs/flutter-apk/app-profile.apk';
+          break;
+        case DeviceOperatingSystem.ios:
+          await flutter('build', options: <String>[
+            'ios',
+             '-v',
+            '--profile',
+          ]);
+          applicationBinaryPath = _findIosAppInBuildDirectory('$testDirectory/build/ios/iphoneos');
+          break;
+        case DeviceOperatingSystem.fuchsia:
+        case DeviceOperatingSystem.fake:
+          break;
+      }
+
+      for (int i = 0; i < iterations; i += 1) {
         await flutter('run', options: <String>[
+          '--no-android-gradle-daemon',
           '--verbose',
           '--profile',
           '--trace-startup',
           '-d',
-          deviceId,
+          device.deviceId,
+          if (applicationBinaryPath != null)
+            '--use-application-binary=$applicationBinaryPath',
         ]);
         final Map<String, dynamic> data = json.decode(
           file('$testDirectory/build/start_up_info.json').readAsStringSync(),
         ) as Map<String, dynamic>;
         results.add(data);
+
+        await flutter('install', options: <String>[
+          '--uninstall-only',
+          '-d',
+          device.deviceId,
+        ]);
       }
 
       final Map<String, dynamic> averageResults = _average(results, iterations);
@@ -575,6 +610,7 @@
       await flutter('packages', options: <String>['get']);
 
       await flutter('drive', options: <String>[
+        '--no-android-gradle-daemon',
         '-v',
         '--verbose-system-logs',
         '--profile',
@@ -1090,6 +1126,7 @@
       }
 
       await adb.cancel();
+      await flutter('install', options: <String>['--uninstall-only', '-d', device.deviceId]);
 
       final ListStatistics startMemoryStatistics = ListStatistics(_startMemory);
       final ListStatistics endMemoryStatistics = ListStatistics(_endMemory);
@@ -1424,3 +1461,12 @@
   final int compressedSize;
   final String path;
 }
+
+String _findIosAppInBuildDirectory(String searchDirectory) {
+  for (final FileSystemEntity entity in Directory(searchDirectory).listSync()) {
+    if (entity.path.endsWith('.app')) {
+      return entity.path;
+    }
+  }
+  return null;
+}
diff --git a/dev/devicelab/lib/tasks/track_widget_creation_enabled_task.dart b/dev/devicelab/lib/tasks/track_widget_creation_enabled_task.dart
index 620dda6..04ab69e 100644
--- a/dev/devicelab/lib/tasks/track_widget_creation_enabled_task.dart
+++ b/dev/devicelab/lib/tasks/track_widget_creation_enabled_task.dart
@@ -43,6 +43,7 @@
       final Process runProcess = await startProcess(
         path.join(flutterDirectory.path, 'bin', 'flutter'),
         flutterCommandArgs('run', <String>[
+          '--no-android-gradle-daemon',
           ...?additionalArgs,
           '--vmservice-out-file=info',
           '--track-widget-creation',
@@ -78,6 +79,7 @@
       final Process runProcess = await startProcess(
         path.join(flutterDirectory.path, 'bin', 'flutter'),
         flutterCommandArgs('run', <String>[
+          '--no-android-gradle-daemon',
            ...?additionalArgs,
            '--vmservice-out-file=info',
           '--no-track-widget-creation',
diff --git a/dev/devicelab/manifest.yaml b/dev/devicelab/manifest.yaml
index 2bec4d8..5a08535 100644
--- a/dev/devicelab/manifest.yaml
+++ b/dev/devicelab/manifest.yaml
@@ -229,14 +229,6 @@
     stage: devicelab
     required_agent_capabilities: ["linux/android"]
 
-  flutter_gallery_sksl_warmup_ios32__transition_perf:
-    description: >
-      Measures the runtime performance of Flutter gallery transitions on iPhone4s
-      with SkSL shader warm-up.
-    stage: devicelab_ios
-    required_agent_capabilities: ["mac/ios32"]
-    flaky: true
-
   flutter_gallery_sksl_warmup__transition_perf_e2e_ios32:
     description: >
       Measures the runtime performance of Flutter gallery transitions on iPhone4s
@@ -401,17 +393,29 @@
     stage: devicelab
     required_agent_capabilities: ["mac/android"]
 
-  integration_ui:
+  integration_ui_driver:
     description: >
       Runs end-to-end Flutter tests on Android.
     stage: devicelab
     required_agent_capabilities: ["mac/android"]
 
-  commands_test:
+  integration_ui_keyboard_resize:
     description: >
-      Runs tests of flutter run commands.
+      Runs end-to-end Flutter tests on Android.
     stage: devicelab
-    required_agent_capabilities: ["mac/android"]
+    required_agent_capabilities: [ "mac/android" ]
+
+  integration_ui_screenshot:
+    description: >
+      Runs end-to-end Flutter tests on Android.
+    stage: devicelab
+    required_agent_capabilities: [ "mac/android" ]
+
+  integration_ui_textfield:
+    description: >
+      Runs end-to-end Flutter tests on Android.
+    stage: devicelab
+    required_agent_capabilities: [ "mac/android" ]
 
   service_extensions_test:
     description: >
@@ -608,7 +612,25 @@
     stage: devicelab_ios
     required_agent_capabilities: ["mac/ios"]
 
-  integration_ui_ios:
+  integration_ui_ios_driver:
+    description: >
+      Runs end-to-end Flutter tests on iPhone 6.
+    stage: devicelab_ios
+    required_agent_capabilities: ["mac/ios"]
+
+  integration_ui_ios_keyboard_resize:
+    description: >
+      Runs end-to-end Flutter tests on iPhone 6.
+    stage: devicelab_ios
+    required_agent_capabilities: ["mac/ios"]
+
+  integration_ui_ios_screenshot:
+    description: >
+      Runs end-to-end Flutter tests on iPhone 6.
+    stage: devicelab_ios
+    required_agent_capabilities: ["mac/ios"]
+
+  integration_ui_ios_textfield:
     description: >
       Runs end-to-end Flutter tests on iPhone 6.
     stage: devicelab_ios
@@ -899,16 +921,6 @@
   #   required_agent_capabilities: ["linux/android"]
   #   flaky: true
 
-  # TODO(cyanglaz): Enable this test when we know how to test gold in device labs
-  # Or completely remove this test to LUCI when LUCI supports physical iOS devices.
-  # flutter_driver_screenshot_test_ios:
-  #   description: >
-  #     Screenshot tests running on a specifc iPhone 6.
-  #     The test makes sure that there is no regression while renderring an image with gl on iOS.
-  #   stage: devicelab_ios
-  #   required_agent_capabilities: ["mac/ios", "ios/gl-render-image"]
-  #   flaky: true
-
   flutter_gallery_v2_chrome_run_test:
     description: >
       Checks that the New Flutter Gallery runs successfully on Chrome.
diff --git a/dev/devicelab/test/cocoon_test.dart b/dev/devicelab/test/cocoon_test.dart
new file mode 100644
index 0000000..f5c27d8
--- /dev/null
+++ b/dev/devicelab/test/cocoon_test.dart
@@ -0,0 +1,88 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:file/file.dart';
+import 'package:file/memory.dart';
+import 'package:http/http.dart';
+import 'package:http/testing.dart';
+
+import 'package:flutter_devicelab/framework/cocoon.dart';
+import 'package:flutter_devicelab/framework/task_result.dart';
+
+import 'common.dart';
+
+void main() {
+  group('Cocoon', () {
+    const String serviceAccountTokenPath = 'test_account_file';
+    const String serviceAccountToken = 'test_token';
+
+    Client mockClient;
+    Cocoon cocoon;
+    FileSystem fs;
+
+    setUp(() {
+      fs = MemoryFileSystem();
+
+      final File serviceAccountFile = fs.file(serviceAccountTokenPath)..createSync();
+      serviceAccountFile.writeAsStringSync(serviceAccountToken);
+    });
+
+    test('sends expected request from successful task', () async {
+      mockClient = MockClient((Request request) async => Response('{}', 200));
+
+      cocoon = Cocoon(
+        serviceAccountTokenPath: serviceAccountTokenPath,
+        filesystem: fs,
+        httpClient: mockClient,
+      );
+
+      final TaskResult result = TaskResult.success(<String, dynamic>{});
+      // This should not throw an error.
+      await cocoon.sendTaskResult(taskName: 'taskAbc', result: result);
+    });
+
+    test('throws client exception on non-200 responses', () async {
+      mockClient = MockClient((Request request) async => Response('', 500));
+
+      cocoon = Cocoon(
+        serviceAccountTokenPath: serviceAccountTokenPath,
+        filesystem: fs,
+        httpClient: mockClient,
+      );
+
+      final TaskResult result = TaskResult.success(<String, dynamic>{});
+      expect(() => cocoon.sendTaskResult(taskName: 'taskAbc', result: result), throwsA(isA<ClientException>()));
+    });
+  });
+
+  group('AuthenticatedCocoonClient', () {
+    const String serviceAccountPath = 'test_account_file';
+    const String serviceAccountToken = 'test_token';
+
+    FileSystem fs;
+
+    setUp(() {
+      fs = MemoryFileSystem();
+      final File serviceAccountFile = fs.file(serviceAccountPath)..createSync();
+      serviceAccountFile.writeAsStringSync(serviceAccountToken);
+    });
+
+    test('reads token from service account file', () {
+      final AuthenticatedCocoonClient client = AuthenticatedCocoonClient(serviceAccountPath, filesystem: fs);
+      expect(client.serviceAccountToken, serviceAccountToken);
+    });
+
+    test('reads token from service account file with whitespace', () {
+      final File serviceAccountFile = fs.file(serviceAccountPath)..createSync();
+      serviceAccountFile.writeAsStringSync(serviceAccountToken + ' \n');
+      final AuthenticatedCocoonClient client = AuthenticatedCocoonClient(serviceAccountPath, filesystem: fs);
+      expect(client.serviceAccountToken, serviceAccountToken);
+    });
+
+    test('throws error when service account file not found', () {
+      final AuthenticatedCocoonClient client = AuthenticatedCocoonClient('idontexist', filesystem: fs);
+      expect(() => client.serviceAccountToken, throwsA(isA<FileSystemException>()));
+    });
+  });
+}
diff --git a/dev/devicelab/test/run_test.dart b/dev/devicelab/test/run_test.dart
index ec5110e..0788659 100644
--- a/dev/devicelab/test/run_test.dart
+++ b/dev/devicelab/test/run_test.dart
@@ -138,5 +138,14 @@
         ),
       );
     });
+
+    test('fails to upload results to Cocoon if flags given', () async {
+      // CocoonClient will fail to find test-file, and will not send any http requests.
+      final ProcessResult result = await runScript(
+        <String>['smoke_test_success'],
+        <String>['--service-account-file=test-file', '--task-key=task123'],
+      );
+      expect(result.exitCode, 1);
+    });
   });
 }
diff --git a/dev/integration_tests/android_embedding_v2_smoke_test/pubspec.yaml b/dev/integration_tests/android_embedding_v2_smoke_test/pubspec.yaml
index 5cdffdf..275937d 100644
--- a/dev/integration_tests/android_embedding_v2_smoke_test/pubspec.yaml
+++ b/dev/integration_tests/android_embedding_v2_smoke_test/pubspec.yaml
@@ -20,12 +20,12 @@
   flutter:
     sdk: flutter
   # This plugin is using Android Embedding 1
-  battery: 1.0.6
+  battery: 1.0.7
   # TODO(egarciad): Add a plugin that uses Android Embedding 2
 
   # The following adds the Cupertino Icons font to your application.
   # Use with the CupertinoIcons class for iOS style icons.
-  cupertino_icons: 0.1.3
+  cupertino_icons: 1.0.0
 
   battery_platform_interface: 1.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   characters: 1.1.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -96,4 +96,4 @@
   # For details regarding fonts from package dependencies,
   # see https://flutter.dev/custom-fonts/#from-packages
 
-# PUBSPEC CHECKSUM: bf4a
+# PUBSPEC CHECKSUM: 3f48
diff --git a/dev/integration_tests/android_semantics_testing/pubspec.yaml b/dev/integration_tests/android_semantics_testing/pubspec.yaml
index f31ee94..77d4867 100644
--- a/dev/integration_tests/android_semantics_testing/pubspec.yaml
+++ b/dev/integration_tests/android_semantics_testing/pubspec.yaml
@@ -1,5 +1,7 @@
 name: android_semantics_testing
 description: Integration testing library for Android semantics
+environment:
+  sdk: '>=2.9.0 <3.0.0'
 
 dependencies:
   flutter:
diff --git a/dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/pubspec.yaml b/dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/pubspec.yaml
index 675d1d5..fb4406f 100644
--- a/dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/pubspec.yaml
+++ b/dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/pubspec.yaml
@@ -22,7 +22,7 @@
 
   # The following adds the Cupertino Icons font to your application.
   # Use with the CupertinoIcons class for iOS style icons.
-  cupertino_icons: 0.1.3
+  cupertino_icons: 1.0.0
 
   characters: 1.1.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   collection: 1.15.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -137,4 +137,4 @@
   # For details regarding fonts from package dependencies,
   # see https://flutter.dev/custom-fonts/#from-packages
 
-# PUBSPEC CHECKSUM: eba8
+# PUBSPEC CHECKSUM: 20a5
diff --git a/dev/integration_tests/android_splash_screens/splash_screen_load_rotate/pubspec.yaml b/dev/integration_tests/android_splash_screens/splash_screen_load_rotate/pubspec.yaml
index fe5489f..08586f3 100644
--- a/dev/integration_tests/android_splash_screens/splash_screen_load_rotate/pubspec.yaml
+++ b/dev/integration_tests/android_splash_screens/splash_screen_load_rotate/pubspec.yaml
@@ -22,7 +22,7 @@
 
   # The following adds the Cupertino Icons font to your application.
   # Use with the CupertinoIcons class for iOS style icons.
-  cupertino_icons: 0.1.3
+  cupertino_icons: 1.0.0
 
   characters: 1.1.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   collection: 1.15.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -91,4 +91,4 @@
   # For details regarding fonts from package dependencies,
   # see https://flutter.dev/custom-fonts/#from-packages
 
-# PUBSPEC CHECKSUM: 9423
+# PUBSPEC CHECKSUM: 2920
diff --git a/dev/integration_tests/android_splash_screens/splash_screen_trans_rotate/pubspec.yaml b/dev/integration_tests/android_splash_screens/splash_screen_trans_rotate/pubspec.yaml
index 2ed07f8..1d19050 100644
--- a/dev/integration_tests/android_splash_screens/splash_screen_trans_rotate/pubspec.yaml
+++ b/dev/integration_tests/android_splash_screens/splash_screen_trans_rotate/pubspec.yaml
@@ -22,7 +22,7 @@
 
   # The following adds the Cupertino Icons font to your application.
   # Use with the CupertinoIcons class for iOS style icons.
-  cupertino_icons: 0.1.3
+  cupertino_icons: 1.0.0
 
   characters: 1.1.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   collection: 1.15.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -137,4 +137,4 @@
   # For details regarding fonts from package dependencies,
   # see https://flutter.dev/custom-fonts/#from-packages
 
-# PUBSPEC CHECKSUM: eba8
+# PUBSPEC CHECKSUM: 20a5
diff --git a/dev/integration_tests/android_views/pubspec.yaml b/dev/integration_tests/android_views/pubspec.yaml
index 127ab8e..4666377 100644
--- a/dev/integration_tests/android_views/pubspec.yaml
+++ b/dev/integration_tests/android_views/pubspec.yaml
@@ -1,6 +1,8 @@
 name: platform_views
 description: An integration test for embedded platform views
 version: 1.0.0+1
+environment:
+  sdk: '>=2.9.0 <3.0.0'
 
 dependencies:
   flutter:
diff --git a/dev/integration_tests/flutter_driver_screenshot_test/pubspec.yaml b/dev/integration_tests/flutter_driver_screenshot_test/pubspec.yaml
index 29987d4..0d2c544 100644
--- a/dev/integration_tests/flutter_driver_screenshot_test/pubspec.yaml
+++ b/dev/integration_tests/flutter_driver_screenshot_test/pubspec.yaml
@@ -9,8 +9,8 @@
     sdk: flutter
   flutter_driver:
     sdk: flutter
-  cupertino_icons: 0.1.3
-  device_info: 0.4.2+8
+  cupertino_icons: 1.0.0
+  device_info: 0.4.2+9
 
   archive: 2.0.13 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   args: 1.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -82,4 +82,4 @@
   assets:
     - assets/
 
-# PUBSPEC CHECKSUM: 5acc
+# PUBSPEC CHECKSUM: beca
diff --git a/dev/integration_tests/flutter_gallery/lib/demo/cupertino/cupertino_navigation_demo.dart b/dev/integration_tests/flutter_gallery/lib/demo/cupertino/cupertino_navigation_demo.dart
index 097d7d5..36fbb4d 100644
--- a/dev/integration_tests/flutter_gallery/lib/demo/cupertino/cupertino_navigation_demo.dart
+++ b/dev/integration_tests/flutter_gallery/lib/demo/cupertino/cupertino_navigation_demo.dart
@@ -32,18 +32,19 @@
 const int _kChildCount = 50;
 
 class CupertinoNavigationDemo extends StatelessWidget {
-  CupertinoNavigationDemo()
+  CupertinoNavigationDemo({ this.randomSeed })
       : colorItems = List<Color>.generate(_kChildCount, (int index) {
-          return coolColors[math.Random().nextInt(coolColors.length)];
+          return coolColors[math.Random(randomSeed).nextInt(coolColors.length)];
         }) ,
         colorNameItems = List<String>.generate(_kChildCount, (int index) {
-          return coolColorNames[math.Random().nextInt(coolColorNames.length)];
+          return coolColorNames[math.Random(randomSeed).nextInt(coolColorNames.length)];
         });
 
   static const String routeName = '/cupertino/navigation';
 
   final List<Color> colorItems;
   final List<String> colorNameItems;
+  final int randomSeed;
 
   @override
   Widget build(BuildContext context) {
@@ -56,15 +57,15 @@
           tabBar: CupertinoTabBar(
             items: const <BottomNavigationBarItem>[
               BottomNavigationBarItem(
-                icon: Icon(CupertinoIcons.home),
+                icon: Icon(CupertinoIcons.house, size: 27),
                 label: 'Home',
               ),
               BottomNavigationBarItem(
-                icon: Icon(CupertinoIcons.conversation_bubble),
+                icon: Icon(CupertinoIcons.chat_bubble, size: 27),
                 label: 'Support',
               ),
               BottomNavigationBarItem(
-                icon: Icon(CupertinoIcons.profile_circled),
+                icon: Icon(CupertinoIcons.person_circle, size: 27),
                 label: 'Profile',
               ),
             ],
@@ -78,6 +79,7 @@
                     return CupertinoDemoTab1(
                       colorItems: colorItems,
                       colorNameItems: colorNameItems,
+                      randomSeed: randomSeed,
                     );
                   },
                   defaultTitle: 'Colors',
@@ -134,10 +136,15 @@
 );
 
 class CupertinoDemoTab1 extends StatelessWidget {
-  const CupertinoDemoTab1({this.colorItems, this.colorNameItems});
+  const CupertinoDemoTab1({
+    this.colorItems,
+    this.colorNameItems,
+    this.randomSeed,
+  });
 
   final List<Color> colorItems;
   final List<String> colorNameItems;
+  final int randomSeed;
 
   @override
   Widget build(BuildContext context) {
@@ -165,6 +172,7 @@
                     lastItem: index == _kChildCount - 1,
                     color: colorItems[index],
                     colorName: colorNameItems[index],
+                    randomSeed: randomSeed,
                   );
                 },
                 childCount: _kChildCount,
@@ -178,12 +186,19 @@
 }
 
 class Tab1RowItem extends StatelessWidget {
-  const Tab1RowItem({this.index, this.lastItem, this.color, this.colorName});
+  const Tab1RowItem({
+    this.index,
+    this.lastItem,
+    this.color,
+    this.colorName,
+    this.randomSeed,
+  });
 
   final int index;
   final bool lastItem;
   final Color color;
   final String colorName;
+  final int randomSeed;
 
   @override
   Widget build(BuildContext context) {
@@ -196,6 +211,7 @@
             color: color,
             colorName: colorName,
             index: index,
+            randomSeed: randomSeed,
           ),
         ));
       },
@@ -274,11 +290,12 @@
 }
 
 class Tab1ItemPage extends StatefulWidget {
-  const Tab1ItemPage({this.color, this.colorName, this.index});
+  const Tab1ItemPage({this.color, this.colorName, this.index, this.randomSeed});
 
   final Color color;
   final String colorName;
   final int index;
+  final int randomSeed;
 
   @override
   State<StatefulWidget> createState() => Tab1ItemPageState();
@@ -289,7 +306,7 @@
   void initState() {
     super.initState();
     relatedColors = List<Color>.generate(10, (int index) {
-      final math.Random random = math.Random();
+      final math.Random random = math.Random(widget.randomSeed);
       return Color.fromARGB(
         255,
         (widget.color.red + random.nextInt(100) - 50).clamp(0, 255) as int,
diff --git a/dev/integration_tests/flutter_gallery/lib/demo/cupertino/cupertino_text_field_demo.dart b/dev/integration_tests/flutter_gallery/lib/demo/cupertino/cupertino_text_field_demo.dart
index 08964d6..77956dd 100644
--- a/dev/integration_tests/flutter_gallery/lib/demo/cupertino/cupertino_text_field_demo.dart
+++ b/dev/integration_tests/flutter_gallery/lib/demo/cupertino/cupertino_text_field_demo.dart
@@ -40,17 +40,15 @@
       keyboardType: TextInputType.multiline,
       prefix: const Padding(padding: EdgeInsets.symmetric(horizontal: 4.0)),
       suffix: Padding(
-        padding: const EdgeInsets.symmetric(horizontal: 4.0),
+        padding: const EdgeInsets.symmetric(horizontal: 2.0),
         child: CupertinoButton(
-          color: CupertinoColors.activeGreen,
           minSize: 0.0,
           child: const Icon(
-            CupertinoIcons.up_arrow,
-            size: 21.0,
-            color: CupertinoColors.white,
+            CupertinoIcons.arrow_up_circle_fill,
+            size: 28.0,
+            color: CupertinoColors.activeGreen,
           ),
-          padding: const EdgeInsets.all(2.0),
-          borderRadius: BorderRadius.circular(15.0),
+          padding: const EdgeInsets.only(bottom: 4),
           onPressed: ()=> setState(()=> _chatTextController.clear()),
         ),
       ),
@@ -63,7 +61,7 @@
   Widget _buildNameField() {
     return const CupertinoTextField(
       prefix: Icon(
-        CupertinoIcons.person_solid,
+        CupertinoIcons.person_fill,
         color: CupertinoColors.lightBackgroundGray,
         size: 28.0,
       ),
@@ -81,9 +79,9 @@
   Widget _buildEmailField() {
     return const CupertinoTextField(
       prefix: Icon(
-        CupertinoIcons.mail_solid,
+        CupertinoIcons.envelope_fill,
         color: CupertinoColors.lightBackgroundGray,
-        size: 28.0,
+        size: 26,
       ),
       padding: EdgeInsets.symmetric(horizontal: 6.0, vertical: 12.0),
       clearButtonMode: OverlayVisibilityMode.editing,
@@ -100,9 +98,9 @@
     return CupertinoTextField(
       controller: _locationTextController,
       prefix: const Icon(
-        CupertinoIcons.location_solid,
+        CupertinoIcons.location_fill,
         color: CupertinoColors.lightBackgroundGray,
-        size: 28.0,
+        size: 26,
       ),
       padding: const EdgeInsets.symmetric(horizontal: 6.0, vertical: 12.0),
       clearButtonMode: OverlayVisibilityMode.editing,
@@ -117,9 +115,9 @@
   Widget _buildPinField() {
     return const CupertinoTextField(
       prefix: Icon(
-        CupertinoIcons.padlock_solid,
+        CupertinoIcons.lock_open_fill,
         color: CupertinoColors.lightBackgroundGray,
-        size: 28.0,
+        size: 26,
       ),
       padding: EdgeInsets.symmetric(horizontal: 6.0, vertical: 12.0),
       clearButtonMode: OverlayVisibilityMode.editing,
@@ -137,9 +135,9 @@
     return CupertinoTextField(
       controller: TextEditingController(text: 'colleague, reading club'),
       prefix: const Icon(
-        CupertinoIcons.tags_solid,
+        CupertinoIcons.tag_fill,
         color: CupertinoColors.lightBackgroundGray,
-        size: 28.0,
+        size: 26,
       ),
       enabled: false,
       padding: const EdgeInsets.symmetric(horizontal: 6.0, vertical: 12.0),
diff --git a/dev/integration_tests/flutter_gallery/pubspec.yaml b/dev/integration_tests/flutter_gallery/pubspec.yaml
index b0cba85..8ea5749 100644
--- a/dev/integration_tests/flutter_gallery/pubspec.yaml
+++ b/dev/integration_tests/flutter_gallery/pubspec.yaml
@@ -8,12 +8,12 @@
   flutter:
     sdk: flutter
   collection: 1.15.0-nullsafety.3
-  device_info: 0.4.2+8
+  device_info: 0.4.2+9
   intl: 0.16.1
-  connectivity: 0.4.9+3
+  connectivity: 0.4.9+5
   string_scanner: 1.1.0-nullsafety.1
-  url_launcher: 5.7.2
-  cupertino_icons: 0.1.3
+  url_launcher: 5.7.5
+  cupertino_icons: 1.0.0
   video_player: 0.10.6
   scoped_model: 1.0.1
   shrine_images: 1.1.2
@@ -52,7 +52,7 @@
   flutter_goldens:
     sdk: flutter
   test: 1.16.0-nullsafety.5
-  e2e: 0.7.0+1
+  integration_test: 0.9.2+1
 
   _fe_analyzer_shared: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   analyzer: 0.39.17 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -277,4 +277,4 @@
       - asset: packages/flutter_gallery_assets/fonts/merriweather/Merriweather-Regular.ttf
       - asset: packages/flutter_gallery_assets/fonts/merriweather/Merriweather-Light.ttf
 
-# PUBSPEC CHECKSUM: 023a
+# PUBSPEC CHECKSUM: c70e
diff --git a/dev/integration_tests/flutter_gallery/test/demo/cupertino/cupertino_navigation_demo_test.dart b/dev/integration_tests/flutter_gallery/test/demo/cupertino/cupertino_navigation_demo_test.dart
new file mode 100644
index 0000000..e1698fb
--- /dev/null
+++ b/dev/integration_tests/flutter_gallery/test/demo/cupertino/cupertino_navigation_demo_test.dart
@@ -0,0 +1,41 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_gallery/demo/cupertino/cupertino_navigation_demo.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+  testWidgets('Navigation demo golden', (WidgetTester tester) async {
+    // The point is to mainly test the cupertino icons that we don't have a
+    // dependency against in the flutter/cupertino package directly.
+
+    final Future<ByteData> font = rootBundle.load(
+      'packages/cupertino_icons/assets/CupertinoIcons.ttf'
+    );
+
+    await (FontLoader('packages/cupertino_icons/CupertinoIcons')..addFont(font))
+      .load();
+
+    await tester.pumpWidget(CupertinoApp(
+      home: CupertinoNavigationDemo(randomSeed: 123456),
+    ));
+
+    await expectLater(
+      find.byType(CupertinoNavigationDemo),
+      matchesGoldenFile('cupertino_navigation_demo.screen.1.png'),
+    );
+
+    // Tap some row to go to the next page.
+    await tester.tap(find.text('Buy this cool color').first);
+    await tester.pump();
+    await tester.pump(const Duration(milliseconds: 500));
+
+    await expectLater(
+      find.byType(CupertinoNavigationDemo),
+      matchesGoldenFile('cupertino_navigation_demo.screen.2.png'),
+    );
+  });
+}
diff --git a/dev/integration_tests/flutter_gallery/test_driver/e2e_utils.dart b/dev/integration_tests/flutter_gallery/test_driver/e2e_utils.dart
deleted file mode 100644
index 813dae3..0000000
--- a/dev/integration_tests/flutter_gallery/test_driver/e2e_utils.dart
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2014 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:ui';
-
-import 'package:flutter/scheduler.dart';
-import 'package:flutter/foundation.dart';
-import 'package:flutter_test/flutter_test.dart';
-
-import 'package:e2e/e2e.dart';
-
-bool _firstRun = true;
-
-// TODO(CareF): move this to e2e after FrameTimingSummarizer goes into stable
-// branch (#63537)
-/// watches the [FrameTiming] of `action` and report it to the e2e binding.
-Future<void> watchPerformance(
-  E2EWidgetsFlutterBinding binding,
-  Future<void> action(), {
-  String reportKey = 'performance',
-}) async {
-  assert(() {
-    if (_firstRun) {
-      debugPrint(kDebugWarning);
-      _firstRun = false;
-    }
-    return true;
-  }());
-
-  // The engine could batch FrameTimings and send them only once per second.
-  // Delay for a sufficient time so either old FrameTimings are flushed and not
-  // interfering our measurements here, or new FrameTimings are all reported.
-  Future<void> delayForFrameTimings() =>
-      Future<void>.delayed(const Duration(seconds: 2));
-
-  await delayForFrameTimings(); // flush old FrameTimings
-  final List<FrameTiming> frameTimings = <FrameTiming>[];
-  final TimingsCallback watcher = frameTimings.addAll;
-  binding.addTimingsCallback(watcher);
-  await action();
-  await delayForFrameTimings(); // make sure all FrameTimings are reported
-  binding.removeTimingsCallback(watcher);
-  final FrameTimingSummarizer frameTimes = FrameTimingSummarizer(frameTimings);
-  binding.reportData = <String, dynamic>{reportKey: frameTimes.summary};
-}
diff --git a/dev/integration_tests/flutter_gallery/test_driver/transitions_perf_e2e.dart b/dev/integration_tests/flutter_gallery/test_driver/transitions_perf_e2e.dart
index 3ebe0e4..69e9be5 100644
--- a/dev/integration_tests/flutter_gallery/test_driver/transitions_perf_e2e.dart
+++ b/dev/integration_tests/flutter_gallery/test_driver/transitions_perf_e2e.dart
@@ -6,13 +6,12 @@
 import 'package:flutter/widgets.dart';
 import 'package:flutter_test/flutter_test.dart';
 
-import 'package:e2e/e2e.dart';
+import 'package:integration_test/integration_test.dart';
 
 import 'package:flutter_gallery/gallery/app.dart' show GalleryApp;
 import 'package:flutter_gallery/gallery/demos.dart';
 import 'package:flutter_gallery/demo_lists.dart';
 
-import 'e2e_utils.dart';
 import 'run_demos.dart';
 
 const List<String> kSkippedDemos = <String>[];
@@ -27,8 +26,8 @@
 
 void main([List<String> args = const <String>[]]) {
   final bool withSemantics = args.contains('--with_semantics');
-  final E2EWidgetsFlutterBinding binding =
-      E2EWidgetsFlutterBinding.ensureInitialized() as E2EWidgetsFlutterBinding;
+  final IntegrationTestWidgetsFlutterBinding binding =
+      IntegrationTestWidgetsFlutterBinding.ensureInitialized() as IntegrationTestWidgetsFlutterBinding;
   binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
   group('flutter gallery transitions on e2e', () {
     testWidgets('find.bySemanticsLabel', (WidgetTester tester) async {
@@ -44,7 +43,7 @@
         runApp(const GalleryApp(testMode: true));
         await tester.pumpAndSettle();
         // Collect timeline data for just a limited set of demos to avoid OOMs.
-        await watchPerformance(binding, () async {
+        await binding.watchPerformance(() async {
           await runDemos(kProfiledDemos, tester);
         });
 
diff --git a/dev/integration_tests/flutter_gallery/test_driver/transitions_perf_e2e_test.dart b/dev/integration_tests/flutter_gallery/test_driver/transitions_perf_e2e_test.dart
index a7d388e..d931eff 100644
--- a/dev/integration_tests/flutter_gallery/test_driver/transitions_perf_e2e_test.dart
+++ b/dev/integration_tests/flutter_gallery/test_driver/transitions_perf_e2e_test.dart
@@ -2,9 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'package:e2e/e2e_driver.dart' as driver;
+import 'package:integration_test/integration_test_driver.dart' as driver;
 
-Future<void> main() => driver.e2eDriver(
+Future<void> main() => driver.integrationDriver(
   timeout: const Duration(minutes: 5),
   responseDataCallback: (Map<String, dynamic> data) async {
     await driver.writeResponseData(
diff --git a/dev/integration_tests/gradle_deprecated_settings/pubspec.yaml b/dev/integration_tests/gradle_deprecated_settings/pubspec.yaml
index 0f78772..f46ce71 100644
--- a/dev/integration_tests/gradle_deprecated_settings/pubspec.yaml
+++ b/dev/integration_tests/gradle_deprecated_settings/pubspec.yaml
@@ -8,7 +8,7 @@
 dependencies:
   flutter:
     sdk: flutter
-  camera: 0.5.8+8
+  camera: 0.5.8+9
 
   characters: 1.1.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   collection: 1.15.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -19,4 +19,4 @@
 flutter:
   uses-material-design: true
 
-# PUBSPEC CHECKSUM: 8dce
+# PUBSPEC CHECKSUM: b3cf
diff --git a/dev/integration_tests/hybrid_android_views/pubspec.yaml b/dev/integration_tests/hybrid_android_views/pubspec.yaml
index efdb489..d8ca6b4 100644
--- a/dev/integration_tests/hybrid_android_views/pubspec.yaml
+++ b/dev/integration_tests/hybrid_android_views/pubspec.yaml
@@ -1,6 +1,8 @@
 name: hybrid_platform_views
 description: An integration test for hybrid composition on Android
 version: 1.0.0+1
+environment:
+  sdk: '>=2.9.0 <3.0.0'
 
 dependencies:
   flutter:
diff --git a/dev/integration_tests/image_loading/pubspec.yaml b/dev/integration_tests/image_loading/pubspec.yaml
index 428805a..dd3111b 100644
--- a/dev/integration_tests/image_loading/pubspec.yaml
+++ b/dev/integration_tests/image_loading/pubspec.yaml
@@ -1,5 +1,7 @@
 name: image_loading
 description: Integration testing library for Android semantics
+environment:
+  sdk: '>=2.9.0 <3.0.0'
 
 dependencies:
   flutter:
diff --git a/dev/integration_tests/ios_add2app/flutterapp/.gitignore b/dev/integration_tests/ios_add2app/flutterapp/.gitignore
new file mode 100644
index 0000000..cdecf14
--- /dev/null
+++ b/dev/integration_tests/ios_add2app/flutterapp/.gitignore
@@ -0,0 +1,41 @@
+.DS_Store
+.dart_tool/
+
+.packages
+.pub/
+
+.idea/
+.vagrant/
+.sconsign.dblite
+.svn/
+
+*.swp
+profile
+
+DerivedData/
+
+.generated/
+
+*.pbxuser
+*.mode1v3
+*.mode2v3
+*.perspectivev3
+
+!default.pbxuser
+!default.mode1v3
+!default.mode2v3
+!default.perspectivev3
+
+xcuserdata
+
+*.moved-aside
+
+*.pyc
+*sync/
+Icon?
+.tags*
+
+build/
+.android/
+.ios/
+.flutter-plugins
diff --git a/dev/integration_tests/ios_add2app/flutterapp/pubspec.yaml b/dev/integration_tests/ios_add2app/flutterapp/pubspec.yaml
index 42c8c49..92e9ef0 100644
--- a/dev/integration_tests/ios_add2app/flutterapp/pubspec.yaml
+++ b/dev/integration_tests/ios_add2app/flutterapp/pubspec.yaml
@@ -22,7 +22,7 @@
 
   # The following adds the Cupertino Icons font to your application.
   # Use with the CupertinoIcons class for iOS style icons.
-  cupertino_icons: 0.1.3
+  cupertino_icons: 1.0.0
 
   characters: 1.1.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   collection: 1.15.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -99,4 +99,4 @@
     androidPackage: com.example.iosadd2appflutter
     iosBundleIdentifier: com.example.iosAdd2appFlutter
 
-# PUBSPEC CHECKSUM: 9423
+# PUBSPEC CHECKSUM: 2920
diff --git a/dev/integration_tests/ios_add2app_life_cycle/flutterapp/pubspec.yaml b/dev/integration_tests/ios_add2app_life_cycle/flutterapp/pubspec.yaml
index a98e3ca..920036b 100644
--- a/dev/integration_tests/ios_add2app_life_cycle/flutterapp/pubspec.yaml
+++ b/dev/integration_tests/ios_add2app_life_cycle/flutterapp/pubspec.yaml
@@ -22,7 +22,7 @@
 
   # The following adds the Cupertino Icons font to your application.
   # Use with the CupertinoIcons class for iOS style icons.
-  cupertino_icons: 0.1.3
+  cupertino_icons: 1.0.0
 
   characters: 1.1.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   collection: 1.15.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -99,4 +99,4 @@
     androidPackage: com.example.iosadd2appflutter
     iosBundleIdentifier: com.example.iosAdd2appFlutter
 
-# PUBSPEC CHECKSUM: 9423
+# PUBSPEC CHECKSUM: 2920
diff --git a/dev/integration_tests/ios_app_with_extensions/pubspec.yaml b/dev/integration_tests/ios_app_with_extensions/pubspec.yaml
index 7c17802..5bd1be4 100644
--- a/dev/integration_tests/ios_app_with_extensions/pubspec.yaml
+++ b/dev/integration_tests/ios_app_with_extensions/pubspec.yaml
@@ -20,7 +20,7 @@
     sdk: flutter
   # This integration test includes a watchOS pod. Add a Flutter plugin
   # to prompt the tool to run pod install.
-  device_info: 0.4.2+8
+  device_info: 0.4.2+9
 
   characters: 1.1.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   collection: 1.15.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -91,4 +91,4 @@
   # For details regarding fonts from package dependencies,
   # see https://flutter.dev/custom-fonts/#from-packages
 
-# PUBSPEC CHECKSUM: c70b
+# PUBSPEC CHECKSUM: 220c
diff --git a/dev/integration_tests/non_nullable/pubspec.yaml b/dev/integration_tests/non_nullable/pubspec.yaml
index be4aa81..c51ec7c 100644
--- a/dev/integration_tests/non_nullable/pubspec.yaml
+++ b/dev/integration_tests/non_nullable/pubspec.yaml
@@ -10,7 +10,7 @@
 dependencies:
   flutter:
     sdk: flutter
-  cupertino_icons: 0.1.3
+  cupertino_icons: 1.0.0
 
   characters: 1.1.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   collection: 1.15.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -39,4 +39,4 @@
 flutter:
   uses-material-design: true
 
-# PUBSPEC CHECKSUM: 9423
+# PUBSPEC CHECKSUM: 2920
diff --git a/dev/integration_tests/web_compile_tests/pubspec.yaml b/dev/integration_tests/web_compile_tests/pubspec.yaml
index f546711..b429c78 100644
--- a/dev/integration_tests/web_compile_tests/pubspec.yaml
+++ b/dev/integration_tests/web_compile_tests/pubspec.yaml
@@ -1,4 +1,6 @@
 name: web_compile_tests
+environment:
+  sdk: '>=2.9.0 <3.0.0'
 
 dependencies:
   flutter:
diff --git a/dev/integration_tests/web_e2e_tests/pubspec.yaml b/dev/integration_tests/web_e2e_tests/pubspec.yaml
index c9d4cab..e14d95d 100644
--- a/dev/integration_tests/web_e2e_tests/pubspec.yaml
+++ b/dev/integration_tests/web_e2e_tests/pubspec.yaml
@@ -19,7 +19,7 @@
     sdk: flutter
   flutter_test:
     sdk: flutter
-  integration_test: 0.9.2
+  integration_test: 0.9.2+1
   http: 0.12.2
   test: 1.16.0-nullsafety.5
 
@@ -78,4 +78,4 @@
   webkit_inspection_protocol: 0.7.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   yaml: 2.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
 
-# PUBSPEC CHECKSUM: eb1f
+# PUBSPEC CHECKSUM: f37b
diff --git a/dev/manual_tests/lib/actions.dart b/dev/manual_tests/lib/actions.dart
index f830178..b929c35 100644
--- a/dev/manual_tests/lib/actions.dart
+++ b/dev/manual_tests/lib/actions.dart
@@ -194,13 +194,13 @@
 class UndoAction extends Action<UndoIntent> {
   @override
   bool isEnabled(UndoIntent intent) {
-    final UndoableActionDispatcher manager = Actions.of(primaryFocus?.context ?? FocusDemo.appKey.currentContext, nullOk: true) as UndoableActionDispatcher;
+    final UndoableActionDispatcher manager = Actions.of(primaryFocus?.context ?? FocusDemo.appKey.currentContext) as UndoableActionDispatcher;
     return manager.canUndo;
   }
 
   @override
   void invoke(UndoIntent intent) {
-    final UndoableActionDispatcher manager = Actions.of(primaryFocus?.context ?? FocusDemo.appKey.currentContext, nullOk: true) as UndoableActionDispatcher;
+    final UndoableActionDispatcher manager = Actions.of(primaryFocus?.context ?? FocusDemo.appKey.currentContext) as UndoableActionDispatcher;
     manager?.undo();
   }
 }
@@ -212,13 +212,13 @@
 class RedoAction extends Action<RedoIntent> {
   @override
   bool isEnabled(RedoIntent intent) {
-    final UndoableActionDispatcher manager = Actions.of(primaryFocus.context, nullOk: true) as UndoableActionDispatcher;
+    final UndoableActionDispatcher manager = Actions.of(primaryFocus.context) as UndoableActionDispatcher;
     return manager.canRedo;
   }
 
   @override
   RedoAction invoke(RedoIntent intent) {
-    final UndoableActionDispatcher manager = Actions.of(primaryFocus.context, nullOk: true) as UndoableActionDispatcher;
+    final UndoableActionDispatcher manager = Actions.of(primaryFocus.context) as UndoableActionDispatcher;
     manager?.redo();
     return this;
   }
diff --git a/dev/manual_tests/lib/focus.dart b/dev/manual_tests/lib/focus.dart
index 7f6a782..e06e0da 100644
--- a/dev/manual_tests/lib/focus.dart
+++ b/dev/manual_tests/lib/focus.dart
@@ -97,7 +97,7 @@
     super.dispose();
   }
 
-  bool _handleKeyPress(FocusNode node, RawKeyEvent event) {
+  KeyEventResult _handleKeyPress(FocusNode node, RawKeyEvent event) {
     if (event is RawKeyDownEvent) {
       print('Scope got key event: ${event.logicalKey}, $node');
       print('Keys down: ${RawKeyboard.instance.keysPressed}');
@@ -106,31 +106,31 @@
         if (event.isShiftPressed) {
           print('Moving to previous.');
           node.previousFocus();
-          return true;
+          return KeyEventResult.handled;
         } else {
           print('Moving to next.');
           node.nextFocus();
-          return true;
+          return KeyEventResult.handled;
         }
       }
       if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
         node.focusInDirection(TraversalDirection.left);
-        return true;
+        return KeyEventResult.handled;
       }
       if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
         node.focusInDirection(TraversalDirection.right);
-        return true;
+        return KeyEventResult.handled;
       }
       if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
         node.focusInDirection(TraversalDirection.up);
-        return true;
+        return KeyEventResult.handled;
       }
       if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
         node.focusInDirection(TraversalDirection.down);
-        return true;
+        return KeyEventResult.handled;
       }
     }
-    return false;
+    return KeyEventResult.ignored;
   }
 
   @override
diff --git a/dev/manual_tests/lib/raw_keyboard.dart b/dev/manual_tests/lib/raw_keyboard.dart
index e08b260..a511ef0 100644
--- a/dev/manual_tests/lib/raw_keyboard.dart
+++ b/dev/manual_tests/lib/raw_keyboard.dart
@@ -37,11 +37,11 @@
     super.dispose();
   }
 
-  bool _handleKeyEvent(FocusNode node, RawKeyEvent event) {
+  KeyEventResult _handleKeyEvent(FocusNode node, RawKeyEvent event) {
     setState(() {
       _event = event;
     });
-    return false;
+    return KeyEventResult.ignored;
   }
 
   String _asHex(int value) => value != null ? '0x${value.toRadixString(16)}' : 'null';
diff --git a/dev/tools/dartdoc.dart b/dev/tools/dartdoc.dart
index f7768fc..a726bb4 100644
--- a/dev/tools/dartdoc.dart
+++ b/dev/tools/dartdoc.dart
@@ -139,7 +139,6 @@
   final List<String> dartdocArgs = <String>[
     ...dartdocBaseArgs,
     '--allow-tools',
-    '--enable-experiment=non-nullable',
     if (args['json'] as bool) '--json',
     if (args['validate-links'] as bool) '--validate-links' else '--no-validate-links',
     '--link-to-source-excludes', '../../bin/cache',
diff --git a/examples/image_list/pubspec.yaml b/examples/image_list/pubspec.yaml
index f2488f8..bfa737b 100644
--- a/examples/image_list/pubspec.yaml
+++ b/examples/image_list/pubspec.yaml
@@ -12,7 +12,7 @@
 
   # The following adds the Cupertino Icons font to your application.
   # Use with the CupertinoIcons class for iOS style icons.
-  cupertino_icons: 0.1.3
+  cupertino_icons: 1.0.0
 
   characters: 1.1.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   collection: 1.15.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -54,4 +54,4 @@
   assets:
    - images/coast.jpg
 
-# PUBSPEC CHECKSUM: 9423
+# PUBSPEC CHECKSUM: 2920
diff --git a/examples/layers/analysis_options.yaml b/examples/layers/analysis_options.yaml
new file mode 100644
index 0000000..03733cd
--- /dev/null
+++ b/examples/layers/analysis_options.yaml
@@ -0,0 +1,13 @@
+# Use the parent analysis options settings and enable null-experiment.
+
+include: ../../analysis_options.yaml
+
+analyzer:
+  enable-experiment:
+    - non-nullable
+  errors:
+    always_require_non_null_named_parameters: false # not needed with nnbd
+    type_init_formals: false # https://github.com/dart-lang/linter/issues/2192
+    unrelated_type_equality_checks: false # https://github.com/dart-lang/linter/issues/2196
+    void_checks: false # https://github.com/dart-lang/linter/issues/2185
+    unnecessary_null_comparison: false # Turned off until null-safe rollout is complete.
diff --git a/examples/layers/rendering/custom_coordinate_systems.dart b/examples/layers/rendering/custom_coordinate_systems.dart
index 5307b56..0402b77 100644
--- a/examples/layers/rendering/custom_coordinate_systems.dart
+++ b/examples/layers/rendering/custom_coordinate_systems.dart
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// @dart = 2.8
+// @dart = 2.10
 
 // This example shows how to build a render tree with a non-cartesian coordinate
 // system. Most of the guts of this examples are in src/sector_layout.dart.
diff --git a/examples/layers/rendering/src/sector_layout.dart b/examples/layers/rendering/src/sector_layout.dart
index 6cb2d3c..dbef998 100644
--- a/examples/layers/rendering/src/sector_layout.dart
+++ b/examples/layers/rendering/src/sector_layout.dart
@@ -2,13 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// @dart = 2.8
+// @dart = 2.10
 
 import 'dart:math' as math;
 
 import 'package:flutter/rendering.dart';
 import 'package:flutter/gestures.dart';
-import 'package:meta/meta.dart';
 
 const double kTwoPi = 2 * math.pi;
 
@@ -33,11 +32,11 @@
   final double maxDeltaTheta;
 
   double constrainDeltaRadius(double deltaRadius) {
-    return deltaRadius.clamp(minDeltaRadius, maxDeltaRadius) as double;
+    return deltaRadius.clamp(minDeltaRadius, maxDeltaRadius);
   }
 
   double constrainDeltaTheta(double deltaTheta) {
-    return deltaTheta.clamp(minDeltaTheta, maxDeltaTheta) as double;
+    return deltaTheta.clamp(minDeltaTheta, maxDeltaTheta);
   }
 
   @override
@@ -49,7 +48,7 @@
   @override
   bool debugAssertIsValid({
     bool isAppliedConstraint = false,
-    InformationCollector informationCollector,
+    InformationCollector? informationCollector,
   }) {
     assert(isNormalized);
     return isNormalized;
@@ -102,7 +101,7 @@
   // RenderSectors always use SectorParentData subclasses, as they need to be
   // able to read their position information for painting and hit testing.
   @override
-  SectorParentData get parentData => super.parentData as SectorParentData;
+  SectorParentData? get parentData => super.parentData as SectorParentData?;
 
   SectorDimensions getIntrinsicDimensions(SectorConstraints constraints, double radius) {
     return SectorDimensions.withConstraints(constraints);
@@ -145,27 +144,27 @@
   @override
   Rect get semanticBounds => Rect.fromLTWH(-deltaRadius, -deltaRadius, 2.0 * deltaRadius, 2.0 * deltaRadius);
 
-  bool hitTest(SectorHitTestResult result, { double radius, double theta }) {
-    if (radius < parentData.radius || radius >= parentData.radius + deltaRadius ||
-        theta < parentData.theta || theta >= parentData.theta + deltaTheta)
+  bool hitTest(SectorHitTestResult result, { required double radius, required double theta }) {
+    if (radius < parentData!.radius || radius >= parentData!.radius + deltaRadius ||
+        theta < parentData!.theta || theta >= parentData!.theta + deltaTheta)
       return false;
     hitTestChildren(result, radius: radius, theta: theta);
     result.add(SectorHitTestEntry(this, radius: radius, theta: theta));
     return true;
   }
-  void hitTestChildren(SectorHitTestResult result, { double radius, double theta }) { }
+  void hitTestChildren(SectorHitTestResult result, { required double radius, required double theta }) { }
 
-  double deltaRadius;
-  double deltaTheta;
+  late double deltaRadius;
+  late double deltaTheta;
 }
 
 abstract class RenderDecoratedSector extends RenderSector {
 
-  RenderDecoratedSector(BoxDecoration decoration) : _decoration = decoration;
+  RenderDecoratedSector(BoxDecoration? decoration) : _decoration = decoration;
 
-  BoxDecoration _decoration;
-  BoxDecoration get decoration => _decoration;
-  set decoration(BoxDecoration value) {
+  BoxDecoration? _decoration;
+  BoxDecoration? get decoration => _decoration;
+  set decoration(BoxDecoration? value) {
     if (value == _decoration)
       return;
     _decoration = value;
@@ -182,16 +181,16 @@
     if (_decoration == null)
       return;
 
-    if (_decoration.color != null) {
+    if (_decoration!.color != null) {
       final Canvas canvas = context.canvas;
-      final Paint paint = Paint()..color = _decoration.color;
+      final Paint paint = Paint()..color = _decoration!.color!;
       final Path path = Path();
-      final double outerRadius = parentData.radius + deltaRadius;
+      final double outerRadius = parentData!.radius + deltaRadius;
       final Rect outerBounds = Rect.fromLTRB(offset.dx-outerRadius, offset.dy-outerRadius, offset.dx+outerRadius, offset.dy+outerRadius);
-      path.arcTo(outerBounds, parentData.theta, deltaTheta, true);
-      final double innerRadius = parentData.radius;
+      path.arcTo(outerBounds, parentData!.theta, deltaTheta, true);
+      final double innerRadius = parentData!.radius;
       final Rect innerBounds = Rect.fromLTRB(offset.dx-innerRadius, offset.dy-innerRadius, offset.dx+innerRadius, offset.dy+innerRadius);
-      path.arcTo(innerBounds, parentData.theta + deltaTheta, -deltaTheta, false);
+      path.arcTo(innerBounds, parentData!.theta + deltaTheta, -deltaTheta, false);
       path.close();
       canvas.drawPath(path, paint);
     }
@@ -202,25 +201,25 @@
 class SectorChildListParentData extends SectorParentData with ContainerParentDataMixin<RenderSector> { }
 
 class RenderSectorWithChildren extends RenderDecoratedSector with ContainerRenderObjectMixin<RenderSector, SectorChildListParentData> {
-  RenderSectorWithChildren(BoxDecoration decoration) : super(decoration);
+  RenderSectorWithChildren(BoxDecoration? decoration) : super(decoration);
 
   @override
-  void hitTestChildren(SectorHitTestResult result, { double radius, double theta }) {
-    RenderSector child = lastChild;
+  void hitTestChildren(SectorHitTestResult result, { required double radius, required double theta }) {
+    RenderSector? child = lastChild;
     while (child != null) {
       if (child.hitTest(result, radius: radius, theta: theta))
         return;
-      final SectorChildListParentData childParentData = child.parentData as SectorChildListParentData;
+      final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData;
       child = childParentData.previousSibling;
     }
   }
 
   @override
   void visitChildren(RenderObjectVisitor visitor) {
-    RenderSector child = lastChild;
+    RenderSector? child = lastChild;
     while (child != null) {
       visitor(child);
-      final SectorChildListParentData childParentData = child.parentData as SectorChildListParentData;
+      final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData;
       child = childParentData.previousSibling;
     }
   }
@@ -230,7 +229,7 @@
   // lays out RenderSector children in a ring
 
   RenderSectorRing({
-    BoxDecoration decoration,
+    BoxDecoration? decoration,
     double deltaRadius = double.infinity,
     double padding = 0.0,
   }) : _padding = padding,
@@ -275,7 +274,7 @@
     final double paddingTheta = math.atan(padding / (radius + outerDeltaRadius));
     double innerTheta = paddingTheta; // increments with each child
     double remainingDeltaTheta = math.max(0.0, constraints.maxDeltaTheta - (innerTheta + paddingTheta));
-    RenderSector child = firstChild;
+    RenderSector? child = firstChild;
     while (child != null) {
       final SectorConstraints innerConstraints = SectorConstraints(
         maxDeltaRadius: innerDeltaRadius,
@@ -284,7 +283,7 @@
       final SectorDimensions childDimensions = child.getIntrinsicDimensions(innerConstraints, childRadius);
       innerTheta += childDimensions.deltaTheta;
       remainingDeltaTheta -= childDimensions.deltaTheta;
-      final SectorChildListParentData childParentData = child.parentData as SectorChildListParentData;
+      final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData;
       child = childParentData.nextSibling;
       if (child != null) {
         innerTheta += paddingTheta;
@@ -302,23 +301,23 @@
     deltaRadius = constraints.constrainDeltaRadius(desiredDeltaRadius);
     assert(deltaRadius < double.infinity);
     final double innerDeltaRadius = deltaRadius - padding * 2.0;
-    final double childRadius = parentData.radius + padding;
-    final double paddingTheta = math.atan(padding / (parentData.radius + deltaRadius));
+    final double childRadius = parentData!.radius + padding;
+    final double paddingTheta = math.atan(padding / (parentData!.radius + deltaRadius));
     double innerTheta = paddingTheta; // increments with each child
     double remainingDeltaTheta = constraints.maxDeltaTheta - (innerTheta + paddingTheta);
-    RenderSector child = firstChild;
+    RenderSector? child = firstChild;
     while (child != null) {
       final SectorConstraints innerConstraints = SectorConstraints(
         maxDeltaRadius: innerDeltaRadius,
         maxDeltaTheta: remainingDeltaTheta,
       );
       assert(child.parentData is SectorParentData);
-      child.parentData.theta = innerTheta;
-      child.parentData.radius = childRadius;
+      child.parentData!.theta = innerTheta;
+      child.parentData!.radius = childRadius;
       child.layout(innerConstraints, parentUsesSize: true);
       innerTheta += child.deltaTheta;
       remainingDeltaTheta -= child.deltaTheta;
-      final SectorChildListParentData childParentData = child.parentData as SectorChildListParentData;
+      final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData;
       child = childParentData.nextSibling;
       if (child != null) {
         innerTheta += paddingTheta;
@@ -334,10 +333,10 @@
   void paint(PaintingContext context, Offset offset) {
     // TODO(ianh): avoid code duplication
     super.paint(context, offset);
-    RenderSector child = firstChild;
+    RenderSector? child = firstChild;
     while (child != null) {
       context.paintChild(child, offset);
-      final SectorChildListParentData childParentData = child.parentData as SectorChildListParentData;
+      final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData;
       child = childParentData.nextSibling;
     }
   }
@@ -348,7 +347,7 @@
   // lays out RenderSector children in a stack
 
   RenderSectorSlice({
-    BoxDecoration decoration,
+    BoxDecoration? decoration,
     double deltaTheta = kTwoPi,
     double padding = 0.0,
   }) : _padding = padding, _desiredDeltaTheta = deltaTheta, super(decoration);
@@ -384,12 +383,12 @@
   @override
   SectorDimensions getIntrinsicDimensions(SectorConstraints constraints, double radius) {
     assert(parentData is SectorParentData);
-    final double paddingTheta = math.atan(padding / parentData.radius);
+    final double paddingTheta = math.atan(padding / parentData!.radius);
     final double outerDeltaTheta = constraints.constrainDeltaTheta(desiredDeltaTheta);
     final double innerDeltaTheta = outerDeltaTheta - paddingTheta * 2.0;
-    double childRadius = parentData.radius + padding;
+    double childRadius = parentData!.radius + padding;
     double remainingDeltaRadius = constraints.maxDeltaRadius - (padding * 2.0);
-    RenderSector child = firstChild;
+    RenderSector? child = firstChild;
     while (child != null) {
       final SectorConstraints innerConstraints = SectorConstraints(
         maxDeltaRadius: remainingDeltaRadius,
@@ -398,13 +397,13 @@
       final SectorDimensions childDimensions = child.getIntrinsicDimensions(innerConstraints, childRadius);
       childRadius += childDimensions.deltaRadius;
       remainingDeltaRadius -= childDimensions.deltaRadius;
-      final SectorChildListParentData childParentData = child.parentData as SectorChildListParentData;
+      final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData;
       child = childParentData.nextSibling;
       childRadius += padding;
       remainingDeltaRadius -= padding;
     }
     return SectorDimensions.withConstraints(constraints,
-                                                deltaRadius: childRadius - parentData.radius,
+                                                deltaRadius: childRadius - parentData!.radius,
                                                 deltaTheta: outerDeltaTheta);
   }
 
@@ -413,28 +412,28 @@
     assert(parentData is SectorParentData);
     deltaTheta = constraints.constrainDeltaTheta(desiredDeltaTheta);
     assert(deltaTheta <= kTwoPi);
-    final double paddingTheta = math.atan(padding / parentData.radius);
-    final double innerTheta = parentData.theta + paddingTheta;
+    final double paddingTheta = math.atan(padding / parentData!.radius);
+    final double innerTheta = parentData!.theta + paddingTheta;
     final double innerDeltaTheta = deltaTheta - paddingTheta * 2.0;
-    double childRadius = parentData.radius + padding;
+    double childRadius = parentData!.radius + padding;
     double remainingDeltaRadius = constraints.maxDeltaRadius - (padding * 2.0);
-    RenderSector child = firstChild;
+    RenderSector? child = firstChild;
     while (child != null) {
       final SectorConstraints innerConstraints = SectorConstraints(
         maxDeltaRadius: remainingDeltaRadius,
         maxDeltaTheta: innerDeltaTheta,
       );
-      child.parentData.theta = innerTheta;
-      child.parentData.radius = childRadius;
+      child.parentData!.theta = innerTheta;
+      child.parentData!.radius = childRadius;
       child.layout(innerConstraints, parentUsesSize: true);
       childRadius += child.deltaRadius;
       remainingDeltaRadius -= child.deltaRadius;
-      final SectorChildListParentData childParentData = child.parentData as SectorChildListParentData;
+      final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData;
       child = childParentData.nextSibling;
       childRadius += padding;
       remainingDeltaRadius -= padding;
     }
-    deltaRadius = childRadius - parentData.radius;
+    deltaRadius = childRadius - parentData!.radius;
   }
 
   // offset must point to the center of our circle
@@ -443,11 +442,11 @@
   void paint(PaintingContext context, Offset offset) {
     // TODO(ianh): avoid code duplication
     super.paint(context, offset);
-    RenderSector child = firstChild;
+    RenderSector? child = firstChild;
     while (child != null) {
       assert(child.parentData is SectorChildListParentData);
       context.paintChild(child, offset);
-      final SectorChildListParentData childParentData = child.parentData as SectorChildListParentData;
+      final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData;
       child = childParentData.nextSibling;
     }
   }
@@ -456,7 +455,7 @@
 
 class RenderBoxToRenderSectorAdapter extends RenderBox with RenderObjectWithChildMixin<RenderSector> {
 
-  RenderBoxToRenderSectorAdapter({ double innerRadius = 0.0, RenderSector child })
+  RenderBoxToRenderSectorAdapter({ double innerRadius = 0.0, RenderSector? child })
     : _innerRadius = innerRadius {
     this.child = child;
   }
@@ -507,13 +506,13 @@
     double height = double.infinity,
   }) {
     assert(child is RenderSector);
-    assert(child.parentData is SectorParentData);
+    assert(child!.parentData is SectorParentData);
     assert(width != null);
     assert(height != null);
     if (!width.isFinite && !height.isFinite)
       return Size.zero;
     final double maxChildDeltaRadius = math.max(0.0, math.min(width, height) / 2.0 - innerRadius);
-    final SectorDimensions childDimensions = child.getIntrinsicDimensions(SectorConstraints(maxDeltaRadius: maxChildDeltaRadius), innerRadius);
+    final SectorDimensions childDimensions = child!.getIntrinsicDimensions(SectorConstraints(maxDeltaRadius: maxChildDeltaRadius), innerRadius);
     final double dimension = (innerRadius + childDimensions.deltaRadius) * 2.0;
     return Size.square(dimension);
   }
@@ -526,12 +525,12 @@
       return;
     }
     assert(child is RenderSector);
-    assert(child.parentData is SectorParentData);
+    assert(child!.parentData is SectorParentData);
     final double maxChildDeltaRadius = math.min(constraints.maxWidth, constraints.maxHeight) / 2.0 - innerRadius;
-    child.parentData.radius = innerRadius;
-    child.parentData.theta = 0.0;
-    child.layout(SectorConstraints(maxDeltaRadius: maxChildDeltaRadius), parentUsesSize: true);
-    final double dimension = (innerRadius + child.deltaRadius) * 2.0;
+    child!.parentData!.radius = innerRadius;
+    child!.parentData!.theta = 0.0;
+    child!.layout(SectorConstraints(maxDeltaRadius: maxChildDeltaRadius), parentUsesSize: true);
+    final double dimension = (innerRadius + child!.deltaRadius) * 2.0;
     size = constraints.constrain(Size(dimension, dimension));
   }
 
@@ -541,12 +540,12 @@
     if (child != null) {
       final Rect bounds = offset & size;
       // we move the offset to the center of the circle for the RenderSectors
-      context.paintChild(child, bounds.center);
+      context.paintChild(child!, bounds.center);
     }
   }
 
   @override
-  bool hitTest(BoxHitTestResult result, { Offset position }) {
+  bool hitTest(BoxHitTestResult result, { required Offset position }) {
     if (child == null)
       return false;
     double x = position.dx;
@@ -559,11 +558,11 @@
     final double theta = (math.atan2(x, -y) - math.pi / 2.0) % kTwoPi;
     if (radius < innerRadius)
       return false;
-    if (radius >= innerRadius + child.deltaRadius)
+    if (radius >= innerRadius + child!.deltaRadius)
       return false;
-    if (theta > child.deltaTheta)
+    if (theta > child!.deltaTheta)
       return false;
-    child.hitTest(SectorHitTestResult.wrap(result), radius: radius, theta: theta);
+    child!.hitTest(SectorHitTestResult.wrap(result), radius: radius, theta: theta);
     result.add(BoxHitTestEntry(this, position));
     return true;
   }
@@ -634,7 +633,7 @@
   /// Creates a box hit test entry.
   ///
   /// The [radius] and [theta] argument must not be null.
-  SectorHitTestEntry(RenderSector target, { @required this.radius,  @required this.theta })
+  SectorHitTestEntry(RenderSector target, { required this.radius,  required this.theta })
       : assert(radius != null),
         assert(theta != null),
         super(target);
diff --git a/packages/flutter/lib/src/animation/animation_controller.dart b/packages/flutter/lib/src/animation/animation_controller.dart
index 03016b8..978fb4a 100644
--- a/packages/flutter/lib/src/animation/animation_controller.dart
+++ b/packages/flutter/lib/src/animation/animation_controller.dart
@@ -645,18 +645,27 @@
     _checkStatusChanged();
   }
 
-  /// Drives the animation with a critically damped spring (within [lowerBound]
-  /// and [upperBound]) and initial velocity.
+  /// Drives the animation with a spring (within [lowerBound] and [upperBound])
+  /// and initial velocity.
   ///
   /// If velocity is positive, the animation will complete, otherwise it will
   /// dismiss.
   ///
+  /// The [springDescription] parameter can be used to specify a custom [SpringType.criticallyDamped]
+  /// or [SpringType.overDamped] spring to drive the animation with. Defaults to null, which uses a
+  /// [SpringType.criticallyDamped] spring. See [SpringDescription.withDampingRatio] for how
+  /// to create a suitable [SpringDescription].
+  ///
+  /// The resulting spring simulation cannot be of type [SpringType.underDamped],
+  /// as this can lead to unexpected look of the produced animation.
+  ///
   /// Returns a [TickerFuture] that completes when the animation is complete.
   ///
   /// The most recently returned [TickerFuture], if any, is marked as having been
   /// canceled, meaning the future never completes and its [TickerFuture.orCancel]
   /// derivative future completes with a [TickerCanceled] error.
-  TickerFuture fling({ double velocity = 1.0, AnimationBehavior? animationBehavior }) {
+  TickerFuture fling({ double velocity = 1.0, SpringDescription? springDescription, AnimationBehavior? animationBehavior }) {
+    springDescription ??= _kFlingSpringDescription;
     _direction = velocity < 0.0 ? _AnimationDirection.reverse : _AnimationDirection.forward;
     final double target = velocity < 0.0 ? lowerBound - _kFlingTolerance.distance
                                          : upperBound + _kFlingTolerance.distance;
@@ -673,8 +682,13 @@
           break;
       }
     }
-    final Simulation simulation = SpringSimulation(_kFlingSpringDescription, value, target, velocity * scale)
+    final SpringSimulation simulation = SpringSimulation(springDescription, value, target, velocity * scale)
       ..tolerance = _kFlingTolerance;
+    assert(
+      simulation.type != SpringType.underDamped,
+      'The resulting spring simulation is of type SpringType.underDamped.\n'
+      'This can lead to unexpected look of the animation, please adjust the springDescription parameter'
+    );
     stop();
     return _startSimulation(simulation);
   }
diff --git a/packages/flutter/lib/src/cupertino/context_menu.dart b/packages/flutter/lib/src/cupertino/context_menu.dart
index a74d104..eaeb4e5 100644
--- a/packages/flutter/lib/src/cupertino/context_menu.dart
+++ b/packages/flutter/lib/src/cupertino/context_menu.dart
@@ -731,7 +731,7 @@
   }
 
   @override
-  bool didPop(T result) {
+  bool didPop(T? result) {
     _updateTweenRects();
     return super.didPop(result);
   }
diff --git a/packages/flutter/lib/src/cupertino/icons.dart b/packages/flutter/lib/src/cupertino/icons.dart
index e27f3e7..671e0af 100644
--- a/packages/flutter/lib/src/cupertino/icons.dart
+++ b/packages/flutter/lib/src/cupertino/icons.dart
@@ -21,7 +21,7 @@
 ///   cupertino_icons: ^1.0.0
 /// ```
 ///
-/// For a map of available icons for use, see <http://flutter.github.io/cupertino_icons>
+/// For a map of available icons for use, see <https://flutter.github.io/cupertino_icons>
 /// for cupertino_icons version >1.0.0.
 ///
 /// See <https://github.com/flutter/cupertino_icons/blob/master/map.png> for
diff --git a/packages/flutter/lib/src/cupertino/nav_bar.dart b/packages/flutter/lib/src/cupertino/nav_bar.dart
index f108e49..0ae2590 100644
--- a/packages/flutter/lib/src/cupertino/nav_bar.dart
+++ b/packages/flutter/lib/src/cupertino/nav_bar.dart
@@ -1379,15 +1379,18 @@
 
     // Replicate the Icon logic here to get a tightly sized icon and add
     // custom non-square padding.
-    Widget iconWidget = Text.rich(
-      TextSpan(
-        text: String.fromCharCode(CupertinoIcons.back.codePoint),
-        style: TextStyle(
-          inherit: false,
-          color: textStyle.color,
-          fontSize: 34.0,
-          fontFamily: CupertinoIcons.back.fontFamily,
-          package: CupertinoIcons.back.fontPackage,
+    Widget iconWidget = Padding(
+      padding: const EdgeInsetsDirectional.only(start: 6, end: 2),
+      child: Text.rich(
+        TextSpan(
+          text: String.fromCharCode(CupertinoIcons.back.codePoint),
+          style: TextStyle(
+            inherit: false,
+            color: textStyle.color,
+            fontSize: 30.0,
+            fontFamily: CupertinoIcons.back.fontFamily,
+            package: CupertinoIcons.back.fontPackage,
+          ),
         ),
       ),
     );
diff --git a/packages/flutter/lib/src/cupertino/route.dart b/packages/flutter/lib/src/cupertino/route.dart
index ace6af9..d1ddf5d 100644
--- a/packages/flutter/lib/src/cupertino/route.dart
+++ b/packages/flutter/lib/src/cupertino/route.dart
@@ -1049,7 +1049,7 @@
 ///  * [CupertinoActionSheet], which is the widget usually returned by the
 ///    `builder` argument to [showCupertinoModalPopup].
 ///  * <https://developer.apple.com/design/human-interface-guidelines/ios/views/action-sheets/>
-Future<T> showCupertinoModalPopup<T>({
+Future<T?> showCupertinoModalPopup<T>({
   required BuildContext context,
   required WidgetBuilder builder,
   ImageFilter? filter,
@@ -1134,7 +1134,7 @@
 ///  * [showDialog], which displays a Material-style dialog.
 ///  * [showGeneralDialog], which allows for customization of the dialog popup.
 ///  * <https://developer.apple.com/ios/human-interface-guidelines/views/alerts/>
-Future<T> showCupertinoDialog<T>({
+Future<T?> showCupertinoDialog<T>({
   required BuildContext context,
   required WidgetBuilder builder,
   bool useRootNavigator = true,
diff --git a/packages/flutter/lib/src/cupertino/slider.dart b/packages/flutter/lib/src/cupertino/slider.dart
index 6eab333..697f100 100644
--- a/packages/flutter/lib/src/cupertino/slider.dart
+++ b/packages/flutter/lib/src/cupertino/slider.dart
@@ -548,6 +548,7 @@
     super.describeSemanticsConfiguration(config);
 
     config.isSemanticBoundary = isInteractive;
+    config.isSlider = true;
     if (isInteractive) {
       config.textDirection = textDirection;
       config.onIncrease = _increaseAction;
diff --git a/packages/flutter/lib/src/cupertino/tab_scaffold.dart b/packages/flutter/lib/src/cupertino/tab_scaffold.dart
index 2c65a09..306a6ec 100644
--- a/packages/flutter/lib/src/cupertino/tab_scaffold.dart
+++ b/packages/flutter/lib/src/cupertino/tab_scaffold.dart
@@ -619,8 +619,9 @@
   }
 
   @override
-  CupertinoTabController fromPrimitives(Object data) {
-    return CupertinoTabController(initialIndex: data as int);
+  CupertinoTabController fromPrimitives(Object? data) {
+    assert(data != null);
+    return CupertinoTabController(initialIndex: data! as int);
   }
 
   @override
diff --git a/packages/flutter/lib/src/cupertino/text_field.dart b/packages/flutter/lib/src/cupertino/text_field.dart
index 0dc8027..5d3e0dd 100644
--- a/packages/flutter/lib/src/cupertino/text_field.dart
+++ b/packages/flutter/lib/src/cupertino/text_field.dart
@@ -46,11 +46,11 @@
   darkColor: Color(0xFF050505),
 );
 
-// Value inspected from Xcode 11 & iOS 13.0 Simulator.
+// Value inspected from Xcode 12 & iOS 14.0 Simulator.
 // Note it may not be consistent with https://developer.apple.com/design/resources/.
 const CupertinoDynamicColor _kClearButtonColor = CupertinoDynamicColor.withBrightness(
-  color: Color(0xFF636366),
-  darkColor: Color(0xFFAEAEB2),
+  color: Color(0x33000000),
+  darkColor: Color(0x33FFFFFF),
 );
 
 // An eyeballed value that moves the cursor slightly left of where it is
diff --git a/packages/flutter/lib/src/cupertino/text_selection.dart b/packages/flutter/lib/src/cupertino/text_selection.dart
index 90e51e6..b2c86d2 100644
--- a/packages/flutter/lib/src/cupertino/text_selection.dart
+++ b/packages/flutter/lib/src/cupertino/text_selection.dart
@@ -781,7 +781,7 @@
     }
     if (slot is IndexedSlot) {
       assert(renderObject.debugValidateChild(child));
-      renderObject.insert(child as RenderBox, after: slot.value?.renderObject as RenderBox);
+      renderObject.insert(child as RenderBox, after: slot.value?.renderObject as RenderBox?);
       return;
     }
     assert(false, 'slot must be _CupertinoTextSelectionToolbarItemsSlot or IndexedSlot');
@@ -1255,4 +1255,4 @@
 class _NullWidget extends Widget {
   @override
   Element createElement() => throw UnimplementedError();
-}
\ No newline at end of file
+}
diff --git a/packages/flutter/lib/src/foundation/change_notifier.dart b/packages/flutter/lib/src/foundation/change_notifier.dart
index 0293cf4..f44cacb 100644
--- a/packages/flutter/lib/src/foundation/change_notifier.dart
+++ b/packages/flutter/lib/src/foundation/change_notifier.dart
@@ -147,7 +147,30 @@
 
   /// Register a closure to be called when the object changes.
   ///
+  /// If the given closure is already registered, an additional instance is
+  /// added, and must be removed the same number of times it is added before it
+  /// will stop being called.
+  ///
   /// This method must not be called after [dispose] has been called.
+  ///
+  /// {@template flutter.foundation.ChangeNotifier.addListener}
+  /// If a listener is added twice, and is removed once during an iteration
+  /// (e.g. in response to a notification), it will still be called again. If,
+  /// on the other hand, it is removed as many times as it was registered, then
+  /// it will no longer be called. This odd behavior is the result of the
+  /// [ChangeNotifier] not being able to determine which listener is being
+  /// removed, since they are identical, therefore it will conservatively still
+  /// call all the listeners when it knows that any are still registered.
+  ///
+  /// This surprising behavior can be unexpectedly observed when registering a
+  /// listener on two separate objects which are both forwarding all
+  /// registrations to a common upstream object.
+  /// {@endtemplate}
+  ///
+  /// See also:
+  ///
+  ///  * [removeListener], which removes a previously registered closure from
+  ///    the list of closures that are notified when the object changes.
   @override
   void addListener(VoidCallback listener) {
     assert(_debugAssertNotDisposed());
@@ -161,18 +184,12 @@
   ///
   /// This method must not be called after [dispose] has been called.
   ///
-  /// If a listener had been added twice, and is removed once during an
-  /// iteration (i.e. in response to a notification), it will still be called
-  /// again. If, on the other hand, it is removed as many times as it was
-  /// registered, then it will no longer be called. This odd behavior is the
-  /// result of the [ChangeNotifier] not being able to determine which listener
-  /// is being removed, since they are identical, and therefore conservatively
-  /// still calling all the listeners when it knows that any are still
-  /// registered.
+  /// {@macro flutter.foundation.ChangeNotifier.addListener}
   ///
-  /// This surprising behavior can be unexpectedly observed when registering a
-  /// listener on two separate objects which are both forwarding all
-  /// registrations to a common upstream object.
+  /// See also:
+  ///
+  ///  * [addListener], which registers a closure to be called when the object
+  ///    changes.
   @override
   void removeListener(VoidCallback listener) {
     assert(_debugAssertNotDisposed());
@@ -208,7 +225,7 @@
   ///
   /// This method must not be called after [dispose] has been called.
   ///
-  /// Surprising behavior can result when reentrantly removing a listener (i.e.
+  /// Surprising behavior can result when reentrantly removing a listener (e.g.
   /// in response to a notification) that has been registered multiple times.
   /// See the discussion at [removeListener].
   @protected
diff --git a/packages/flutter/lib/src/foundation/collections.dart b/packages/flutter/lib/src/foundation/collections.dart
index d6f9f8f..d62b156 100644
--- a/packages/flutter/lib/src/foundation/collections.dart
+++ b/packages/flutter/lib/src/foundation/collections.dart
@@ -166,7 +166,7 @@
   final int firstLength = middle - start;
   final int secondLength = end - middle;
   // secondLength is always the same as firstLength, or one greater.
-  final List<T?> scratchSpace = List<T?>.filled(secondLength, null, growable: false);
+  final List<T> scratchSpace = List<T>.filled(secondLength, list[start]);
   _mergeSort<T>(list, compare, middle, end, scratchSpace, 0);
   final int firstTarget = end - firstLength;
   _mergeSort<T>(list, compare, start, middle, list, firstTarget);
@@ -273,7 +273,7 @@
     int Function(T, T) compare,
     int start,
     int end,
-    List<T?> target,
+    List<T> target,
     int targetOffset,
     ) {
   final int length = end - start;
@@ -316,10 +316,10 @@
   List<T> firstList,
   int firstStart,
   int firstEnd,
-  List<T?> secondList,
+  List<T> secondList,
   int secondStart,
   int secondEnd,
-  List<T?> target,
+  List<T> target,
   int targetOffset,
 ) {
   // No empty lists reaches here.
@@ -328,7 +328,7 @@
   int cursor1 = firstStart;
   int cursor2 = secondStart;
   T firstElement = firstList[cursor1++];
-  T secondElement = secondList[cursor2++] as T;
+  T secondElement = secondList[cursor2++];
   while (true) {
     if (compare(firstElement, secondElement) <= 0) {
       target[targetOffset++] = firstElement;
@@ -340,7 +340,7 @@
     } else {
       target[targetOffset++] = secondElement;
       if (cursor2 != secondEnd) {
-        secondElement = secondList[cursor2++] as T;
+        secondElement = secondList[cursor2++];
         continue;
       }
       // Second list empties first. Flushing first list here.
diff --git a/packages/flutter/lib/src/material/bottom_sheet.dart b/packages/flutter/lib/src/material/bottom_sheet.dart
index 57dd8a2..0087473 100644
--- a/packages/flutter/lib/src/material/bottom_sheet.dart
+++ b/packages/flutter/lib/src/material/bottom_sheet.dart
@@ -644,7 +644,7 @@
 ///  * [DraggableScrollableSheet], which allows you to create a bottom sheet
 ///    that grows and then becomes scrollable once it reaches its maximum size.
 ///  * <https://material.io/design/components/sheets-bottom.html#modal-bottom-sheet>
-Future<T> showModalBottomSheet<T>({
+Future<T?> showModalBottomSheet<T>({
   required BuildContext context,
   required WidgetBuilder builder,
   Color? backgroundColor,
diff --git a/packages/flutter/lib/src/material/dialog.dart b/packages/flutter/lib/src/material/dialog.dart
index a944f75..7b93e5d 100644
--- a/packages/flutter/lib/src/material/dialog.dart
+++ b/packages/flutter/lib/src/material/dialog.dart
@@ -955,7 +955,7 @@
 ///  * [showCupertinoDialog], which displays an iOS-style dialog.
 ///  * [showGeneralDialog], which allows for customization of the dialog popup.
 ///  * <https://material.io/design/components/dialogs.html>
-Future<T> showDialog<T>({
+Future<T?> showDialog<T>({
   required BuildContext context,
   WidgetBuilder? builder,
   bool barrierDismissible = true,
diff --git a/packages/flutter/lib/src/material/input_decorator.dart b/packages/flutter/lib/src/material/input_decorator.dart
index 9bc4cc6..cd3875b 100644
--- a/packages/flutter/lib/src/material/input_decorator.dart
+++ b/packages/flutter/lib/src/material/input_decorator.dart
@@ -980,7 +980,7 @@
       _boxSize(icon).width
       + contentPadding.left
       + _boxSize(prefixIcon).width
-      + (decoration.border!.isOutline ? 0.0 : _boxSize(suffixIcon).width)
+      + _boxSize(suffixIcon).width
       + contentPadding.right));
     boxToBaseline[label] = _layoutLineBox(
       label,
diff --git a/packages/flutter/lib/src/material/material_state.dart b/packages/flutter/lib/src/material/material_state.dart
index 9403b75..59f7eda 100644
--- a/packages/flutter/lib/src/material/material_state.dart
+++ b/packages/flutter/lib/src/material/material_state.dart
@@ -120,7 +120,7 @@
 /// }
 /// ```
 /// {@end-tool}
-abstract class MaterialStateColor extends Color implements MaterialStateProperty<Color?> {
+abstract class MaterialStateColor extends Color implements MaterialStateProperty<Color> {
   /// Creates a [MaterialStateColor].
   const MaterialStateColor(int defaultValue) : super(defaultValue);
 
diff --git a/packages/flutter/lib/src/material/pickers/date_picker_dialog.dart b/packages/flutter/lib/src/material/pickers/date_picker_dialog.dart
index 45fe8b4..5c11427 100644
--- a/packages/flutter/lib/src/material/pickers/date_picker_dialog.dart
+++ b/packages/flutter/lib/src/material/pickers/date_picker_dialog.dart
@@ -99,7 +99,7 @@
 ///  * [CalendarDatePicker], which provides the calendar grid used by the date picker dialog.
 ///  * [InputDatePickerFormField], which provides a text input field for entering dates.
 ///
-Future<DateTime> showDatePicker({
+Future<DateTime?> showDatePicker({
   required BuildContext context,
   required DateTime initialDate,
   required DateTime firstDate,
diff --git a/packages/flutter/lib/src/material/pickers/date_range_picker_dialog.dart b/packages/flutter/lib/src/material/pickers/date_range_picker_dialog.dart
index 6e5042e..9b5ce42 100644
--- a/packages/flutter/lib/src/material/pickers/date_range_picker_dialog.dart
+++ b/packages/flutter/lib/src/material/pickers/date_range_picker_dialog.dart
@@ -105,7 +105,7 @@
 ///    select a single date.
 ///  * [DateTimeRange], which is used to describe a date range.
 ///
-Future<DateTimeRange> showDateRangePicker({
+Future<DateTimeRange?> showDateRangePicker({
   required BuildContext context,
   DateTimeRange? initialDateRange,
   required DateTime firstDate,
diff --git a/packages/flutter/lib/src/material/popup_menu.dart b/packages/flutter/lib/src/material/popup_menu.dart
index 47263dd..7c4df12 100644
--- a/packages/flutter/lib/src/material/popup_menu.dart
+++ b/packages/flutter/lib/src/material/popup_menu.dart
@@ -829,7 +829,7 @@
 ///    calling this method automatically.
 ///  * [SemanticsConfiguration.namesRoute], for a description of edge triggered
 ///    semantics.
-Future<T> showMenu<T>({
+Future<T?> showMenu<T>({
   required BuildContext context,
   required RelativeRect position,
   required List<PopupMenuEntry<T>> items,
@@ -1088,7 +1088,7 @@
     final List<PopupMenuEntry<T>> items = widget.itemBuilder(context);
     // Only show the menu if there is something to show
     if (items.isNotEmpty) {
-      showMenu<T>(
+      showMenu<T?>(
         context: context,
         elevation: widget.elevation ?? popupMenuTheme.elevation,
         items: items,
@@ -1098,7 +1098,7 @@
         color: widget.color ?? popupMenuTheme.color,
         captureInheritedThemes: widget.captureInheritedThemes,
       )
-      .then<void>((T newValue) {
+      .then<void>((T? newValue) {
         if (!mounted)
           return null;
         if (newValue == null) {
diff --git a/packages/flutter/lib/src/material/range_slider.dart b/packages/flutter/lib/src/material/range_slider.dart
index ef07c18..35495f6 100644
--- a/packages/flutter/lib/src/material/range_slider.dart
+++ b/packages/flutter/lib/src/material/range_slider.dart
@@ -1529,6 +1529,7 @@
     final SemanticsConfiguration config = SemanticsConfiguration();
     config.isEnabled = isEnabled;
     config.textDirection = textDirection;
+    config.isSlider = true;
     if (isEnabled) {
       config.onIncrease = increaseAction;
       config.onDecrease = decreaseAction;
diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart
index 7294b8f..5092e5b 100644
--- a/packages/flutter/lib/src/material/scaffold.dart
+++ b/packages/flutter/lib/src/material/scaffold.dart
@@ -2146,7 +2146,10 @@
   /// See also:
   ///
   ///   * [ScaffoldMessenger], this should be used instead to manage [SnackBar]s.
-  // TODO(Piinks): Deprecate & defer to ScaffoldMessenger after customers are migrated.
+  @Deprecated(
+    'Use ScaffoldMessenger.showSnackBar. '
+    'This feature was deprecated after v1.23.0-14.0.pre.'
+  )
   ScaffoldFeatureController<SnackBar, SnackBarClosedReason> showSnackBar(SnackBar snackbar) {
     _snackBarController ??= SnackBar.createAnimationController(vsync: this)
       ..addStatusListener(_handleSnackBarStatusChange);
@@ -2205,7 +2208,10 @@
   /// See also:
   ///
   ///   * [ScaffoldMessenger], this should be used instead to manage [SnackBar]s.
-  // TODO(Piinks): Deprecate & defer to ScaffoldMessenger after customers are migrated
+  @Deprecated(
+    'Use ScaffoldMessenger.removeCurrentSnackBar. '
+    'This feature was deprecated after v1.23.0-14.0.pre.'
+  )
   void removeCurrentSnackBar({ SnackBarClosedReason reason = SnackBarClosedReason.remove }) {
     assert(reason != null);
 
@@ -2247,7 +2253,10 @@
   /// See also:
   ///
   ///   * [ScaffoldMessenger], this should be used instead to manage [SnackBar]s.
-  // TODO(Piinks): Deprecate & defer to ScaffoldMessenger after customers are migrated.
+  @Deprecated(
+    'Use ScaffoldMessenger.hideCurrentSnackBar. '
+    'This feature was deprecated after v1.23.0-14.0.pre.'
+  )
   void hideCurrentSnackBar({ SnackBarClosedReason reason = SnackBarClosedReason.hide }) {
     assert(reason != null);
 
diff --git a/packages/flutter/lib/src/material/search.dart b/packages/flutter/lib/src/material/search.dart
index 0e4aeb9..e4e8996 100644
--- a/packages/flutter/lib/src/material/search.dart
+++ b/packages/flutter/lib/src/material/search.dart
@@ -49,7 +49,7 @@
 /// See also:
 ///
 ///  * [SearchDelegate] to define the content of the search page.
-Future<T> showSearch<T>({
+Future<T?> showSearch<T>({
   required BuildContext context,
   required SearchDelegate<T> delegate,
   String? query = '',
@@ -387,7 +387,7 @@
   }
 
   @override
-  void didComplete(T result) {
+  void didComplete(T? result) {
     super.didComplete(result);
     assert(delegate._route == this);
     delegate._route = null;
diff --git a/packages/flutter/lib/src/material/slider.dart b/packages/flutter/lib/src/material/slider.dart
index e58e2db..e77af47 100644
--- a/packages/flutter/lib/src/material/slider.dart
+++ b/packages/flutter/lib/src/material/slider.dart
@@ -707,6 +707,7 @@
 
     return Semantics(
       container: true,
+      slider: true,
       child: FocusableActionDetector(
         actions: _actionMap,
         shortcuts: _shortcutMap,
diff --git a/packages/flutter/lib/src/material/time_picker.dart b/packages/flutter/lib/src/material/time_picker.dart
index d4e46c1..1b465e5 100644
--- a/packages/flutter/lib/src/material/time_picker.dart
+++ b/packages/flutter/lib/src/material/time_picker.dart
@@ -2175,7 +2175,7 @@
 ///    date picker.
 ///  * [TimePickerThemeData], which allows you to customize the colors,
 ///    typography, and shape of the time picker.
-Future<TimeOfDay> showTimePicker({
+Future<TimeOfDay?> showTimePicker({
   required BuildContext context,
   required TimeOfDay initialTime,
   TransitionBuilder? builder,
diff --git a/packages/flutter/lib/src/painting/image_provider.dart b/packages/flutter/lib/src/painting/image_provider.dart
index 110ca73..8ccae23 100644
--- a/packages/flutter/lib/src/painting/image_provider.dart
+++ b/packages/flutter/lib/src/painting/image_provider.dart
@@ -379,12 +379,12 @@
   /// [FlutterError.onError], and the method will return null.
   ///
   /// A completed return value of null indicates that an error has occurred.
-  Future<ImageCacheStatus> obtainCacheStatus({
+  Future<ImageCacheStatus?> obtainCacheStatus({
     required ImageConfiguration configuration,
     ImageErrorListener? handleError,
   }) {
     assert(configuration != null);
-    final Completer<ImageCacheStatus> completer = Completer<ImageCacheStatus>();
+    final Completer<ImageCacheStatus?> completer = Completer<ImageCacheStatus?>();
     _createErrorHandlerAndKey(
       configuration,
       (T key, ImageErrorListener innerHandleError) {
diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart
index 067ba9b..5824927 100644
--- a/packages/flutter/lib/src/rendering/editable.dart
+++ b/packages/flutter/lib/src/rendering/editable.dart
@@ -676,14 +676,24 @@
         }
       } else {
         if (rightArrow && newSelection.extentOffset < _plainText.length) {
-          final int nextExtent = nextCharacter(newSelection.extentOffset, _plainText);
+          int nextExtent;
+          if (!shift && !wordModifier && !lineModifier && newSelection.start != newSelection.end) {
+            nextExtent = newSelection.end;
+          } else {
+            nextExtent = nextCharacter(newSelection.extentOffset, _plainText);
+          }
           final int distance = nextExtent - newSelection.extentOffset;
           newSelection = newSelection.copyWith(extentOffset: nextExtent);
           if (shift) {
             _cursorResetLocation += distance;
           }
         } else if (leftArrow && newSelection.extentOffset > 0) {
-          final int previousExtent = previousCharacter(newSelection.extentOffset, _plainText);
+          int previousExtent;
+          if (!shift && !wordModifier && !lineModifier && newSelection.start != newSelection.end) {
+            previousExtent = newSelection.start;
+          } else {
+            previousExtent = previousCharacter(newSelection.extentOffset, _plainText);
+          }
           final int distance = newSelection.extentOffset - previousExtent;
           newSelection = newSelection.copyWith(extentOffset: previousExtent);
           if (shift) {
diff --git a/packages/flutter/lib/src/rendering/table.dart b/packages/flutter/lib/src/rendering/table.dart
index 476def3..35174c4 100644
--- a/packages/flutter/lib/src/rendering/table.dart
+++ b/packages/flutter/lib/src/rendering/table.dart
@@ -390,7 +390,7 @@
        _textBaseline = textBaseline,
        _defaultVerticalAlignment = defaultVerticalAlignment,
        _configuration = configuration {
-    _children = <RenderBox>[]..length = _columns * _rows;
+    _children = <RenderBox?>[]..length = _columns * _rows;
     this.rowDecorations = rowDecorations; // must use setter to initialize box painters array
     children?.forEach(addRow);
   }
@@ -645,7 +645,7 @@
     // update our internal values
     _columns = columns;
     _rows = cells.length ~/ columns;
-    _children = cells.toList();
+    _children = List<RenderBox?>.from(cells);
     assert(_children.length == rows * columns);
     markNeedsLayout();
   }
diff --git a/packages/flutter/lib/src/services/restoration.dart b/packages/flutter/lib/src/services/restoration.dart
index 42e33ee..54d9a94 100644
--- a/packages/flutter/lib/src/services/restoration.dart
+++ b/packages/flutter/lib/src/services/restoration.dart
@@ -151,13 +151,13 @@
       return SynchronousFuture<RestorationBucket?>(_rootBucket);
     }
     if (_pendingRootBucket == null) {
-      _pendingRootBucket = Completer<RestorationBucket>();
+      _pendingRootBucket = Completer<RestorationBucket?>();
       _getRootBucketFromEngine();
     }
     return _pendingRootBucket!.future;
   }
   RestorationBucket? _rootBucket; // May be null to indicate that restoration is turned off.
-  Completer<RestorationBucket>? _pendingRootBucket;
+  Completer<RestorationBucket?>? _pendingRootBucket;
   bool _rootBucketIsValid = false;
 
   /// Returns true for the frame after [rootBucket] has been replaced with a
@@ -186,7 +186,7 @@
   void _parseAndHandleRestorationUpdateFromEngine(Map<dynamic, dynamic>? update) {
     handleRestorationUpdateFromEngine(
       enabled: update != null && update['enabled'] as bool,
-      data: update == null ? null : update['data'] as Uint8List,
+      data: update == null ? null : update['data'] as Uint8List?,
     );
   }
 
@@ -580,10 +580,10 @@
   ///  * [remove], which removes a value from the bucket.
   ///  * [contains], which checks whether any value is stored under a given
   ///    restoration ID.
-  P read<P>(String restorationId) {
+  P? read<P>(String restorationId) {
     assert(_debugAssertNotDisposed());
     assert(restorationId != null);
-    return _rawValues[restorationId] as P;
+    return _rawValues[restorationId] as P?;
   }
 
   /// Stores the provided `value` of type `P` under the provided `restorationId`
@@ -624,11 +624,11 @@
   ///  * [write], which stores a value in the bucket.
   ///  * [contains], which checks whether any value is stored under a given
   ///    restoration ID.
-  P remove<P>(String restorationId) {
+  P? remove<P>(String restorationId) {
     assert(_debugAssertNotDisposed());
     assert(restorationId != null);
     final bool needsUpdate = _rawValues.containsKey(restorationId);
-    final P result = _rawValues.remove(restorationId) as P;
+    final P? result = _rawValues.remove(restorationId) as P?;
     if (_rawValues.isEmpty) {
       _rawData.remove(_valuesMapKey);
     }
diff --git a/packages/flutter/lib/src/widgets/actions.dart b/packages/flutter/lib/src/widgets/actions.dart
index f66903a..f06f95a 100644
--- a/packages/flutter/lib/src/widgets/actions.dart
+++ b/packages/flutter/lib/src/widgets/actions.dart
@@ -6,6 +6,7 @@
 import 'package:flutter/scheduler.dart';
 import 'package:flutter/gestures.dart';
 import 'package:flutter/rendering.dart';
+import 'package:flutter/services.dart';
 
 import 'basic.dart';
 import 'focus_manager.dart';
@@ -28,7 +29,7 @@
   return parent;
 }
 
-/// A class representing a particular configuration of an [Action].
+/// An abstract class representing a particular configuration of an [Action].
 ///
 /// This class is what the [Shortcuts.shortcuts] map has as values, and is used
 /// by an [ActionDispatcher] to look up an action and invoke it, giving it this
@@ -40,11 +41,12 @@
 ///    [Intent] using the [Actions] widget that most tightly encloses the given
 ///    [BuildContext].
 @immutable
-class Intent with Diagnosticable {
+abstract class Intent with Diagnosticable {
   /// A const constructor for an [Intent].
   const Intent();
 
-  /// An intent that can't be mapped to an action.
+  /// An intent that is mapped to a [DoNothingAction], which, as the name
+  /// implies, does nothing.
   ///
   /// This Intent is mapped to an action in the [WidgetsApp] that does nothing,
   /// so that it can be bound to a key in a [Shortcuts] widget in order to
@@ -92,6 +94,19 @@
   /// [notifyActionListeners] to notify any listeners of the change.
   bool isEnabled(covariant T intent) => true;
 
+  /// Indicates whether this action should treat key events mapped to this
+  /// action as being "handled" when it is invoked via the key event.
+  ///
+  /// If the key is handled, then no other key event handlers in the focus chain
+  /// will receive the event.
+  ///
+  /// If the key event is not handled, it will be passed back to the engine, and
+  /// continue to be processed there, allowing text fields and non-Flutter
+  /// widgets to receive the key event.
+  ///
+  /// The default implementation returns true.
+  bool consumesKey(covariant T intent) => true;
+
   /// Called when the action is to be performed.
   ///
   /// This is called by the [ActionDispatcher] when an action is invoked via
@@ -380,21 +395,25 @@
   /// the action is a [ContextAction], then the context from the [primaryFocus]
   /// is used.
   ///
-  /// Returns the object returned from [Action.invoke] if the action was
-  /// successfully invoked, and null if the action is not enabled. May also
-  /// return null if [Action.invoke] returns null.
-  Object? invokeAction(covariant Action<Intent> action, covariant Intent intent, [BuildContext? context]) {
+  /// Returns the object returned from [Action.invoke].
+  ///
+  /// The caller must receive a `true` result from [Action.isEnabled] before
+  /// calling this function. This function will assert if the action is not
+  /// enabled when called.
+  Object? invokeAction(
+    covariant Action<Intent> action,
+    covariant Intent intent, [
+    BuildContext? context,
+  ]) {
     assert(action != null);
     assert(intent != null);
-    context ??= primaryFocus?.context;
-    if (action.isEnabled(intent)) {
-      if (action is ContextAction) {
-        return action.invoke(intent, context);
-      } else {
-        return action.invoke(intent);
-      }
+    assert(action.isEnabled(intent), 'Action must be enabled when calling invokeAction');
+    if (action is ContextAction) {
+      context ??= primaryFocus?.context;
+      return action.invoke(intent, context);
+    } else {
+      return action.invoke(intent);
     }
-    return null;
   }
 }
 
@@ -496,7 +515,11 @@
     final Action<T>? action = Actions.find<T>(context, nullOk: nullOk);
     if (action != null && action.isEnabled(intent)) {
       return () {
-        Actions.of(context).invokeAction(action, intent, context);
+        // Could be that the action was enabled when the closure was created,
+        // but is now no longer enabled, so check again.
+        if (action.isEnabled(intent)) {
+          Actions.of(context).invokeAction(action, intent, context);
+        }
       };
     }
     return null;
@@ -507,12 +530,26 @@
   /// Creates a dependency on the [Actions] widget that maps the bound action so
   /// that if the actions change, the context will be rebuilt and find the
   /// updated action.
-  static Action<T>? find<T extends Intent>(BuildContext context, {bool nullOk = false}) {
+  ///
+  /// The optional `intent` argument supplies the type of the intent to look for
+  /// if the concrete type of the intent sought isn't available. If not
+  /// supplied, then `T` is used.
+  static Action<T>? find<T extends Intent>(BuildContext context, {bool nullOk = false, T? intent}) {
     Action<T>? action;
 
+    // Specialize the type if a runtime example instance of the intent is given.
+    // This allows this function to be called by code that doesn't know the
+    // concrete type of the intent at compile time.
+    final Type type = intent?.runtimeType ?? T;
+    assert(type != Intent,
+      'The type passed to "find" resolved to "Intent": either a non-Intent'
+      'generic type argument or an example intent derived from Intent must be'
+      'specified. Intent may be used as the generic type as long as the optional'
+      '"intent" argument is passed.');
+
     _visitActionsAncestors(context, (InheritedElement element) {
       final _ActionsMarker actions = element.widget as _ActionsMarker;
-      final Action<T>? result = actions.actions[T] as Action<T>?;
+      final Action<T>? result = actions.actions[type] as Action<T>?;
       if (result != null) {
         context.dependOnInheritedElement(element);
         action = result;
@@ -526,14 +563,14 @@
         return true;
       }
       if (action == null) {
-        throw FlutterError('Unable to find an action for a $T in an $Actions widget '
+        throw FlutterError('Unable to find an action for a $type in an $Actions widget '
             'in the given context.\n'
             "$Actions.find() was called on a context that doesn\'t contain an "
             '$Actions widget with a mapping for the given intent type.\n'
             'The context used was:\n'
             '  $context\n'
             'The intent type requested was:\n'
-            '  $T');
+            '  $type');
       }
       return true;
     }());
@@ -543,31 +580,11 @@
   /// Returns the [ActionDispatcher] associated with the [Actions] widget that
   /// most tightly encloses the given [BuildContext].
   ///
-  /// Will throw if no ambient [Actions] widget is found.
-  ///
-  /// If `nullOk` is set to true, then if no ambient [Actions] widget is found,
-  /// this will return null.
-  ///
-  /// The `context` argument must not be null.
-  static ActionDispatcher of(BuildContext context, {bool nullOk = false}) {
+  /// Will return a newly created [ActionDispatcher] if no ambient [Actions]
+  /// widget is found.
+  static ActionDispatcher of(BuildContext context) {
     assert(context != null);
     final _ActionsMarker? marker = context.dependOnInheritedWidgetOfExactType<_ActionsMarker>();
-    assert(() {
-      if (nullOk) {
-        return true;
-      }
-      if (marker == null) {
-        throw FlutterError('Unable to find an $Actions widget in the given context.\n'
-            '$Actions.of() was called with a context that does not contain an '
-            '$Actions widget.\n'
-            'No $Actions ancestor could be found starting from the context that '
-            'was passed to $Actions.of(). This can happen if the context comes '
-            'from a widget above those widgets.\n'
-            'The context used was:\n'
-            '  $context');
-      }
-      return true;
-    }());
     return marker?.dispatcher ?? _findDispatcher(context);
   }
 
@@ -632,9 +649,15 @@
       }
       return true;
     }());
-    // Invoke the action we found using the relevant dispatcher from the Actions
-    // Element we found.
-    return actionElement != null ? _findDispatcher(actionElement!).invokeAction(action!, intent, context) : null;
+    if (actionElement == null || action == null) {
+      return null;
+    }
+    if (action!.isEnabled(intent)) {
+      // Invoke the action we found using the relevant dispatcher from the Actions
+      // Element we found.
+      return _findDispatcher(actionElement!).invokeAction(action!, intent, context);
+    }
+    return null;
   }
 
   @override
@@ -1116,12 +1139,19 @@
   }
 }
 
-/// An [Intent], that, as the name implies, is bound to a [DoNothingAction].
+/// An [Intent], that is bound to a [DoNothingAction].
 ///
 /// Attaching a [DoNothingIntent] to a [Shortcuts] mapping is one way to disable
-/// a keyboard shortcut defined by a widget higher in the widget hierarchy.
+/// a keyboard shortcut defined by a widget higher in the widget hierarchy and
+/// consume any key event that triggers it via a shortcut.
 ///
 /// This intent cannot be subclassed.
+///
+/// See also:
+///
+///  * [DoNothingAndStopPropagationIntent], a similar intent that will not
+///    handle the key event, but will still keep it from being passed to other key
+///    handlers in the focus chain.
 class DoNothingIntent extends Intent {
   /// Creates a const [DoNothingIntent].
   factory DoNothingIntent() => const DoNothingIntent._();
@@ -1130,17 +1160,61 @@
   const DoNothingIntent._();
 }
 
-/// An [Action], that, as the name implies, does nothing.
+/// An [Intent], that is bound to a [DoNothingAction], but, in addition to not
+/// performing an action, also stops the propagation of the key event bound to
+/// this intent to other key event handlers in the focus chain.
 ///
-/// Attaching a [DoNothingAction] to an [Actions] mapping is one way to disable
-/// an action defined by a widget higher in the widget hierarchy.
+/// Attaching a [DoNothingAndStopPropagationIntent] to a [Shortcuts.shortcuts]
+/// mapping is one way to disable a keyboard shortcut defined by a widget higher
+/// in the widget hierarchy. In addition, the bound [DoNothingAction] will
+/// return false from [DoNothingAction.consumesKey], causing the key bound to
+/// this intent to be passed on to the platform embedding as "not handled" with
+/// out passing it to other key handlers in the focus chain (e.g. parent
+/// `Shortcuts` widgets higher up in the chain).
 ///
-/// This action can be bound to any intent.
+/// This intent cannot be subclassed.
 ///
 /// See also:
-///  - [DoNothingIntent], which is an intent that can be bound to a keystroke in
+///
+///  * [DoNothingIntent], a similar intent that will handle the key event.
+class DoNothingAndStopPropagationIntent extends Intent {
+  /// Creates a const [DoNothingAndStopPropagationIntent].
+  factory DoNothingAndStopPropagationIntent() => const DoNothingAndStopPropagationIntent._();
+
+  // Make DoNothingAndStopPropagationIntent constructor private so it can't be subclassed.
+  const DoNothingAndStopPropagationIntent._();
+}
+
+/// An [Action], that doesn't perform any action when invoked.
+///
+/// Attaching a [DoNothingAction] to an [Actions.actions] mapping is a way to
+/// disable an action defined by a widget higher in the widget hierarchy.
+///
+/// If [consumesKey] returns false, then not only will this action do nothing,
+/// but it will stop the propagation of the key event used to trigger it to
+/// other widgets in the focus chain and tell the embedding that the key wasn't
+/// handled, allowing text input fields or other non-Flutter elements to receive
+/// that key event. The return value of [consumesKey] can be set via the
+/// `consumesKey` argument to the constructor.
+///
+/// This action can be bound to any [Intent].
+///
+/// See also:
+///  - [DoNothingIntent], which is an intent that can be bound to a [KeySet] in
 ///    a [Shortcuts] widget to do nothing.
+///  - [DoNothingAndStopPropagationIntent], which is an intent that can be bound
+///    to a [KeySet] in a [Shortcuts] widget to do nothing and also stop key event
+///    propagation to other key handlers in the focus chain.
 class DoNothingAction extends Action<Intent> {
+  /// Creates a [DoNothingAction].
+  ///
+  /// The optional [consumesKey] argument defaults to true.
+  DoNothingAction({bool consumesKey = true}) : _consumesKey = consumesKey;
+
+  @override
+  bool consumesKey(Intent intent) => _consumesKey;
+  final bool _consumesKey;
+
   @override
   void invoke(Intent intent) {}
 }
diff --git a/packages/flutter/lib/src/widgets/app.dart b/packages/flutter/lib/src/widgets/app.dart
index 333cf35..926daa7 100644
--- a/packages/flutter/lib/src/widgets/app.dart
+++ b/packages/flutter/lib/src/widgets/app.dart
@@ -1095,6 +1095,7 @@
   /// The default value of [WidgetsApp.actions].
   static Map<Type, Action<Intent>> defaultActions = <Type, Action<Intent>>{
     DoNothingIntent: DoNothingAction(),
+    DoNothingAndStopPropagationIntent: DoNothingAction(consumesKey: false),
     RequestFocusIntent: RequestFocusAction(),
     NextFocusIntent: NextFocusAction(),
     PreviousFocusIntent: PreviousFocusAction(),
diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart
index 431ee70..cfb1f33 100644
--- a/packages/flutter/lib/src/widgets/basic.dart
+++ b/packages/flutter/lib/src/widgets/basic.dart
@@ -6960,6 +6960,25 @@
 /// would be presented as a separate feature than the checkbox, and
 /// the user would not be able to be sure that they were related.
 ///
+/// {@tool snippet}
+/// This snippet shows how to use [MergeSemantics] to merge the semantics of
+/// a [Checkbox] and [Text] widget.
+///
+/// ```dart
+/// MergeSemantics(
+///   child: Row(
+///     children: <Widget>[
+///       Checkbox(
+///         value: true,
+///         onChanged: (bool value) => null,
+///       ),
+///       const Text("Settings"),
+///     ],
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
 /// Be aware that if two nodes in the subtree have conflicting
 /// semantics, the result may be nonsensical. For example, a subtree
 /// with a checked checkbox and an unchecked checkbox will be
diff --git a/packages/flutter/lib/src/widgets/dismissible.dart b/packages/flutter/lib/src/widgets/dismissible.dart
index 799880c..b6c823f 100644
--- a/packages/flutter/lib/src/widgets/dismissible.dart
+++ b/packages/flutter/lib/src/widgets/dismissible.dart
@@ -28,7 +28,7 @@
 /// confirm or veto a dismiss gesture.
 ///
 /// Used by [Dismissible.confirmDismiss].
-typedef ConfirmDismissCallback = Future<bool> Function(DismissDirection direction);
+typedef ConfirmDismissCallback = Future<bool?> Function(DismissDirection direction);
 
 /// The direction in which a [Dismissible] can be dismissed.
 enum DismissDirection {
@@ -120,7 +120,7 @@
   /// If the returned Future<bool> completes true, then this widget will be
   /// dismissed, otherwise it will be moved back to its original location.
   ///
-  /// If the returned Future<bool> completes to false or null the [onResize]
+  /// If the returned Future<bool?> completes to false or null the [onResize]
   /// and [onDismissed] callbacks will not run.
   final ConfirmDismissCallback? confirmDismiss;
 
@@ -458,7 +458,7 @@
     updateKeepAlive();
   }
 
-  Future<bool> _confirmStartResizeAnimation() async {
+  Future<bool?> _confirmStartResizeAnimation() async {
     if (widget.confirmDismiss != null) {
       final DismissDirection direction = _dismissDirection!;
       assert(direction != null);
diff --git a/packages/flutter/lib/src/widgets/focus_manager.dart b/packages/flutter/lib/src/widgets/focus_manager.dart
index e473cc3..4c224ba 100644
--- a/packages/flutter/lib/src/widgets/focus_manager.dart
+++ b/packages/flutter/lib/src/widgets/focus_manager.dart
@@ -31,11 +31,32 @@
   return true;
 }
 
+/// An enum that describes how to handle a key event handled by a
+/// [FocusOnKeyCallback].
+enum KeyEventResult {
+  /// The key event has been handled, and the event should not be propagated to
+  /// other key event handlers.
+  handled,
+  /// The key event has not been handled, and the event should continue to be
+  /// propagated to other key event handlers, even non-Flutter ones.
+  ignored,
+  /// The key event has not been handled, but the key event should not be
+  /// propagated to other key event handlers.
+  ///
+  /// It will be returned to the platform embedding to be propagated to text
+  /// fields and non-Flutter key event handlers on the platform.
+  skipRemainingHandlers,
+}
+
 /// Signature of a callback used by [Focus.onKey] and [FocusScope.onKey]
 /// to receive key events.
 ///
 /// The [node] is the node that received the event.
-typedef FocusOnKeyCallback = bool Function(FocusNode node, RawKeyEvent event);
+///
+/// Returns a [KeyEventResult] that describes how, and whether, the key event
+/// was handled.
+// TODO(gspencergoog): Convert this from dynamic to KeyEventResult once migration is complete.
+typedef FocusOnKeyCallback = dynamic Function(FocusNode node, RawKeyEvent event);
 
 /// An attachment point for a [FocusNode].
 ///
@@ -321,7 +342,7 @@
 ///     }
 ///   }
 ///
-///   bool _handleKeyPress(FocusNode node, RawKeyEvent event) {
+///   KeyEventResult _handleKeyPress(FocusNode node, RawKeyEvent event) {
 ///     if (event is RawKeyDownEvent) {
 ///       print('Focus node ${node.debugLabel} got key event: ${event.logicalKey}');
 ///       if (event.logicalKey == LogicalKeyboardKey.keyR) {
@@ -329,22 +350,22 @@
 ///         setState(() {
 ///           _color = Colors.red;
 ///         });
-///         return true;
+///         return KeyEventResult.handled;
 ///       } else if (event.logicalKey == LogicalKeyboardKey.keyG) {
 ///         print('Changing color to green.');
 ///         setState(() {
 ///           _color = Colors.green;
 ///         });
-///         return true;
+///         return KeyEventResult.handled;
 ///       } else if (event.logicalKey == LogicalKeyboardKey.keyB) {
 ///         print('Changing color to blue.');
 ///         setState(() {
 ///           _color = Colors.blue;
 ///         });
-///         return true;
+///         return KeyEventResult.handled;
 ///       }
 ///     }
-///     return false;
+///     return KeyEventResult.ignored;
 ///   }
 ///
 ///   @override
@@ -1613,17 +1634,43 @@
     _updateHighlightMode();
 
     assert(_focusDebug('Received key event ${event.logicalKey}'));
-    // Walk the current focus from the leaf to the root, calling each one's
-    // onKey on the way up, and if one responds that they handled it, stop.
     if (_primaryFocus == null) {
       assert(_focusDebug('No primary focus for key event, ignored: $event'));
       return false;
     }
+
+    // Walk the current focus from the leaf to the root, calling each one's
+    // onKey on the way up, and if one responds that they handled it or want to
+    // stop propagation, stop.
     bool handled = false;
     for (final FocusNode node in <FocusNode>[_primaryFocus!, ..._primaryFocus!.ancestors]) {
-      if (node.onKey != null && node.onKey!(node, event)) {
-        assert(_focusDebug('Node $node handled key event $event.'));
-        handled = true;
+      if (node.onKey != null) {
+        // TODO(gspencergoog): Convert this from dynamic to KeyEventResult once migration is complete.
+        final dynamic result = node.onKey!(node, event);
+        assert(result is bool || result is KeyEventResult,
+            'Value returned from onKey handler must be a non-null bool or KeyEventResult, not ${result.runtimeType}');
+        if (result is KeyEventResult) {
+          switch (result) {
+            case KeyEventResult.handled:
+              assert(_focusDebug('Node $node handled key event $event.'));
+              handled = true;
+              break;
+            case KeyEventResult.skipRemainingHandlers:
+              assert(_focusDebug('Node $node stopped key event propagation: $event.'));
+              handled = false;
+              break;
+            case KeyEventResult.ignored:
+              continue;
+          }
+        } else if (result is bool){
+          if (result) {
+            assert(_focusDebug('Node $node handled key event $event.'));
+            handled = true;
+            break;
+          } else {
+            continue;
+          }
+        }
         break;
       }
     }
diff --git a/packages/flutter/lib/src/widgets/focus_scope.dart b/packages/flutter/lib/src/widgets/focus_scope.dart
index 4a95ab7..b37d1c1 100644
--- a/packages/flutter/lib/src/widgets/focus_scope.dart
+++ b/packages/flutter/lib/src/widgets/focus_scope.dart
@@ -60,7 +60,7 @@
 /// ```dart
 /// Color _color = Colors.white;
 ///
-/// bool _handleKeyPress(FocusNode node, RawKeyEvent event) {
+/// KeyEventResult _handleKeyPress(FocusNode node, RawKeyEvent event) {
 ///   if (event is RawKeyDownEvent) {
 ///     print('Focus node ${node.debugLabel} got key event: ${event.logicalKey}');
 ///     if (event.logicalKey == LogicalKeyboardKey.keyR) {
@@ -68,22 +68,22 @@
 ///       setState(() {
 ///         _color = Colors.red;
 ///       });
-///       return true;
+///       return KeyEventResult.handled;
 ///     } else if (event.logicalKey == LogicalKeyboardKey.keyG) {
 ///       print('Changing color to green.');
 ///       setState(() {
 ///         _color = Colors.green;
 ///       });
-///       return true;
+///       return KeyEventResult.handled;
 ///     } else if (event.logicalKey == LogicalKeyboardKey.keyB) {
 ///       print('Changing color to blue.');
 ///       setState(() {
 ///         _color = Colors.blue;
 ///       });
-///       return true;
+///       return KeyEventResult.handled;
 ///     }
 ///   }
-///   return false;
+///   return KeyEventResult.ignored;
 /// }
 ///
 /// @override
diff --git a/packages/flutter/lib/src/widgets/heroes.dart b/packages/flutter/lib/src/widgets/heroes.dart
index f7377cd..572beb1 100644
--- a/packages/flutter/lib/src/widgets/heroes.dart
+++ b/packages/flutter/lib/src/widgets/heroes.dart
@@ -300,7 +300,7 @@
           // the Hero is inside a nested Navigator and should only be
           // considered for animation if it is part of the top-most route in
           // that nested Navigator and if that route is also a PageRoute.
-          final ModalRoute<Object>? heroRoute = ModalRoute.of(hero);
+          final ModalRoute<Object?>? heroRoute = ModalRoute.of(hero);
           if (heroRoute != null && heroRoute is PageRoute && heroRoute.isCurrent) {
             inviteHero(hero, tag);
           }
diff --git a/packages/flutter/lib/src/widgets/localizations.dart b/packages/flutter/lib/src/widgets/localizations.dart
index ef61693..8de4803 100644
--- a/packages/flutter/lib/src/widgets/localizations.dart
+++ b/packages/flutter/lib/src/widgets/localizations.dart
@@ -450,7 +450,7 @@
     assert(context != null);
     assert(type != null);
     final _LocalizationsScope? scope = context.dependOnInheritedWidgetOfExactType<_LocalizationsScope>();
-    return scope?.localizationsState.resourcesFor<T>(type);
+    return scope?.localizationsState.resourcesFor<T?>(type);
   }
 
   @override
diff --git a/packages/flutter/lib/src/widgets/modal_barrier.dart b/packages/flutter/lib/src/widgets/modal_barrier.dart
index c4d3ce3..3d1b693 100644
--- a/packages/flutter/lib/src/widgets/modal_barrier.dart
+++ b/packages/flutter/lib/src/widgets/modal_barrier.dart
@@ -159,7 +159,7 @@
   ///
   ///  * [ModalRoute.barrierColor], which controls this property for the
   ///    [AnimatedModalBarrier] built by [ModalRoute] pages.
-  Animation<Color> get color => listenable as Animation<Color>;
+  Animation<Color?> get color => listenable as Animation<Color?>;
 
   /// Whether touching the barrier will pop the current route off the [Navigator].
   ///
diff --git a/packages/flutter/lib/src/widgets/navigator.dart b/packages/flutter/lib/src/widgets/navigator.dart
index 33ad7a6..6884e89 100644
--- a/packages/flutter/lib/src/widgets/navigator.dart
+++ b/packages/flutter/lib/src/widgets/navigator.dart
@@ -306,8 +306,8 @@
   /// The future completes with the value given to [Navigator.pop], if any, or
   /// else the value of [currentResult]. See [didComplete] for more discussion
   /// on this topic.
-  Future<T> get popped => _popCompleter.future;
-  final Completer<T> _popCompleter = Completer<T>();
+  Future<T?> get popped => _popCompleter.future;
+  final Completer<T?> _popCompleter = Completer<T?>();
 
   /// A request was made to pop this route. If the route can handle it
   /// internally (e.g. because it has its own stack of internal state) then
@@ -329,7 +329,7 @@
   /// See [popped], [didComplete], and [currentResult] for a discussion of the
   /// `result` argument.
   @mustCallSuper
-  bool didPop(T result) {
+  bool didPop(T? result) {
     didComplete(result);
     return true;
   }
@@ -351,7 +351,7 @@
   /// iOS-style back gesture. See [NavigatorState.didStartUserGesture].
   @protected
   @mustCallSuper
-  void didComplete(T result) {
+  void didComplete(T? result) {
     _popCompleter.complete(result ?? currentResult);
   }
 
@@ -1693,7 +1693,7 @@
   ///  * [restorablePushNamed], which pushes a route that can be restored
   ///    during state restoration.
   @optionalTypeArgs
-  static Future<T> pushNamed<T extends Object?>(
+  static Future<T?> pushNamed<T extends Object?>(
     BuildContext context,
     String routeName, {
     Object? arguments,
@@ -1810,7 +1810,7 @@
   ///  * [restorablePushReplacementNamed], which pushes a replacement route that
   ///    can be restored during state restoration.
   @optionalTypeArgs
-  static Future<T> pushReplacementNamed<T extends Object?, TO extends Object?>(
+  static Future<T?> pushReplacementNamed<T extends Object?, TO extends Object?>(
     BuildContext context,
     String routeName, {
     TO? result,
@@ -1904,7 +1904,7 @@
   ///  * [restorablePopAndPushNamed], which pushes a new route that can be
   ///    restored during state restoration.
   @optionalTypeArgs
-  static Future<T> popAndPushNamed<T extends Object?, TO extends Object?>(
+  static Future<T?> popAndPushNamed<T extends Object?, TO extends Object?>(
     BuildContext context,
     String routeName, {
     TO? result,
@@ -2008,7 +2008,7 @@
   ///  * [restorablePushNamedAndRemoveUntil], which pushes a new route that can
   ///    be restored during state restoration.
   @optionalTypeArgs
-  static Future<T> pushNamedAndRemoveUntil<T extends Object?>(
+  static Future<T?> pushNamedAndRemoveUntil<T extends Object?>(
     BuildContext context,
     String newRouteName,
     RoutePredicate predicate, {
@@ -2087,7 +2087,7 @@
   ///  * [restorablePush], which pushes a route that can be restored during
   ///    state restoration.
   @optionalTypeArgs
-  static Future<T> push<T extends Object?>(BuildContext context, Route<T> route) {
+  static Future<T?> push<T extends Object?>(BuildContext context, Route<T> route) {
     return Navigator.of(context)!.push(route);
   }
 
@@ -2190,7 +2190,7 @@
   ///  * [restorablePushReplacement], which pushes a replacement route that can
   ///    be restored during state restoration.
   @optionalTypeArgs
-  static Future<T> pushReplacement<T extends Object?, TO extends Object?>(BuildContext context, Route<T> newRoute, { TO? result }) {
+  static Future<T?> pushReplacement<T extends Object?, TO extends Object?>(BuildContext context, Route<T> newRoute, { TO? result }) {
     return Navigator.of(context)!.pushReplacement<T, TO>(newRoute, result: result);
   }
 
@@ -2295,7 +2295,7 @@
   ///  * [restorablePushAndRemoveUntil], which pushes a route that can be
   ///    restored during state restoration.
   @optionalTypeArgs
-  static Future<T> pushAndRemoveUntil<T extends Object?>(BuildContext context, Route<T> newRoute, RoutePredicate predicate) {
+  static Future<T?> pushAndRemoveUntil<T extends Object?>(BuildContext context, Route<T> newRoute, RoutePredicate predicate) {
     return Navigator.of(context)!.pushAndRemoveUntil<T>(newRoute, predicate);
   }
 
@@ -2876,7 +2876,7 @@
     // User-provided restoration ids of Pages are prefixed with 'p+'. Generated
     // ids for pageless routes are prefixed with 'r+' to avoid clashes.
     if (hasPage) {
-      final Page<Object> page = route.settings as Page<Object>;
+      final Page<Object?> page = route.settings as Page<Object?>;
       return page.restorationId != null ? 'p+${page.restorationId}' : null;
     }
     if (restorationInformation != null) {
@@ -4000,7 +4000,7 @@
   ///  * [restorablePushNamed], which pushes a route that can be restored
   ///    during state restoration.
   @optionalTypeArgs
-  Future<T> pushNamed<T extends Object?>(
+  Future<T?> pushNamed<T extends Object?>(
     String routeName, {
     Object? arguments,
   }) {
@@ -4067,7 +4067,7 @@
   ///  * [restorablePushReplacementNamed], which pushes a replacement route that
   ///  can be restored during state restoration.
   @optionalTypeArgs
-  Future<T> pushReplacementNamed<T extends Object?, TO extends Object?>(
+  Future<T?> pushReplacementNamed<T extends Object?, TO extends Object?>(
     String routeName, {
     TO? result,
     Object? arguments,
@@ -4137,7 +4137,7 @@
   ///  * [restorablePopAndPushNamed], which pushes a new route that can be
   ///    restored during state restoration.
   @optionalTypeArgs
-  Future<T> popAndPushNamed<T extends Object?, TO extends Object?>(
+  Future<T?> popAndPushNamed<T extends Object?, TO extends Object?>(
     String routeName, {
     TO? result,
     Object? arguments,
@@ -4200,7 +4200,7 @@
   ///  * [restorablePushNamedAndRemoveUntil], which pushes a new route that can
   ///    be restored during state restoration.
   @optionalTypeArgs
-  Future<T> pushNamedAndRemoveUntil<T extends Object?>(
+  Future<T?> pushNamedAndRemoveUntil<T extends Object?>(
     String newRouteName,
     RoutePredicate predicate, {
     Object? arguments,
@@ -4266,7 +4266,7 @@
   ///  * [restorablePush], which pushes a route that can be restored during
   ///    state restoration.
   @optionalTypeArgs
-  Future<T> push<T extends Object?>(Route<T> route) {
+  Future<T?> push<T extends Object?>(Route<T> route) {
     _pushEntry(_RouteEntry(route, initialState: _RouteLifecycle.push));
     return route.popped;
   }
@@ -4410,7 +4410,7 @@
   ///  * [restorablePushReplacement], which pushes a replacement route that can
   ///    be restored during state restoration.
   @optionalTypeArgs
-  Future<T> pushReplacement<T extends Object?, TO extends Object?>(Route<T> newRoute, { TO? result }) {
+  Future<T?> pushReplacement<T extends Object?, TO extends Object?>(Route<T> newRoute, { TO? result }) {
     assert(newRoute != null);
     assert(newRoute._navigator == null);
     _pushReplacementEntry(_RouteEntry(newRoute, initialState: _RouteLifecycle.pushReplace), result);
@@ -4516,7 +4516,7 @@
   ///  * [restorablePushAndRemoveUntil], which pushes a route that can be
   ///    restored during state restoration.
   @optionalTypeArgs
-  Future<T> pushAndRemoveUntil<T extends Object?>(Route<T> newRoute, RoutePredicate predicate) {
+  Future<T?> pushAndRemoveUntil<T extends Object?>(Route<T> newRoute, RoutePredicate predicate) {
     assert(newRoute != null);
     assert(newRoute._navigator == null);
     assert(newRoute.overlayEntries.isEmpty);
@@ -5116,9 +5116,9 @@
 
   factory _RestorationInformation.fromSerializableData(Object data) {
     assert(data != null);
-    final List<Object> casted = data as List<Object>;
+    final List<Object?> casted = data as List<Object?>;
     assert(casted.isNotEmpty);
-    final _RouteRestorationType type = _RouteRestorationType.values[casted[0] as int];
+    final _RouteRestorationType type = _RouteRestorationType.values[casted[0]! as int];
     switch (type) {
       case _RouteRestorationType.named:
         return _NamedRestorationInformation.fromSerializableData(casted.sublist(1));
@@ -5167,11 +5167,11 @@
     required this.restorationScopeId,
   }) : assert(name != null), super(_RouteRestorationType.named);
 
-  factory _NamedRestorationInformation.fromSerializableData(List<Object> data) {
+  factory _NamedRestorationInformation.fromSerializableData(List<Object?> data) {
     assert(data.length >= 2);
     return _NamedRestorationInformation(
-      restorationScopeId: data[0] as int,
-      name: data[1] as String,
+      restorationScopeId: data[0]! as int,
+      name: data[1]! as String,
       arguments: data.length > 2 ? data[2] : null,
     );
   }
@@ -5206,11 +5206,11 @@
     required this.restorationScopeId,
   }) : assert(routeBuilder != null), super(_RouteRestorationType.anonymous);
 
-  factory _AnonymousRestorationInformation.fromSerializableData(List<Object> data) {
+  factory _AnonymousRestorationInformation.fromSerializableData(List<Object?> data) {
     assert(data.length > 1);
-    final RestorableRouteBuilder routeBuilder = ui.PluginUtilities.getCallbackFromHandle(ui.CallbackHandle.fromRawHandle(data[1] as int))! as RestorableRouteBuilder;
+    final RestorableRouteBuilder routeBuilder = ui.PluginUtilities.getCallbackFromHandle(ui.CallbackHandle.fromRawHandle(data[1]! as int))! as RestorableRouteBuilder;
     return _AnonymousRestorationInformation(
-      restorationScopeId: data[0] as int,
+      restorationScopeId: data[0]! as int,
       routeBuilder: routeBuilder,
       arguments: data.length > 2 ? data[2] : null,
     );
@@ -5383,10 +5383,10 @@
   }
 
   @override
-  Map<String?, List<Object>>? fromPrimitives(Object data) {
-    final Map<dynamic, dynamic> casted = data as Map<dynamic, dynamic>;
-    return casted.map<String, List<Object>>((dynamic key, dynamic value) => MapEntry<String, List<Object>>(
-      key as String,
+  Map<String?, List<Object>>? fromPrimitives(Object? data) {
+    final Map<dynamic, dynamic> casted = data! as Map<dynamic, dynamic>;
+    return casted.map<String?, List<Object>>((dynamic key, dynamic value) => MapEntry<String?, List<Object>>(
+      key as String?,
       List<Object>.from(value as List<dynamic>, growable: true),
     ));
   }
@@ -5694,9 +5694,9 @@
   }
 
   @override
-  String fromPrimitives(Object data) {
+  String fromPrimitives(Object? data) {
     assert(data != null);
-    return data as String;
+    return data! as String;
   }
 
   bool _disposed = false;
diff --git a/packages/flutter/lib/src/widgets/restoration.dart b/packages/flutter/lib/src/widgets/restoration.dart
index e83c6ed..9fc259f 100644
--- a/packages/flutter/lib/src/widgets/restoration.dart
+++ b/packages/flutter/lib/src/widgets/restoration.dart
@@ -425,7 +425,7 @@
   /// [RestorableProperty]. Whenever new restoration data has been provided to
   /// the [RestorationMixin] the property is registered to, either this method
   /// or [createDefaultValue] is called before [initWithValue] is invoked.
-  T fromPrimitives(Object data);
+  T fromPrimitives(Object? data);
 
   /// Called by the [RestorationMixin] with the `value` returned by either
   /// [createDefaultValue] or [fromPrimitives] to set the value that this
@@ -884,7 +884,7 @@
   /// restore the internal state of a [State] object, it may be removed from the
   /// restoration data by calling this method.
   @protected
-  void unregisterFromRestoration(RestorableProperty<Object> property) {
+  void unregisterFromRestoration(RestorableProperty<Object?> property) {
     assert(property != null);
     assert(property._owner == this);
     _bucket?.remove<Object?>(property._restorationId!);
diff --git a/packages/flutter/lib/src/widgets/restoration_properties.dart b/packages/flutter/lib/src/widgets/restoration_properties.dart
index 430e6cb..3a7221e 100644
--- a/packages/flutter/lib/src/widgets/restoration_properties.dart
+++ b/packages/flutter/lib/src/widgets/restoration_properties.dart
@@ -173,9 +173,9 @@
   }
 
   @override
-  T fromPrimitives(Object serialized) {
+  T fromPrimitives(Object? serialized) {
     assert(serialized != null);
-    return serialized as T;
+    return serialized! as T;
   }
 
   @override
@@ -360,8 +360,8 @@
   }
 
   @override
-  TextEditingController fromPrimitives(Object data) {
-    return TextEditingController(text: data as String);
+  TextEditingController fromPrimitives(Object? data) {
+    return TextEditingController(text: data! as String);
   }
 
   @override
diff --git a/packages/flutter/lib/src/widgets/routes.dart b/packages/flutter/lib/src/widgets/routes.dart
index a91d8d3..cf136f8 100644
--- a/packages/flutter/lib/src/widgets/routes.dart
+++ b/packages/flutter/lib/src/widgets/routes.dart
@@ -62,7 +62,7 @@
   bool get finishedWhenPopped => true;
 
   @override
-  bool didPop(T result) {
+  bool didPop(T? result) {
     final bool returnValue = super.didPop(result);
     assert(returnValue);
     if (finishedWhenPopped)
@@ -90,8 +90,8 @@
   /// This future completes once the animation has been dismissed. That will be
   /// after [popped], because [popped] typically completes before the animation
   /// even starts, as soon as the route is popped.
-  Future<T> get completed => _transitionCompleter.future;
-  final Completer<T> _transitionCompleter = Completer<T>();
+  Future<T?> get completed => _transitionCompleter.future;
+  final Completer<T?> _transitionCompleter = Completer<T?>();
 
   /// {@template flutter.widgets.transitionRoute.transitionDuration}
   /// The duration the transition going forwards.
@@ -235,7 +235,7 @@
   }
 
   @override
-  bool didPop(T result) {
+  bool didPop(T? result) {
     assert(_controller != null, '$runtimeType.didPop called before calling install() or after calling dispose().');
     assert(!_transitionCompleter.isCompleted, 'Cannot reuse a $runtimeType after disposing it.');
     _result = result;
@@ -631,7 +631,7 @@
   }
 
   @override
-  bool didPop(T result) {
+  bool didPop(T? result) {
     if (_localHistory != null && _localHistory!.isNotEmpty) {
       final LocalHistoryEntry entry = _localHistory!.removeLast();
       assert(entry._owner == this);
@@ -1818,7 +1818,7 @@
 ///
 ///  * [showDialog], which displays a Material-style dialog.
 ///  * [showCupertinoDialog], which displays an iOS-style dialog.
-Future<T> showGeneralDialog<T extends Object?>({
+Future<T?> showGeneralDialog<T extends Object?>({
   required BuildContext context,
   required RoutePageBuilder pageBuilder,
   bool barrierDismissible = false,
diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart
index d6ffbc3..8b3d819 100644
--- a/packages/flutter/lib/src/widgets/scrollable.dart
+++ b/packages/flutter/lib/src/widgets/scrollable.dart
@@ -1084,8 +1084,8 @@
   }
 
   @override
-  double fromPrimitives(Object data) {
-    return data as double;
+  double fromPrimitives(Object? data) {
+    return data! as double;
   }
 
   @override
diff --git a/packages/flutter/lib/src/widgets/shortcuts.dart b/packages/flutter/lib/src/widgets/shortcuts.dart
index c0a1ee5..8754301 100644
--- a/packages/flutter/lib/src/widgets/shortcuts.dart
+++ b/packages/flutter/lib/src/widgets/shortcuts.dart
@@ -264,9 +264,13 @@
   /// True if the [ShortcutManager] should not pass on keys that it doesn't
   /// handle to any key-handling widgets that are ancestors to this one.
   ///
-  /// Setting [modal] to true is the equivalent of always handling any key given
-  /// to it, even if that key doesn't appear in the [shortcuts] map. Keys that
-  /// don't appear in the map will be dropped.
+  /// Setting [modal] to true will prevent any key event given to this manager
+  /// from being given to any ancestor managers, even if that key doesn't appear
+  /// in the [shortcuts] map.
+  ///
+  /// The net effect of setting `modal` to true is to return
+  /// [KeyEventResult.skipRemainingHandlers] from [handleKeypress] if it does not
+  /// exist in the shortcut map, instead of returning [KeyEventResult.ignored].
   final bool modal;
 
   /// Returns the shortcut map.
@@ -284,68 +288,91 @@
     }
   }
 
-  /// Handles a key pressed `event` in the given `context`.
+  /// Returns the [Intent], if any, that matches the current set of pressed
+  /// keys.
   ///
-  /// The optional `keysPressed` argument provides an override to keys that the
-  /// [RawKeyboard] reports. If not specified, uses [RawKeyboard.keysPressed]
-  /// instead.
+  /// Returns null if no intent matches the current set of pressed keys.
   ///
-  /// If a key mapping is found, then the associated action will be invoked
-  /// using the [Intent] that the [LogicalKeySet] maps to, and the currently
-  /// focused widget's context (from [FocusManager.primaryFocus]).
-  ///
-  /// The object returned is the result of [Action.invoke] being called on the
-  /// [Action] bound to the [Intent] that the key press maps to, or null, if the
-  /// key press didn't match any intent.
-  @protected
-  bool handleKeypress(
-    BuildContext context,
-    RawKeyEvent event, {
-    LogicalKeySet? keysPressed,
-  }) {
-    if (event is! RawKeyDownEvent) {
-      return false;
+  /// Defaults to a set derived from [RawKeyboard.keysPressed] if `keysPressed` is
+  /// not supplied.
+  Intent? _find({ LogicalKeySet? keysPressed }) {
+    if (keysPressed == null && RawKeyboard.instance.keysPressed.isEmpty) {
+      return null;
     }
-    assert(context != null);
-    LogicalKeySet? keySet = keysPressed;
-    if (keySet == null) {
-      assert(RawKeyboard.instance.keysPressed.isNotEmpty,
-        'Received a key down event when no keys are in keysPressed. '
-        "This state can occur if the key event being sent doesn't properly "
-        'set its modifier flags. This was the event: $event and its data: '
-        '${event.data}');
-      // Avoid the crash in release mode, since it's easy to miss a particular
-      // bad key sequence in testing, and so shouldn't crash the app in release.
-      if (RawKeyboard.instance.keysPressed.isNotEmpty) {
-        keySet = LogicalKeySet.fromSet(RawKeyboard.instance.keysPressed);
-      } else {
-        return false;
-      }
-    }
-    Intent? matchedIntent = _shortcuts[keySet];
+    keysPressed ??= LogicalKeySet.fromSet(RawKeyboard.instance.keysPressed);
+    Intent? matchedIntent = _shortcuts[keysPressed];
     if (matchedIntent == null) {
       // If there's not a more specific match, We also look for any keys that
       // have synonyms in the map.  This is for things like left and right shift
       // keys mapping to just the "shift" pseudo-key.
       final Set<LogicalKeyboardKey> pseudoKeys = <LogicalKeyboardKey>{};
-      for (final LogicalKeyboardKey setKey in keySet.keys) {
-        final Set<LogicalKeyboardKey> synonyms = setKey.synonyms;
-        if (synonyms.isNotEmpty) {
-          // There currently aren't any synonyms that match more than one key.
-          pseudoKeys.add(synonyms.first);
-        } else {
-          pseudoKeys.add(setKey);
+      for (final KeyboardKey setKey in keysPressed.keys) {
+        if (setKey is LogicalKeyboardKey) {
+          final Set<LogicalKeyboardKey> synonyms = setKey.synonyms;
+          if (synonyms.isNotEmpty) {
+            // There currently aren't any synonyms that match more than one key.
+            assert(synonyms.length == 1, 'Unexpectedly encountered a key synonym with more than one key.');
+            pseudoKeys.add(synonyms.first);
+          } else {
+            pseudoKeys.add(setKey);
+          }
         }
       }
       matchedIntent = _shortcuts[LogicalKeySet.fromSet(pseudoKeys)];
     }
-    if (matchedIntent != null) {
-      final BuildContext? primaryContext = primaryFocus?.context;
-      assert (primaryContext != null);
-      Actions.invoke(primaryContext!, matchedIntent, nullOk: true);
-      return true;
+    return matchedIntent;
+  }
+
+  /// Handles a key press `event` in the given `context`.
+  ///
+  /// The optional `keysPressed` argument is used as the set of currently
+  /// pressed keys. Defaults to a set derived from [RawKeyboard.keysPressed] if
+  /// `keysPressed` is not supplied.
+  ///
+  /// If a key mapping is found, then the associated action will be invoked
+  /// using the [Intent] that the `keysPressed` maps to, and the currently
+  /// focused widget's context (from [FocusManager.primaryFocus]).
+  ///
+  /// Returns a [KeyEventResult.handled] if an action was invoked, otherwise a
+  /// [KeyEventResult.skipRemainingHandlers] if [modal] is true, or if it maps to a
+  /// [DoNothingAction] with [DoNothingAction.consumesKey] set to false, and
+  /// in all other cases returns [KeyEventResult.ignored].
+  ///
+  /// In order for an action to be invoked (and [KeyEventResult.handled]
+  /// returned), a pressed [KeySet] must be mapped to an [Intent], the [Intent]
+  /// must be mapped to an [Action], and the [Action] must be enabled.
+  @protected
+  KeyEventResult handleKeypress(
+    BuildContext context,
+    RawKeyEvent event, {
+    LogicalKeySet? keysPressed,
+  }) {
+    if (event is! RawKeyDownEvent) {
+      return KeyEventResult.ignored;
     }
-    return false;
+    assert(context != null);
+    assert(keysPressed != null || RawKeyboard.instance.keysPressed.isNotEmpty,
+      'Received a key down event when no keys are in keysPressed. '
+      "This state can occur if the key event being sent doesn't properly "
+      'set its modifier flags. This was the event: $event and its data: '
+      '${event.data}');
+    final Intent? matchedIntent = _find(keysPressed: keysPressed);
+    if (matchedIntent != null) {
+      final BuildContext primaryContext = primaryFocus!.context!;
+      assert (primaryContext != null);
+      final Action<Intent>? action = Actions.find<Intent>(
+        primaryContext,
+        intent: matchedIntent,
+        nullOk: true,
+      );
+      if (action != null && action.isEnabled(matchedIntent)) {
+        Actions.of(primaryContext).invokeAction(action, matchedIntent, primaryContext);
+        return action.consumesKey(matchedIntent)
+            ? KeyEventResult.handled
+            : KeyEventResult.skipRemainingHandlers;
+      }
+    }
+    return modal ? KeyEventResult.skipRemainingHandlers : KeyEventResult.ignored;
   }
 
   @override
@@ -433,7 +460,7 @@
       }
       return true;
     }());
-    return inherited?.notifier;
+    return inherited?.manager;
   }
 
   @override
@@ -480,11 +507,11 @@
     manager.shortcuts = widget.shortcuts;
   }
 
-  bool _handleOnKey(FocusNode node, RawKeyEvent event) {
+  KeyEventResult _handleOnKey(FocusNode node, RawKeyEvent event) {
     if (node.context == null) {
-      return false;
+      return KeyEventResult.ignored;
     }
-    return manager.handleKeypress(node.context!, event) || manager.modal;
+    return manager.handleKeypress(node.context!, event);
   }
 
   @override
@@ -508,4 +535,6 @@
   })  : assert(manager != null),
         assert(child != null),
         super(notifier: manager, child: child);
+
+  ShortcutManager get manager => super.notifier!;
 }
diff --git a/packages/flutter/lib/src/widgets/table.dart b/packages/flutter/lib/src/widgets/table.dart
index d3e42af..ec60a50 100644
--- a/packages/flutter/lib/src/widgets/table.dart
+++ b/packages/flutter/lib/src/widgets/table.dart
@@ -303,16 +303,16 @@
   }
 
   @override
-  void insertRenderObjectChild(RenderObject child, IndexedSlot<Element>? slot) {
+  void insertRenderObjectChild(RenderObject child, dynamic slot) {
     renderObject.setupParentData(child);
   }
 
   @override
-  void moveRenderObjectChild(RenderObject child, IndexedSlot<Element>? oldSlot, IndexedSlot<Element>? newSlot) {
+  void moveRenderObjectChild(RenderObject child, dynamic oldSlot, dynamic newSlot) {
   }
 
   @override
-  void removeRenderObjectChild(RenderObject child, IndexedSlot<Element>? slot) {
+  void removeRenderObjectChild(RenderObject child, dynamic slot) {
     final TableCellParentData childParentData = child.parentData! as TableCellParentData;
     renderObject.setChild(childParentData.x!, childParentData.y!, null);
   }
diff --git a/packages/flutter/lib/src/widgets/widget_inspector.dart b/packages/flutter/lib/src/widgets/widget_inspector.dart
index b7a96a5..194a88e 100644
--- a/packages/flutter/lib/src/widgets/widget_inspector.dart
+++ b/packages/flutter/lib/src/widgets/widget_inspector.dart
@@ -880,17 +880,20 @@
     registerServiceExtension(
       name: name,
       callback: (Map<String, String> parameters) async {
-        const String argPrefix = 'arg';
         final List<String> args = <String>[];
-        parameters.forEach((String name, String value) {
-          if (name.startsWith(argPrefix)) {
-            final int index = int.parse(name.substring(argPrefix.length));
-            if (index >= args.length) {
-              args.length = index + 1;
-            }
-            args[index] = value;
+        int index = 0;
+        while (true) {
+          final String name = 'arg$index';
+          if (parameters.containsKey(name)) {
+            args.add(parameters[name]!);
+          } else {
+            break;
           }
-        });
+          index++;
+        }
+        // Verify that the only arguments other than perhaps 'isolateId' are
+        // arguments we have already handled.
+        assert(index == parameters.length || (index == parameters.length - 1 && parameters.containsKey('isolateId')));
         return <String, Object?>{'result': await callback(args)};
       },
     );
diff --git a/packages/flutter/test/animation/animation_controller_test.dart b/packages/flutter/test/animation/animation_controller_test.dart
index e634487..29bdbec 100644
--- a/packages/flutter/test/animation/animation_controller_test.dart
+++ b/packages/flutter/test/animation/animation_controller_test.dart
@@ -253,6 +253,29 @@
     largeRangeController.stop();
   });
 
+  test('Custom springDescription can be applied', () {
+    final AnimationController controller = AnimationController(
+      vsync: const TestVSync(),
+    );
+    final AnimationController customSpringController = AnimationController(
+      vsync: const TestVSync(),
+    );
+
+    controller.fling();
+    // Will produce longer and smoother animation than the default.
+    customSpringController.fling(
+      springDescription: SpringDescription.withDampingRatio(
+        mass: 0.01,
+        stiffness: 10.0,
+        ratio: 2.0,
+      ),
+    );
+    tick(const Duration(milliseconds: 0));
+    tick(const Duration(milliseconds: 50));
+
+    expect(customSpringController.value < controller.value, true);
+  });
+
   test('lastElapsedDuration control test', () {
     final AnimationController controller = AnimationController(
       duration: const Duration(milliseconds: 100),
diff --git a/packages/flutter/test/cupertino/nav_bar_transition_test.dart b/packages/flutter/test/cupertino/nav_bar_transition_test.dart
index f7f46b2..dfa955d 100644
--- a/packages/flutter/test/cupertino/nav_bar_transition_test.dart
+++ b/packages/flutter/test/cupertino/nav_bar_transition_test.dart
@@ -135,11 +135,11 @@
     // place.
     expect(
       tester.getTopLeft(flying(tester, find.text('Page 1')).first),
-      const Offset(337.0234375, 13.5),
+      const Offset(337.1953125, 13.5),
     );
     expect(
       tester.getTopLeft(flying(tester, find.text('Page 1')).last),
-      const Offset(337.0234375, 13.5),
+      const Offset(337.1953125, 13.5),
     );
   });
 
@@ -156,11 +156,11 @@
     // Same as LTR but more to the right now.
     expect(
       tester.getTopLeft(flying(tester, find.text('Page 1')).first),
-      const Offset(362.9765625, 13.5),
+      const Offset(362.8046875, 13.5),
     );
     expect(
       tester.getTopLeft(flying(tester, find.text('Page 1')).last),
-      const Offset(362.9765625, 13.5),
+      const Offset(362.8046875, 13.5),
     );
   });
 
@@ -329,7 +329,7 @@
       expect(bottomMiddle.text.style!.color, const Color(0xff00050a));
       expect(
         tester.getTopLeft(flying(tester, find.text('Page 1')).first),
-        const Offset(337.0234375, 13.5),
+        const Offset(337.1953125, 13.5),
       );
 
       // The top back label is styled exactly the same way. But the opacity tweens
@@ -339,7 +339,7 @@
       expect(topBackLabel.text.style!.color, const Color(0xff00050a));
       expect(
         tester.getTopLeft(flying(tester, find.text('Page 1')).last),
-        const Offset(337.0234375, 13.5),
+        const Offset(337.1953125, 13.5),
       );
     }
 
@@ -374,7 +374,7 @@
       expect(bottomMiddle.text.style!.color, const Color(0xff00050a));
       expect(
         tester.getTopLeft(flying(tester, find.text('Page 1')).first),
-        const Offset(362.9765625, 13.5),
+        const Offset(362.8046875, 13.5),
       );
 
       // The top back label is styled exactly the same way. But the opacity tweens
@@ -384,7 +384,7 @@
       expect(topBackLabel.text.style!.color, const Color(0xff00050a));
       expect(
         tester.getTopLeft(flying(tester, find.text('Page 1')).last),
-        const Offset(362.9765625, 13.5),
+        const Offset(362.8046875, 13.5),
       );
     }
 
@@ -599,12 +599,12 @@
     // Come in from the right and fade in.
     checkOpacity(tester, backChevron, 0.0);
     expect(
-        tester.getTopLeft(backChevron), const Offset(73.078125, 5.0));
+        tester.getTopLeft(backChevron), const Offset(86.734375, 7.0));
 
     await tester.pump(const Duration(milliseconds: 150));
     checkOpacity(tester, backChevron, 0.09497911669313908);
     expect(
-        tester.getTopLeft(backChevron), const Offset(23.260527312755585, 5.0));
+        tester.getTopLeft(backChevron), const Offset(31.055883467197418, 7.0));
   });
 
   testWidgets('First appearance of back chevron fades in from the left in RTL', (WidgetTester tester) async {
@@ -643,14 +643,14 @@
     checkOpacity(tester, backChevron, 0.0);
     expect(
       tester.getTopRight(backChevron),
-      const Offset(692.921875, 5.0),
+      const Offset(687.265625, 7.0),
     );
 
     await tester.pump(const Duration(milliseconds: 150));
     checkOpacity(tester, backChevron, 0.09497911669313908);
     expect(
       tester.getTopRight(backChevron),
-      const Offset(742.7394726872444, 5.0),
+      const Offset(742.9441165328026, 7.0),
     );
   });
 
@@ -670,15 +670,15 @@
     checkOpacity(tester, backChevrons.first, 0.8833301812410355);
     checkOpacity(tester, backChevrons.last, 0.0);
     // Both overlap at the same place.
-    expect(tester.getTopLeft(backChevrons.first), const Offset(8.0, 5.0));
-    expect(tester.getTopLeft(backChevrons.last), const Offset(8.0, 5.0));
+    expect(tester.getTopLeft(backChevrons.first), const Offset(14.0, 7.0));
+    expect(tester.getTopLeft(backChevrons.last), const Offset(14.0, 7.0));
 
     await tester.pump(const Duration(milliseconds: 150));
     checkOpacity(tester, backChevrons.first, 0.0);
     checkOpacity(tester, backChevrons.last, 0.4604858811944723);
     // Still in the same place.
-    expect(tester.getTopLeft(backChevrons.first), const Offset(8.0, 5.0));
-    expect(tester.getTopLeft(backChevrons.last), const Offset(8.0, 5.0));
+    expect(tester.getTopLeft(backChevrons.first), const Offset(14.0, 7.0));
+    expect(tester.getTopLeft(backChevrons.last), const Offset(14.0, 7.0));
   });
 
   testWidgets('Bottom middle just fades if top page has a custom leading', (WidgetTester tester) async {
@@ -789,14 +789,14 @@
     checkOpacity(tester, flying(tester, find.text('Page 1')), 0.6697911769151688);
     expect(
       tester.getTopLeft(flying(tester, find.text('Page 1'))),
-      const Offset(30.8125, 13.5),
+      const Offset(34.8125, 13.5),
     );
 
     await tester.pump(const Duration(milliseconds: 150));
     checkOpacity(tester, flying(tester, find.text('Page 1')), 0.0);
     expect(
       tester.getTopLeft(flying(tester, find.text('Page 1'))),
-      const Offset(-262.2321922779083, 13.5),
+      const Offset(-258.2321922779083, 13.5),
     );
   });
 
@@ -826,7 +826,7 @@
     checkOpacity(tester, flying(tester, find.text('Page 1')), 0.6697911769151688);
     expect(
       tester.getTopRight(flying(tester, find.text('Page 1'))),
-      const Offset(769.1875, 13.5),
+      const Offset(765.1875, 13.5),
     );
 
     await tester.pump(const Duration(milliseconds: 150));
@@ -834,7 +834,7 @@
     expect(
       tester.getTopRight(flying(tester, find.text('Page 1'))),
       // >1000. It's now off the screen.
-      const Offset(1062.2321922779083, 13.5),
+      const Offset(1058.2321922779083, 13.5),
     );
   });
 
@@ -856,11 +856,11 @@
     checkOpacity(tester, flying(tester, find.text('Page 1')).last, 0.0);
     expect(
       tester.getTopLeft(flying(tester, find.text('Page 1')).first),
-      const Offset(17.375, 52.39453125),
+      const Offset(17.546875, 52.39453125),
     );
     expect(
       tester.getTopLeft(flying(tester, find.text('Page 1')).last),
-      const Offset(17.375, 52.39453125),
+      const Offset(17.546875, 52.39453125),
     );
 
     await tester.pump(const Duration(milliseconds: 150));
@@ -868,11 +868,11 @@
     checkOpacity(tester, flying(tester, find.text('Page 1')).last, 0.4604858811944723);
     expect(
       tester.getTopLeft(flying(tester, find.text('Page 1')).first),
-      const Offset(40.818575382232666, 22.49655644595623),
+      const Offset(43.92089730501175, 22.49655644595623),
     );
     expect(
       tester.getTopLeft(flying(tester, find.text('Page 1')).last),
-      const Offset(40.818575382232666, 22.49655644595623),
+      const Offset(43.92089730501175, 22.49655644595623),
     );
   });
 
@@ -896,11 +896,11 @@
     checkOpacity(tester, flying(tester, find.text('Back')), 0.0);
     expect(
       tester.getTopLeft(flying(tester, find.text('A title too long to fit'))),
-      const Offset(17.375, 52.39453125),
+      const Offset(17.546875, 52.39453125),
     );
     expect(
       tester.getTopLeft(flying(tester, find.text('Back'))),
-      const Offset(17.375, 52.39453125),
+      const Offset(17.546875, 52.39453125),
     );
 
     await tester.pump(const Duration(milliseconds: 150));
@@ -908,11 +908,11 @@
     checkOpacity(tester, flying(tester, find.text('Back')), 0.4604858811944723);
     expect(
       tester.getTopLeft(flying(tester, find.text('A title too long to fit'))),
-      const Offset(40.818575382232666, 22.49655644595623),
+      const Offset(43.92089730501175, 22.49655644595623),
     );
     expect(
       tester.getTopLeft(flying(tester, find.text('Back'))),
-      const Offset(40.818575382232666, 22.49655644595623),
+      const Offset(43.92089730501175, 22.49655644595623),
     );
   });
 
diff --git a/packages/flutter/test/cupertino/route_test.dart b/packages/flutter/test/cupertino/route_test.dart
index bacea8f..0a9a8bd 100644
--- a/packages/flutter/test/cupertino/route_test.dart
+++ b/packages/flutter/test/cupertino/route_test.dart
@@ -154,8 +154,8 @@
 
     // Also shows the previous page's title next to the back button.
     expect(find.widgetWithText(CupertinoButton, 'An iPod'), findsOneWidget);
-    // 2 paddings + 1 ahem character at font size 34.0.
-    expect(tester.getTopLeft(find.text('An iPod')).dx, 8.0 + 34.0 + 6.0);
+    // 3 paddings + 1 ahem character at font size 34.0.
+    expect(tester.getTopLeft(find.text('An iPod')).dx, 8.0 + 4.0 + 34.0 + 6.0);
   });
 
   testWidgets('Previous title is correct on first transition frame', (WidgetTester tester) async {
@@ -261,7 +261,7 @@
     // from An iPod to Back (since An Internet communicator is too long to
     // fit in the back button).
     expect(find.widgetWithText(CupertinoButton, 'Back'), findsOneWidget);
-    expect(tester.getTopLeft(find.text('Back')).dx, 8.0 + 34.0 + 6.0);
+    expect(tester.getTopLeft(find.text('Back')).dx, 8.0 + 4.0 + 34.0 + 6.0);
   });
 
   testWidgets('Back swipe dismiss interrupted by route push', (WidgetTester tester) async {
diff --git a/packages/flutter/test/cupertino/slider_test.dart b/packages/flutter/test/cupertino/slider_test.dart
index 15a9623..2afed42 100644
--- a/packages/flutter/test/cupertino/slider_test.dart
+++ b/packages/flutter/test/cupertino/slider_test.dart
@@ -318,6 +318,7 @@
             increasedValue: '60%',
             decreasedValue: '40%',
             textDirection: TextDirection.ltr,
+            flags: <SemanticsFlag>[SemanticsFlag.isSlider],
             actions: SemanticsAction.decrease.index | SemanticsAction.increase.index,
           ),
         ],
@@ -341,7 +342,14 @@
     );
 
     expect(semantics, hasSemantics(
-      TestSemantics.root(),
+      TestSemantics.root(
+        children: <TestSemantics>[
+          TestSemantics(
+            id: 1,
+            flags: <SemanticsFlag>[SemanticsFlag.isSlider],
+          )
+        ],
+      ),
       ignoreRect: true,
       ignoreTransform: true,
     ));
@@ -365,6 +373,7 @@
     );
 
     expect(tester.getSemantics(find.byType(CupertinoSlider)), matchesSemantics(
+      isSlider: true,
       hasIncreaseAction: true,
       hasDecreaseAction: true,
       value: '50%',
@@ -387,6 +396,7 @@
     );
 
     expect(tester.getSemantics(find.byType(CupertinoSlider)), matchesSemantics(
+      isSlider: true,
       hasIncreaseAction: true,
       hasDecreaseAction: true,
       value: '60%',
diff --git a/packages/flutter/test/foundation/consolidate_response_test.dart b/packages/flutter/test/foundation/consolidate_response_test.dart
index b4775f9..b628813 100644
--- a/packages/flutter/test/foundation/consolidate_response_test.dart
+++ b/packages/flutter/test/foundation/consolidate_response_test.dart
@@ -111,7 +111,7 @@
 
       test('Notifies onBytesReceived with gzipped numbers', () async {
         response.contentLength = gzipped.length;
-        final List<int?> records = <int>[];
+        final List<int?> records = <int?>[];
         await consolidateHttpClientResponseBytes(
           response,
           onBytesReceived: (int cumulative, int? total) {
diff --git a/packages/flutter/test/foundation/diagnostics_json_test.dart b/packages/flutter/test/foundation/diagnostics_json_test.dart
index 77ad289..facc1c1 100644
--- a/packages/flutter/test/foundation/diagnostics_json_test.dart
+++ b/packages/flutter/test/foundation/diagnostics_json_test.dart
@@ -76,7 +76,7 @@
     test('subtreeDepth 1', () {
       final Map<String, Object?> result = testTree.toDiagnosticsNode().toJsonMap(const DiagnosticsSerializationDelegate(subtreeDepth: 1));
       expect(result.containsKey('properties'), isFalse);
-      final List<Map<String, Object>> children = result['children']! as List<Map<String, Object>>;
+      final List<Map<String, Object?>> children = result['children']! as List<Map<String, Object?>>;
       expect(children[0].containsKey('children'), isFalse);
       expect(children[1].containsKey('children'), isFalse);
       expect(children[2].containsKey('children'), isFalse);
@@ -85,7 +85,7 @@
     test('subtreeDepth 5', () {
       final Map<String, Object?> result = testTree.toDiagnosticsNode().toJsonMap(const DiagnosticsSerializationDelegate(subtreeDepth: 5));
       expect(result.containsKey('properties'), isFalse);
-      final List<Map<String, Object>> children = result['children']! as List<Map<String, Object>>;
+      final List<Map<String, Object?>> children = result['children']! as List<Map<String, Object?>>;
       expect(children[0]['children'], hasLength(0));
       expect(children[1]['children'], hasLength(3));
       expect(children[2]['children'], hasLength(0));
@@ -103,7 +103,7 @@
         subtreeDepth: 1,
       ));
       expect(result['properties'], hasLength(7));
-      final List<Map<String, Object>> children = result['children']! as List<Map<String, Object>>;
+      final List<Map<String, Object?>> children = result['children']! as List<Map<String, Object?>>;
       expect(children, hasLength(3));
       expect(children[0]['properties'], hasLength(0));
       expect(children[1]['properties'], hasLength(2));
@@ -119,13 +119,13 @@
         },
       ));
       expect(result['foo'], isTrue);
-      final List<Map<String, Object>> properties = result['properties']! as List<Map<String, Object>>;
+      final List<Map<String, Object?>> properties = result['properties']! as List<Map<String, Object?>>;
       expect(properties, hasLength(7));
-      expect(properties.every((Map<String, Object> property) => property['foo'] == true), isTrue);
+      expect(properties.every((Map<String, Object?> property) => property['foo'] == true), isTrue);
 
-      final List<Map<String, Object>> children = result['children']! as List<Map<String, Object>>;
+      final List<Map<String, Object?>> children = result['children']! as List<Map<String, Object?>>;
       expect(children, hasLength(3));
-      expect(children.every((Map<String, Object> child) => child['foo'] == true), isTrue);
+      expect(children.every((Map<String, Object?> child) => child['foo'] == true), isTrue);
     });
 
     test('filterProperties - sublist', () {
@@ -135,9 +135,9 @@
             return nodes.whereType<StringProperty>().toList();
           },
       ));
-      final List<Map<String, Object>> properties = result['properties']! as List<Map<String, Object>>;
+      final List<Map<String, Object?>> properties = result['properties']! as List<Map<String, Object?>>;
       expect(properties, hasLength(3));
-      expect(properties.every((Map<String, Object> property) => property['type'] == 'StringProperty'), isTrue);
+      expect(properties.every((Map<String, Object?> property) => property['type'] == 'StringProperty'), isTrue);
     });
 
     test('filterProperties - replace', () {
@@ -154,7 +154,7 @@
             ];
           },
       ));
-      final List<Map<String, Object>> properties = result['properties']! as List<Map<String, Object>>;
+      final List<Map<String, Object?>> properties = result['properties']! as List<Map<String, Object?>>;
       expect(properties, hasLength(1));
       expect(properties.single['name'], 'foo');
     });
@@ -166,7 +166,7 @@
             return nodes.where((DiagnosticsNode node) => node.getProperties().isEmpty).toList();
           },
       ));
-      final List<Map<String, Object>> children = result['children']! as List<Map<String, Object>>;
+      final List<Map<String, Object?>> children = result['children']! as List<Map<String, Object?>>;
       expect(children, hasLength(1));
     });
 
@@ -177,7 +177,7 @@
             return nodes.expand((DiagnosticsNode node) => node.getChildren()).toList();
           },
       ));
-      final List<Map<String, Object>> children = result['children']! as List<Map<String, Object>>;
+      final List<Map<String, Object?>> children = result['children']! as List<Map<String, Object?>>;
       expect(children, hasLength(3));
       expect(children.first['name'], 'child node B1');
     });
@@ -190,11 +190,11 @@
             return nodes.take(2).toList();
           },
       ));
-      final List<Map<String, Object>> children = result['children']! as List<Map<String, Object>>;
+      final List<Map<String, Object?>> children = result['children']! as List<Map<String, Object?>>;
       expect(children, hasLength(3));
       expect(children.last['truncated'], isTrue);
 
-      final List<Map<String, Object>> properties = result['properties']! as List<Map<String, Object>>;
+      final List<Map<String, Object?>> properties = result['properties']! as List<Map<String, Object?>>;
       expect(properties, hasLength(3));
       expect(properties.last['truncated'], isTrue);
     });
@@ -207,13 +207,13 @@
             return delegate.copyWith(includeProperties: false);
           },
       ));
-      final List<Map<String, Object>> properties = result['properties']! as List<Map<String, Object>>;
+      final List<Map<String, Object?>> properties = result['properties']! as List<Map<String, Object?>>;
       expect(properties, hasLength(7));
-      expect(properties.every((Map<String, Object> property) => !property.containsKey('properties')), isTrue);
+      expect(properties.every((Map<String, Object?> property) => !property.containsKey('properties')), isTrue);
 
-      final List<Map<String, Object>> children = result['children']! as List<Map<String, Object>>;
+      final List<Map<String, Object?>> children = result['children']! as List<Map<String, Object?>>;
       expect(children, hasLength(3));
-      expect(children.every((Map<String, Object> child) => !child.containsKey('properties')), isTrue);
+      expect(children.every((Map<String, Object?> child) => !child.containsKey('properties')), isTrue);
     });
   });
 }
diff --git a/packages/flutter/test/foundation/diagnostics_test.dart b/packages/flutter/test/foundation/diagnostics_test.dart
index fea0b46..a6cef4d 100644
--- a/packages/flutter/test/foundation/diagnostics_test.dart
+++ b/packages/flutter/test/foundation/diagnostics_test.dart
@@ -47,15 +47,15 @@
 
 /// Encode and decode to JSON to make sure all objects in the JSON for the
 /// [DiagnosticsNode] are valid JSON.
-Map<String, Object> simulateJsonSerialization(DiagnosticsNode node) {
-  return json.decode(json.encode(node.toJsonMap(const DiagnosticsSerializationDelegate()))) as Map<String, Object>;
+Map<String, Object?> simulateJsonSerialization(DiagnosticsNode node) {
+  return json.decode(json.encode(node.toJsonMap(const DiagnosticsSerializationDelegate()))) as Map<String, Object?>;
 }
 
 void validateNodeJsonSerialization(DiagnosticsNode node) {
   validateNodeJsonSerializationHelper(simulateJsonSerialization(node), node);
 }
 
-void validateNodeJsonSerializationHelper(Map<String, Object> json, DiagnosticsNode node) {
+void validateNodeJsonSerializationHelper(Map<String, Object?> json, DiagnosticsNode node) {
   expect(json['name'], equals(node.name));
   expect(json['showSeparator'] ?? true, equals(node.showSeparator));
   expect(json['description'], equals(node.toDescription()));
@@ -67,18 +67,18 @@
   expect(json['hasChildren'] ?? false, equals(node.getChildren().isNotEmpty));
 }
 
-void validatePropertyJsonSerialization(DiagnosticsProperty<Object> property) {
+void validatePropertyJsonSerialization(DiagnosticsProperty<Object?> property) {
   validatePropertyJsonSerializationHelper(simulateJsonSerialization(property), property);
 }
 
 void validateStringPropertyJsonSerialization(StringProperty property) {
-  final Map<String, Object> json = simulateJsonSerialization(property);
+  final Map<String, Object?> json = simulateJsonSerialization(property);
   expect(json['quoted'], equals(property.quoted));
   validatePropertyJsonSerializationHelper(json, property);
 }
 
 void validateFlagPropertyJsonSerialization(FlagProperty property) {
-  final Map<String, Object> json = simulateJsonSerialization(property);
+  final Map<String, Object?> json = simulateJsonSerialization(property);
   expect(json['ifTrue'], equals(property.ifTrue));
 
   if (property.ifTrue != null) {
@@ -96,7 +96,7 @@
 }
 
 void validateDoublePropertyJsonSerialization(DoubleProperty property) {
-  final Map<String, Object> json = simulateJsonSerialization(property);
+  final Map<String, Object?> json = simulateJsonSerialization(property);
   if (property.unit != null) {
     expect(json['unit'], equals(property.unit));
   } else {
@@ -109,7 +109,7 @@
 }
 
 void validateObjectFlagPropertyJsonSerialization(ObjectFlagProperty<Object> property) {
-  final Map<String, Object> json = simulateJsonSerialization(property);
+  final Map<String, Object?> json = simulateJsonSerialization(property);
   if (property.ifPresent != null) {
     expect(json['ifPresent'], equals(property.ifPresent));
   } else {
@@ -120,7 +120,7 @@
 }
 
 void validateIterableFlagsPropertyJsonSerialization(FlagsSummary<Object?> property) {
-  final Map<String, Object> json = simulateJsonSerialization(property);
+  final Map<String, Object?> json = simulateJsonSerialization(property);
   if (property.value.isNotEmpty) {
     expect(json['values'], equals(
       property.value.entries
@@ -135,9 +135,9 @@
 }
 
 void validateIterablePropertyJsonSerialization(IterableProperty<Object> property) {
-  final Map<String, Object> json = simulateJsonSerialization(property);
+  final Map<String, Object?> json = simulateJsonSerialization(property);
   if (property.value != null) {
-    final List<Object> valuesJson = json['values']! as List<Object>;
+    final List<Object?> valuesJson = json['values']! as List<Object?>;
     final List<String> expectedValues = property.value!.map<String>((Object value) => value.toString()).toList();
     expect(listEquals(valuesJson, expectedValues), isTrue);
   } else {
@@ -147,7 +147,7 @@
   validatePropertyJsonSerializationHelper(json, property);
 }
 
-void validatePropertyJsonSerializationHelper(final Map<String, Object> json, DiagnosticsProperty<Object> property) {
+void validatePropertyJsonSerializationHelper(final Map<String, Object?> json, DiagnosticsProperty<Object?> property) {
   if (property.defaultValue != kNoDefaultValue) {
     expect(json['defaultValue'], equals(property.defaultValue.toString()));
   } else {
@@ -1919,7 +1919,7 @@
     expect(messageProperty.name, equals('diagnostics'));
     expect(messageProperty.value, isNull);
     expect(messageProperty.showName, isTrue);
-    validatePropertyJsonSerialization(messageProperty as DiagnosticsProperty<Object>);
+    validatePropertyJsonSerialization(messageProperty as DiagnosticsProperty<Object?>);
   });
 
   test('error message style wrap test', () {
@@ -2246,7 +2246,7 @@
 
   test('DiagnosticsProperty for basic types has value in json', () {
     DiagnosticsProperty<int> intProperty = DiagnosticsProperty<int>('int1', 10);
-    Map<String, Object> json = simulateJsonSerialization(intProperty);
+    Map<String, Object?> json = simulateJsonSerialization(intProperty);
     expect(json['name'], 'int1');
     expect(json['value'], 10);
 
diff --git a/packages/flutter/test/material/app_test.dart b/packages/flutter/test/material/app_test.dart
index 428af5e..46fbcec 100644
--- a/packages/flutter/test/material/app_test.dart
+++ b/packages/flutter/test/material/app_test.dart
@@ -249,7 +249,7 @@
   });
 
   testWidgets('Return value from pop is correct', (WidgetTester tester) async {
-    late Future<Object> result;
+    late Future<Object?> result;
     await tester.pumpWidget(
         MaterialApp(
           home: Builder(
@@ -258,7 +258,7 @@
                   child: ElevatedButton(
                       child: const Text('X'),
                       onPressed: () async {
-                        result = Navigator.of(context)!.pushNamed('/a');
+                        result = Navigator.of(context)!.pushNamed<Object?>('/a');
                       },
                   ),
                 );
diff --git a/packages/flutter/test/material/date_picker_test.dart b/packages/flutter/test/material/date_picker_test.dart
index b1ea26e..e135d56 100644
--- a/packages/flutter/test/material/date_picker_test.dart
+++ b/packages/flutter/test/material/date_picker_test.dart
@@ -83,7 +83,7 @@
 
   Future<void> prepareDatePicker(
     WidgetTester tester,
-    Future<void> callback(Future<DateTime> date),
+    Future<void> callback(Future<DateTime?> date),
     { TextDirection textDirection = TextDirection.ltr }
   ) async {
     late BuildContext buttonContext;
@@ -105,7 +105,7 @@
     await tester.tap(find.text('Go'));
     expect(buttonContext, isNotNull);
 
-    final Future<DateTime> date = showDatePicker(
+    final Future<DateTime?> date = showDatePicker(
       context: buttonContext,
       initialDate: initialDate,
       firstDate: firstDate,
@@ -138,7 +138,7 @@
       cancelText = 'nope';
       confirmText = 'yep';
       helpText = 'help';
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         expect(find.text(cancelText!), findsOneWidget);
         expect(find.text(confirmText!), findsOneWidget);
         expect(find.text(helpText!), findsOneWidget);
@@ -146,21 +146,21 @@
     });
 
     testWidgets('Initial date is the default', (WidgetTester tester) async {
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         await tester.tap(find.text('OK'));
         expect(await date, DateTime(2016, DateTime.january, 15));
       });
     });
 
     testWidgets('Can cancel', (WidgetTester tester) async {
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         await tester.tap(find.text('CANCEL'));
         expect(await date, isNull);
       });
     });
 
     testWidgets('Can toggle to input entry mode', (WidgetTester tester) async {
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         expect(find.byType(TextField), findsNothing);
         await tester.tap(find.byIcon(Icons.edit));
         await tester.pumpAndSettle();
@@ -169,7 +169,7 @@
     });
 
     testWidgets('Toggle to input mode keeps selected date', (WidgetTester tester) async {
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         await tester.tap(find.text('12'));
         await tester.tap(find.byIcon(Icons.edit));
         await tester.pumpAndSettle();
@@ -179,7 +179,7 @@
     });
 
     testWidgets('Switching to input mode resets input error state', (WidgetTester tester) async {
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         // Enter text input mode and type an invalid date to get error.
         await tester.tap(find.byIcon(Icons.edit));
         await tester.pumpAndSettle();
@@ -438,7 +438,7 @@
 
   group('Calendar mode', () {
     testWidgets('Can select a day', (WidgetTester tester) async {
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         await tester.tap(find.text('12'));
         await tester.tap(find.text('OK'));
         expect(await date, equals(DateTime(2016, DateTime.january, 12)));
@@ -446,7 +446,7 @@
     });
 
     testWidgets('Can select a month', (WidgetTester tester) async {
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         await tester.tap(previousMonthIcon);
         await tester.pumpAndSettle(const Duration(seconds: 1));
         await tester.tap(find.text('25'));
@@ -456,7 +456,7 @@
     });
 
     testWidgets('Can select a year', (WidgetTester tester) async {
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         await tester.tap(find.text('January 2016')); // Switch to year mode.
         await tester.pump();
         await tester.tap(find.text('2018'));
@@ -467,7 +467,7 @@
 
     testWidgets('Selecting date does not change displayed month', (WidgetTester tester) async {
       initialDate = DateTime(2020, DateTime.march, 15);
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         await tester.tap(nextMonthIcon);
         await tester.pumpAndSettle(const Duration(seconds: 1));
         expect(find.text('April 2020'), findsOneWidget);
@@ -480,7 +480,7 @@
     });
 
     testWidgets('Changing year does not change selected date', (WidgetTester tester) async {
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         await tester.tap(find.text('January 2016'));
         await tester.pump();
         await tester.tap(find.text('2018'));
@@ -491,7 +491,7 @@
     });
 
     testWidgets('Changing year does not change the month', (WidgetTester tester) async {
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         await tester.tap(nextMonthIcon);
         await tester.pumpAndSettle();
         await tester.tap(nextMonthIcon);
@@ -505,7 +505,7 @@
     });
 
     testWidgets('Can select a year and then a day', (WidgetTester tester) async {
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         await tester.tap(find.text('January 2016')); // Switch to year mode.
         await tester.pump();
         await tester.tap(find.text('2017'));
@@ -517,7 +517,7 @@
     });
 
     testWidgets('Current year is visible in year picker', (WidgetTester tester) async {
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         await tester.tap(find.text('January 2016')); // Switch to year mode.
         await tester.pump();
         expect(find.text('2016'), findsOneWidget);
@@ -528,7 +528,7 @@
       initialDate = DateTime(2017, DateTime.january, 15);
       firstDate = initialDate;
       lastDate = initialDate;
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         // Earlier than firstDate. Should be ignored.
         await tester.tap(find.text('10'));
         // Later than lastDate. Should be ignored.
@@ -543,7 +543,7 @@
       initialDate = DateTime(2017, DateTime.january, 15);
       firstDate = initialDate;
       lastDate = DateTime(2017, DateTime.february, 20);
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         await tester.tap(nextMonthIcon);
         await tester.pumpAndSettle(const Duration(seconds: 1));
         // Shouldn't be possible to keep going into March.
@@ -555,7 +555,7 @@
       initialDate = DateTime(2017, DateTime.january, 15);
       firstDate = DateTime(2016, DateTime.december, 10);
       lastDate = initialDate;
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         await tester.tap(previousMonthIcon);
         await tester.pumpAndSettle(const Duration(seconds: 1));
         // Shouldn't be possible to keep going into November.
@@ -567,7 +567,7 @@
       initialDate = DateTime(2018, DateTime.july, 4);
       firstDate = DateTime(2018, DateTime.june, 9);
       lastDate = DateTime(2018, DateTime.december, 15);
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         await tester.tap(find.text('July 2018')); // Switch to year mode.
         await tester.pumpAndSettle();
         await tester.tap(find.text('2016')); // Disabled, doesn't change the year.
@@ -582,7 +582,7 @@
       initialDate = DateTime(2018, DateTime.may, 4);
       firstDate = DateTime(2016, DateTime.june, 9);
       lastDate = DateTime(2019, DateTime.january, 15);
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         await tester.tap(find.text('May 2018'));
         await tester.pumpAndSettle();
         await tester.tap(find.text('2016'));
@@ -596,7 +596,7 @@
       initialDate = DateTime(2018, DateTime.may, 4);
       firstDate = DateTime(2016, DateTime.june, 9);
       lastDate = DateTime(2019, DateTime.january, 15);
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         await tester.tap(find.text('May 2018'));
         await tester.pumpAndSettle();
         await tester.tap(find.text('2019'));
@@ -611,7 +611,7 @@
       firstDate = DateTime(2017, DateTime.january, 10);
       lastDate = DateTime(2017, DateTime.january, 20);
       selectableDayPredicate = (DateTime day) => day.day.isEven;
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         await tester.tap(find.text('13')); // Odd, doesn't work.
         await tester.tap(find.text('10')); // Even, works.
         await tester.tap(find.text('17')); // Odd, doesn't work.
@@ -623,7 +623,7 @@
     testWidgets('Can select initial calendar picker mode', (WidgetTester tester) async {
       initialDate = DateTime(2014, DateTime.january, 15);
       initialCalendarMode = DatePickerMode.year;
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         await tester.pump();
         // 2018 wouldn't be available if the year picker wasn't showing.
         // The initial current year is 2014.
@@ -635,7 +635,7 @@
 
     testWidgets('currentDate is highlighted', (WidgetTester tester) async {
       today = DateTime(2016, 1, 2);
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         await tester.pump();
         const Color todayColor = Color(0xff2196f3); // default primary color
         expect(
@@ -649,7 +649,7 @@
     testWidgets('Selecting date does not switch picker to year selection', (WidgetTester tester) async {
       initialDate = DateTime(2020, DateTime.may, 10);
       initialCalendarMode = DatePickerMode.year;
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         await tester.pump();
         await tester.tap(find.text('2017'));
         await tester.pump();
@@ -671,7 +671,7 @@
     });
 
     testWidgets('Initial entry mode is used', (WidgetTester tester) async {
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         expect(find.byType(TextField), findsOneWidget);
       });
     });
@@ -682,7 +682,7 @@
       fieldHintText = 'hint';
       fieldLabelText = 'label';
       helpText = 'help';
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         expect(find.text(cancelText!), findsOneWidget);
         expect(find.text(confirmText!), findsOneWidget);
         expect(find.text(fieldHintText!), findsOneWidget);
@@ -692,14 +692,14 @@
     });
 
     testWidgets('Initial date is the default', (WidgetTester tester) async {
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         await tester.tap(find.text('OK'));
         expect(await date, DateTime(2016, DateTime.january, 15));
       });
     });
 
     testWidgets('Can toggle to calendar entry mode', (WidgetTester tester) async {
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         expect(find.byType(TextField), findsOneWidget);
         await tester.tap(find.byIcon(Icons.calendar_today));
         await tester.pumpAndSettle();
@@ -708,7 +708,7 @@
     });
 
     testWidgets('Toggle to calendar mode keeps selected date', (WidgetTester tester) async {
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         final TextField field = textField(tester);
         field.controller!.clear();
 
@@ -721,7 +721,7 @@
     });
 
     testWidgets('Entered text returns date', (WidgetTester tester) async {
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         final TextField field = textField(tester);
         field.controller!.clear();
 
@@ -733,7 +733,7 @@
 
     testWidgets('Too short entered text shows error', (WidgetTester tester) async {
       errorFormatText = 'oops';
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         final TextField field = textField(tester);
         field.controller!.clear();
 
@@ -749,7 +749,7 @@
 
     testWidgets('Bad format entered text shows error', (WidgetTester tester) async {
       errorFormatText = 'oops';
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         final TextField field = textField(tester);
         field.controller!.clear();
 
@@ -766,7 +766,7 @@
 
     testWidgets('Invalid entered text shows error', (WidgetTester tester) async {
       errorInvalidText = 'oops';
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         final TextField field = textField(tester);
         field.controller!.clear();
 
@@ -912,7 +912,7 @@
     });
 
     testWidgets('Selecting date vibrates', (WidgetTester tester) async {
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         await tester.tap(find.text('10'));
         await tester.pump(hapticFeedbackInterval);
         expect(feedback.hapticCount, 1);
@@ -926,7 +926,7 @@
     });
 
     testWidgets('Tapping unselectable date does not vibrate', (WidgetTester tester) async {
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         await tester.tap(find.text('11'));
         await tester.pump(hapticFeedbackInterval);
         expect(feedback.hapticCount, 0);
@@ -940,7 +940,7 @@
     });
 
     testWidgets('Changing modes and year vibrates', (WidgetTester tester) async {
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         await tester.tap(find.text('January 2017'));
         await tester.pump(hapticFeedbackInterval);
         expect(feedback.hapticCount, 1);
@@ -956,7 +956,7 @@
       final SemanticsHandle semantics = tester.ensureSemantics();
       addTearDown(semantics.dispose);
 
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         // Header
         expect(tester.getSemantics(find.text('SELECT DATE')), matchesSemantics(
           label: 'SELECT DATE\nFri, Jan 15',
@@ -1174,7 +1174,7 @@
       addTearDown(semantics.dispose);
 
       initialCalendarMode = DatePickerMode.year;
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         // Header
         expect(tester.getSemantics(find.text('SELECT DATE')), matchesSemantics(
           label: 'SELECT DATE\nFri, Jan 15',
@@ -1231,7 +1231,7 @@
       addTearDown(semantics.dispose);
 
       initialEntryMode = DatePickerEntryMode.input;
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         // Header
         expect(tester.getSemantics(find.text('SELECT DATE')), matchesSemantics(
           label: 'SELECT DATE\nFri, Jan 15',
@@ -1285,7 +1285,7 @@
 
   group('Keyboard navigation', () {
     testWidgets('Can toggle to calendar entry mode', (WidgetTester tester) async {
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         expect(find.byType(TextField), findsNothing);
         // Navigate to the entry toggle button and activate it
         await tester.sendKeyEvent(LogicalKeyboardKey.tab);
@@ -1301,7 +1301,7 @@
     });
 
     testWidgets('Can toggle to year mode', (WidgetTester tester) async {
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         expect(find.text('2016'), findsNothing);
         // Navigate to the year selector and activate it
         await tester.sendKeyEvent(LogicalKeyboardKey.tab);
@@ -1313,7 +1313,7 @@
     });
 
     testWidgets('Can navigate next/previous months', (WidgetTester tester) async {
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         expect(find.text('January 2016'), findsOneWidget);
         // Navigate to the previous month button and activate it twice
         await tester.sendKeyEvent(LogicalKeyboardKey.tab);
@@ -1341,7 +1341,7 @@
     });
 
     testWidgets('Can navigate date grid with arrow keys', (WidgetTester tester) async {
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         // Navigate to the grid
         await tester.sendKeyEvent(LogicalKeyboardKey.tab);
         await tester.sendKeyEvent(LogicalKeyboardKey.tab);
@@ -1377,7 +1377,7 @@
     });
 
     testWidgets('Navigating with arrow keys scrolls months', (WidgetTester tester) async {
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         // Navigate to the grid
         await tester.sendKeyEvent(LogicalKeyboardKey.tab);
         await tester.sendKeyEvent(LogicalKeyboardKey.tab);
@@ -1425,7 +1425,7 @@
     });
 
     testWidgets('RTL text direction reverses the horizontal arrow key navigation', (WidgetTester tester) async {
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         // Navigate to the grid
         await tester.sendKeyEvent(LogicalKeyboardKey.tab);
         await tester.sendKeyEvent(LogicalKeyboardKey.tab);
@@ -1484,7 +1484,7 @@
       addTearDown(tester.binding.window.clearPhysicalSizeTestValue);
       tester.binding.window.devicePixelRatioTestValue = 1.0;
       addTearDown(tester.binding.window.clearDevicePixelRatioTestValue);
-      await prepareDatePicker(tester, (Future<DateTime> date) async {
+      await prepareDatePicker(tester, (Future<DateTime?> date) async {
         await tester.tap(find.text('OK'));
       });
       await tester.pumpAndSettle();
diff --git a/packages/flutter/test/material/date_range_picker_test.dart b/packages/flutter/test/material/date_range_picker_test.dart
index 8d1bb8a..a890021 100644
--- a/packages/flutter/test/material/date_range_picker_test.dart
+++ b/packages/flutter/test/material/date_range_picker_test.dart
@@ -52,7 +52,7 @@
 
   Future<void> preparePicker(
     WidgetTester tester,
-    Future<void> callback(Future<DateTimeRange> date),
+    Future<void> callback(Future<DateTimeRange?> date),
     { TextDirection textDirection = TextDirection.ltr }
   ) async {
     late BuildContext buttonContext;
@@ -74,7 +74,7 @@
     await tester.tap(find.text('Go'));
     expect(buttonContext, isNotNull);
 
-    final Future<DateTimeRange> range = showDateRangePicker(
+    final Future<DateTimeRange?> range = showDateRangePicker(
       context: buttonContext,
       initialDateRange: initialDateRange,
       firstDate: firstDate,
@@ -107,14 +107,14 @@
   testWidgets('Save and help text is used', (WidgetTester tester) async {
     helpText = 'help';
     saveText = 'make it so';
-    await preparePicker(tester, (Future<DateTimeRange> range) async {
+    await preparePicker(tester, (Future<DateTimeRange?> range) async {
       expect(find.text(helpText!), findsOneWidget);
       expect(find.text(saveText!), findsOneWidget);
     });
   });
 
   testWidgets('Initial date is the default', (WidgetTester tester) async {
-    await preparePicker(tester, (Future<DateTimeRange> range) async {
+    await preparePicker(tester, (Future<DateTimeRange?> range) async {
       await tester.tap(find.text('SAVE'));
       expect(await range, DateTimeRange(
         start: DateTime(2016, DateTime.january, 15),
@@ -131,7 +131,7 @@
       start: lastDate,
       end: lastDate,
     );
-    await preparePicker(tester, (Future<DateTimeRange> range) async {
+    await preparePicker(tester, (Future<DateTimeRange?> range) async {
       // December header should be showing, but no November
       expect(find.text('December 2016'), findsOneWidget);
       expect(find.text('November 2016'), findsNothing);
@@ -146,7 +146,7 @@
       start: firstDate,
       end: firstDate,
     );
-    await preparePicker(tester, (Future<DateTimeRange> range) async {
+    await preparePicker(tester, (Future<DateTimeRange?> range) async {
       // January and February headers should be showing, but no March
       expect(find.text('January 2015'), findsOneWidget);
       expect(find.text('February 2015'), findsOneWidget);
@@ -161,7 +161,7 @@
     currentDate = DateTime(2016, DateTime.september, 1);
     initialDateRange = null;
 
-    await preparePicker(tester, (Future<DateTimeRange> range) async {
+    await preparePicker(tester, (Future<DateTimeRange?> range) async {
       // September and October headers should be showing, but no August
       expect(find.text('September 2016'), findsOneWidget);
       expect(find.text('October 2016'), findsOneWidget);
@@ -170,14 +170,14 @@
   });
 
   testWidgets('Can cancel', (WidgetTester tester) async {
-    await preparePicker(tester, (Future<DateTimeRange> range) async {
+    await preparePicker(tester, (Future<DateTimeRange?> range) async {
       await tester.tap(find.byIcon(Icons.close));
       expect(await range, isNull);
     });
   });
 
   testWidgets('Can select a range', (WidgetTester tester) async {
-    await preparePicker(tester, (Future<DateTimeRange> range) async {
+    await preparePicker(tester, (Future<DateTimeRange?> range) async {
       await tester.tap(find.text('12').first);
       await tester.tap(find.text('14').first);
       await tester.tap(find.text('SAVE'));
@@ -189,7 +189,7 @@
   });
 
   testWidgets('Tapping earlier date resets selected range', (WidgetTester tester) async {
-    await preparePicker(tester, (Future<DateTimeRange> range) async {
+    await preparePicker(tester, (Future<DateTimeRange?> range) async {
       await tester.tap(find.text('12').first);
       await tester.tap(find.text('11').first);
       await tester.tap(find.text('15').first);
@@ -202,7 +202,7 @@
   });
 
   testWidgets('Can select single day range', (WidgetTester tester) async {
-    await preparePicker(tester, (Future<DateTimeRange> range) async {
+    await preparePicker(tester, (Future<DateTimeRange?> range) async {
       await tester.tap(find.text('12').first);
       await tester.tap(find.text('12').first);
       await tester.tap(find.text('SAVE'));
@@ -220,7 +220,7 @@
     );
     firstDate = DateTime(2017, DateTime.january, 12);
     lastDate = DateTime(2017, DateTime.january, 16);
-    await preparePicker(tester, (Future<DateTimeRange> range) async {
+    await preparePicker(tester, (Future<DateTimeRange?> range) async {
       // Earlier than firstDate. Should be ignored.
       await tester.tap(find.text('10'));
       // Later than lastDate. Should be ignored.
@@ -232,7 +232,7 @@
   });
 
   testWidgets('Can toggle to input entry mode', (WidgetTester tester) async {
-    await preparePicker(tester, (Future<DateTimeRange> range) async {
+    await preparePicker(tester, (Future<DateTimeRange?> range) async {
       expect(find.byType(TextField), findsNothing);
       await tester.tap(find.byIcon(Icons.edit));
       await tester.pumpAndSettle();
@@ -241,7 +241,7 @@
   });
 
   testWidgets('Toggle to input mode keeps selected date', (WidgetTester tester) async {
-    await preparePicker(tester, (Future<DateTimeRange> range) async {
+    await preparePicker(tester, (Future<DateTimeRange?> range) async {
       await tester.tap(find.text('12').first);
       await tester.tap(find.text('14').first);
       await tester.tap(find.byIcon(Icons.edit));
@@ -262,7 +262,7 @@
     testWidgets('Invalid start date', (WidgetTester tester) async {
       // Invalid start date should have neither a start nor end date selected in
       // calendar mode
-      await preparePicker(tester, (Future<DateTimeRange> range) async {
+      await preparePicker(tester, (Future<DateTimeRange?> range) async {
         await tester.enterText(find.byType(TextField).at(0), '12/27/1918');
         await tester.enterText(find.byType(TextField).at(1), '12/25/2016');
         await tester.tap(find.byIcon(Icons.calendar_today));
@@ -275,7 +275,7 @@
 
     testWidgets('Invalid end date', (WidgetTester tester) async {
       // Invalid end date should only have a start date selected
-      await preparePicker(tester, (Future<DateTimeRange> range) async {
+      await preparePicker(tester, (Future<DateTimeRange?> range) async {
         await tester.enterText(find.byType(TextField).at(0), '12/24/2016');
         await tester.enterText(find.byType(TextField).at(1), '12/25/2050');
         await tester.tap(find.byIcon(Icons.calendar_today));
@@ -288,7 +288,7 @@
 
     testWidgets('Invalid range', (WidgetTester tester) async {
       // Start date after end date should just use the start date
-      await preparePicker(tester, (Future<DateTimeRange> range) async {
+      await preparePicker(tester, (Future<DateTimeRange?> range) async {
         await tester.enterText(find.byType(TextField).at(0), '12/25/2016');
         await tester.enterText(find.byType(TextField).at(1), '12/24/2016');
         await tester.tap(find.byIcon(Icons.calendar_today));
@@ -375,7 +375,7 @@
     });
 
     testWidgets('Selecting dates vibrates', (WidgetTester tester) async {
-      await preparePicker(tester, (Future<DateTimeRange> range) async {
+      await preparePicker(tester, (Future<DateTimeRange?> range) async {
         await tester.tap(find.text('10').first);
         await tester.pump(hapticFeedbackInterval);
         expect(feedback.hapticCount, 1);
@@ -389,7 +389,7 @@
     });
 
     testWidgets('Tapping unselectable date does not vibrate', (WidgetTester tester) async {
-      await preparePicker(tester, (Future<DateTimeRange> range) async {
+      await preparePicker(tester, (Future<DateTimeRange?> range) async {
         await tester.tap(find.text('8').first);
         await tester.pump(hapticFeedbackInterval);
         expect(feedback.hapticCount, 0);
@@ -399,7 +399,7 @@
 
   group('Keyboard navigation', () {
     testWidgets('Can toggle to calendar entry mode', (WidgetTester tester) async {
-      await preparePicker(tester, (Future<DateTimeRange> range) async {
+      await preparePicker(tester, (Future<DateTimeRange?> range) async {
         expect(find.byType(TextField), findsNothing);
         // Navigate to the entry toggle button and activate it
         await tester.sendKeyEvent(LogicalKeyboardKey.tab);
@@ -412,7 +412,7 @@
     });
 
     testWidgets('Can navigate date grid with arrow keys', (WidgetTester tester) async {
-      await preparePicker(tester, (Future<DateTimeRange> range) async {
+      await preparePicker(tester, (Future<DateTimeRange?> range) async {
         // Navigate to the grid
         await tester.sendKeyEvent(LogicalKeyboardKey.tab);
         await tester.sendKeyEvent(LogicalKeyboardKey.tab);
@@ -464,7 +464,7 @@
     });
 
     testWidgets('Navigating with arrow keys scrolls as needed', (WidgetTester tester) async {
-      await preparePicker(tester, (Future<DateTimeRange> range) async {
+      await preparePicker(tester, (Future<DateTimeRange?> range) async {
         // Jan and Feb headers should be showing, but no March
         expect(find.text('January 2016'), findsOneWidget);
         expect(find.text('February 2016'), findsOneWidget);
@@ -529,7 +529,7 @@
     });
 
     testWidgets('RTL text direction reverses the horizontal arrow key navigation', (WidgetTester tester) async {
-      await preparePicker(tester, (Future<DateTimeRange> range) async {
+      await preparePicker(tester, (Future<DateTimeRange?> range) async {
         // Navigate to the grid
         await tester.sendKeyEvent(LogicalKeyboardKey.tab);
         await tester.sendKeyEvent(LogicalKeyboardKey.tab);
@@ -589,7 +589,7 @@
     });
 
     testWidgets('Initial entry mode is used', (WidgetTester tester) async {
-      await preparePicker(tester, (Future<DateTimeRange> range) async {
+      await preparePicker(tester, (Future<DateTimeRange?> range) async {
         expect(find.byType(TextField), findsNWidgets(2));
       });
     });
@@ -603,7 +603,7 @@
       fieldStartLabelText = 'label1';
       fieldEndLabelText = 'label2';
       helpText = 'help';
-      await preparePicker(tester, (Future<DateTimeRange> range) async {
+      await preparePicker(tester, (Future<DateTimeRange?> range) async {
         expect(find.text(cancelText!), findsOneWidget);
         expect(find.text(confirmText!), findsOneWidget);
         expect(find.text(fieldStartHintText!), findsOneWidget);
@@ -615,7 +615,7 @@
     });
 
     testWidgets('Initial date is the default', (WidgetTester tester) async {
-      await preparePicker(tester, (Future<DateTimeRange> range) async {
+      await preparePicker(tester, (Future<DateTimeRange?> range) async {
         await tester.tap(find.text('OK'));
         expect(await range, DateTimeRange(
           start: DateTime(2017, DateTime.january, 15),
@@ -625,7 +625,7 @@
     });
 
     testWidgets('Can toggle to calendar entry mode', (WidgetTester tester) async {
-      await preparePicker(tester, (Future<DateTimeRange> range) async {
+      await preparePicker(tester, (Future<DateTimeRange?> range) async {
         expect(find.byType(TextField), findsNWidgets(2));
         await tester.tap(find.byIcon(Icons.calendar_today));
         await tester.pumpAndSettle();
@@ -635,7 +635,7 @@
 
     testWidgets('Toggle to calendar mode keeps selected date', (WidgetTester tester) async {
       initialDateRange = null;
-      await preparePicker(tester, (Future<DateTimeRange> range) async {
+      await preparePicker(tester, (Future<DateTimeRange?> range) async {
         await tester.enterText(find.byType(TextField).at(0), '12/25/2016');
         await tester.enterText(find.byType(TextField).at(1), '12/27/2016');
         await tester.tap(find.byIcon(Icons.calendar_today));
@@ -651,7 +651,7 @@
 
     testWidgets('Entered text returns range', (WidgetTester tester) async {
       initialDateRange = null;
-      await preparePicker(tester, (Future<DateTimeRange> range) async {
+      await preparePicker(tester, (Future<DateTimeRange?> range) async {
         await tester.enterText(find.byType(TextField).at(0), '12/25/2016');
         await tester.enterText(find.byType(TextField).at(1), '12/27/2016');
         await tester.tap(find.text('OK'));
@@ -666,7 +666,7 @@
     testWidgets('Too short entered text shows error', (WidgetTester tester) async {
       initialDateRange = null;
       errorFormatText = 'oops';
-      await preparePicker(tester, (Future<DateTimeRange> range) async {
+      await preparePicker(tester, (Future<DateTimeRange?> range) async {
         await tester.enterText(find.byType(TextField).at(0), '12/25');
         await tester.enterText(find.byType(TextField).at(1), '12/25');
         expect(find.text(errorFormatText!), findsNothing);
@@ -680,7 +680,7 @@
     testWidgets('Bad format entered text shows error', (WidgetTester tester) async {
       initialDateRange = null;
       errorFormatText = 'oops';
-      await preparePicker(tester, (Future<DateTimeRange> range) async {
+      await preparePicker(tester, (Future<DateTimeRange?> range) async {
         await tester.enterText(find.byType(TextField).at(0), '20202014');
         await tester.enterText(find.byType(TextField).at(1), '20212014');
         expect(find.text(errorFormatText!), findsNothing);
@@ -694,7 +694,7 @@
     testWidgets('Invalid entered text shows error', (WidgetTester tester) async {
       initialDateRange = null;
       errorInvalidText = 'oops';
-      await preparePicker(tester, (Future<DateTimeRange> range) async {
+      await preparePicker(tester, (Future<DateTimeRange?> range) async {
         await tester.enterText(find.byType(TextField).at(0), '08/08/2014');
         await tester.enterText(find.byType(TextField).at(1), '08/08/2014');
         expect(find.text(errorInvalidText!), findsNothing);
@@ -708,7 +708,7 @@
     testWidgets('End before start date shows error', (WidgetTester tester) async {
       initialDateRange = null;
       errorInvalidRangeText = 'oops';
-      await preparePicker(tester, (Future<DateTimeRange> range) async {
+      await preparePicker(tester, (Future<DateTimeRange?> range) async {
         await tester.enterText(find.byType(TextField).at(0), '12/27/2016');
         await tester.enterText(find.byType(TextField).at(1), '12/25/2016');
         expect(find.text(errorInvalidRangeText!), findsNothing);
@@ -722,7 +722,7 @@
     testWidgets('Error text only displayed for invalid date', (WidgetTester tester) async {
       initialDateRange = null;
       errorInvalidText = 'oops';
-      await preparePicker(tester, (Future<DateTimeRange> range) async {
+      await preparePicker(tester, (Future<DateTimeRange?> range) async {
         await tester.enterText(find.byType(TextField).at(0), '12/27/2016');
         await tester.enterText(find.byType(TextField).at(1), '01/01/2018');
         expect(find.text(errorInvalidText!), findsNothing);
@@ -735,7 +735,7 @@
 
     testWidgets('End before start date does not get passed to calendar mode', (WidgetTester tester) async {
       initialDateRange = null;
-      await preparePicker(tester, (Future<DateTimeRange> range) async {
+      await preparePicker(tester, (Future<DateTimeRange?> range) async {
         await tester.enterText(find.byType(TextField).at(0), '12/27/2016');
         await tester.enterText(find.byType(TextField).at(1), '12/25/2016');
 
diff --git a/packages/flutter/test/material/dialog_test.dart b/packages/flutter/test/material/dialog_test.dart
index cc54bbd..212c499 100644
--- a/packages/flutter/test/material/dialog_test.dart
+++ b/packages/flutter/test/material/dialog_test.dart
@@ -233,7 +233,7 @@
 
     final BuildContext context = tester.element(find.text('Go'));
 
-    final Future<int> result = showDialog<int>(
+    final Future<int?> result = showDialog<int>(
       context: context,
       builder: (BuildContext context) {
         return SimpleDialog(
@@ -273,7 +273,7 @@
       ),
     );
 
-    final Future<int> result = showDialog<int>(
+    final Future<int?> result = showDialog<int>(
       context: navigator.currentContext!,
       builder: (BuildContext context) {
         return SimpleDialog(
@@ -1382,7 +1382,7 @@
     final List<int> dismissedItems = <int>[];
 
     // Dismiss is confirmed IFF confirmDismiss() returns true.
-    Future<bool> confirmDismiss (DismissDirection dismissDirection) {
+    Future<bool?> confirmDismiss (DismissDirection dismissDirection) async {
       return showDialog<bool>(
         context: _scaffoldKey.currentContext!,
         barrierDismissible: true, // showDialog() returns null if tapped outside the dialog
@@ -1762,7 +1762,7 @@
     final BuildContext context = tester.element(find.text('Go'));
     const RouteSettings exampleSetting = RouteSettings(name: 'simple');
 
-    final Future<int> result = showDialog<int>(
+    final Future<int?> result = showDialog<int>(
       context: context,
       builder: (BuildContext context) {
         return SimpleDialog(
diff --git a/packages/flutter/test/material/input_decorator_test.dart b/packages/flutter/test/material/input_decorator_test.dart
index 35a8646..056e8b6 100644
--- a/packages/flutter/test/material/input_decorator_test.dart
+++ b/packages/flutter/test/material/input_decorator_test.dart
@@ -4253,40 +4253,6 @@
     expect(getOpacity(tester, prefixText), 1.0);
   });
 
-  testWidgets('OutlineInputBorder with InputDecorator long label, width should ignore icon width', (WidgetTester tester) async {
-    // Related issue: https://github.com/flutter/flutter/issues/64427
-    const String labelText = 'Flutter is Google’s UI toolkit for building beautiful, natively compiled applications for mobile, web, and desktop from a single codebase.';
-
-    Widget getLabeledInputDecorator(bool useOutlineBorder) => MaterialApp(
-        home: Material(
-          child: Container(
-            width: 300,
-            child: TextField(
-              decoration: InputDecoration(
-                border: useOutlineBorder ? const OutlineInputBorder(
-                  borderSide: BorderSide(color: Colors.greenAccent, width: 1.0),
-                ) : null,
-                suffixIcon: const Icon(Icons.arrow_drop_down),
-                floatingLabelBehavior: FloatingLabelBehavior.always,
-                labelText: labelText,
-              ),
-            ),
-          ),
-        ),
-      );
-
-    // Build with no OutlineInputBorder.
-    await tester.pumpWidget(getLabeledInputDecorator(false));
-
-    // Get the width of the label when there is no OutlineInputBorder.
-    final double labelWidth = tester.getSize(find.text(labelText)).width;
-
-    // Build with a OutlineInputBorder.
-    await tester.pumpWidget(getLabeledInputDecorator(true));
-
-    expect(tester.getSize(find.text(labelText)).width > labelWidth, isTrue);
-  });
-
   testWidgets('given enough space, constrained and unconstrained heights result in the same size widget', (WidgetTester tester) async {
     // Regression test for https://github.com/flutter/flutter/issues/65572
     final UniqueKey keyUnconstrained = UniqueKey();
diff --git a/packages/flutter/test/material/popup_menu_test.dart b/packages/flutter/test/material/popup_menu_test.dart
index 5347310..d4f5be8 100644
--- a/packages/flutter/test/material/popup_menu_test.dart
+++ b/packages/flutter/test/material/popup_menu_test.dart
@@ -465,7 +465,7 @@
     final WidgetPredicate popupMenu = (Widget widget) {
       final String widgetType = widget.runtimeType.toString();
       // TODO(mraleph): Remove the old case below.
-      return widgetType == '_PopupMenu<int>' // normal case
+      return widgetType == '_PopupMenu<int?>' // normal case
           || widgetType == '_PopupMenu'; // for old versions of Dart that don't reify method type arguments
     };
 
@@ -794,7 +794,7 @@
     await tester.pumpAndSettle();
 
     // The position is different than the offset because the default position isn't at the origin.
-    expect(tester.getTopLeft(find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_PopupMenu<int>')), const Offset(364.0, 324.0));
+    expect(tester.getTopLeft(find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_PopupMenu<int?>')), const Offset(364.0, 324.0));
   });
 
   testWidgets('open PopupMenu has correct semantics', (WidgetTester tester) async {
diff --git a/packages/flutter/test/material/range_slider_test.dart b/packages/flutter/test/material/range_slider_test.dart
index ecf5d68..66c4e71 100644
--- a/packages/flutter/test/material/range_slider_test.dart
+++ b/packages/flutter/test/material/range_slider_test.dart
@@ -1775,6 +1775,7 @@
             children:  <Matcher>[
               matchesSemantics(
                 isEnabled: true,
+                isSlider: true,
                 hasEnabledState: true,
                 hasIncreaseAction: true,
                 hasDecreaseAction: true,
@@ -1784,6 +1785,7 @@
               ),
               matchesSemantics(
                 isEnabled: true,
+                isSlider: true,
                 hasEnabledState: true,
                 hasIncreaseAction: true,
                 hasDecreaseAction: true,
diff --git a/packages/flutter/test/material/search_test.dart b/packages/flutter/test/material/search_test.dart
index fd73035..9345317 100644
--- a/packages/flutter/test/material/search_test.dart
+++ b/packages/flutter/test/material/search_test.dart
@@ -80,7 +80,7 @@
     // regression test for https://github.com/flutter/flutter/issues/18145
 
     final _TestSearchDelegate delegate = _TestSearchDelegate();
-    final List<String> selectedResults = <String>[];
+    final List<String?> selectedResults = <String?>[];
 
     await tester.pumpWidget(TestHomePage(
       delegate: delegate,
@@ -105,7 +105,7 @@
     await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { });
     await tester.pumpAndSettle();
 
-    expect(selectedResults, <void>[null]);
+    expect(selectedResults, <String?>[null]);
 
     // We are on the homepage again
     expect(find.text('HomeBody'), findsOneWidget);
@@ -374,7 +374,7 @@
   });
 
   testWidgets('Closing nested search returns to search', (WidgetTester tester) async {
-    final List<String> nestedSearchResults = <String>[];
+    final List<String?> nestedSearchResults = <String?>[];
     final _TestSearchDelegate nestedSearchDelegate = _TestSearchDelegate(
       suggestions: 'Nested Suggestions',
       result: 'Nested Result',
@@ -389,7 +389,7 @@
               tooltip: 'Nested Search',
               icon: const Icon(Icons.search),
               onPressed: () async {
-                final String result = await showSearch(
+                final String? result = await showSearch(
                   context: context,
                   delegate: nestedSearchDelegate,
                 );
@@ -467,7 +467,7 @@
               tooltip: 'Nested Search',
               icon: const Icon(Icons.search),
               onPressed: () async {
-                final String result = await showSearch(
+                final String? result = await showSearch(
                   context: context,
                   delegate: nestedSearchDelegate,
                 );
@@ -702,7 +702,7 @@
     this.initialQuery,
   }) : super(key: key);
 
-  final List<String>? results;
+  final List<String?>? results;
   final SearchDelegate<String> delegate;
   final bool passInInitialQuery;
   final String? initialQuery;
@@ -719,7 +719,7 @@
                 tooltip: 'Search',
                 icon: const Icon(Icons.search),
                 onPressed: () async {
-                  String selectedResult;
+                  String? selectedResult;
                   if (passInInitialQuery) {
                     selectedResult = await showSearch<String>(
                       context: context,
diff --git a/packages/flutter/test/material/slider_test.dart b/packages/flutter/test/material/slider_test.dart
index 1e8c586..e754216 100644
--- a/packages/flutter/test/material/slider_test.dart
+++ b/packages/flutter/test/material/slider_test.dart
@@ -1345,7 +1345,7 @@
                       children: <TestSemantics>[
                         TestSemantics(
                           id: 4,
-                          flags: <SemanticsFlag>[SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled, SemanticsFlag.isFocusable],
+                          flags: <SemanticsFlag>[SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled, SemanticsFlag.isFocusable, SemanticsFlag.isSlider],
                           actions: <SemanticsAction>[SemanticsAction.increase, SemanticsAction.decrease],
                           value: '50%',
                           increasedValue: '55%',
@@ -1403,6 +1403,7 @@
                             SemanticsFlag.hasEnabledState,
                             // isFocusable is delayed by 1 frame.
                             SemanticsFlag.isFocusable,
+                            SemanticsFlag.isSlider,
                           ],
                           value: '50%',
                           increasedValue: '55%',
@@ -1443,6 +1444,7 @@
                           id: 4,
                           flags: <SemanticsFlag>[
                             SemanticsFlag.hasEnabledState,
+                            SemanticsFlag.isSlider,
                           ],
                           value: '50%',
                           increasedValue: '55%',
@@ -1508,7 +1510,7 @@
                       children: <TestSemantics>[
                         TestSemantics(
                           id: 4,
-                          flags: <SemanticsFlag>[SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled, SemanticsFlag.isFocusable],
+                          flags: <SemanticsFlag>[SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled, SemanticsFlag.isFocusable, SemanticsFlag.isSlider],
                           actions: <SemanticsAction>[SemanticsAction.increase, SemanticsAction.decrease],
                           value: '50%',
                           increasedValue: '60%',
@@ -1562,7 +1564,7 @@
                       children: <TestSemantics>[
                         TestSemantics(
                           id: 5,
-                          flags: <SemanticsFlag>[SemanticsFlag.hasEnabledState],
+                          flags: <SemanticsFlag>[SemanticsFlag.hasEnabledState, SemanticsFlag.isSlider],
                           value: '50%',
                           increasedValue: '60%',
                           decreasedValue: '40%',
@@ -1623,7 +1625,7 @@
                       children: <TestSemantics>[
                         TestSemantics(
                           id: 4,
-                          flags: <SemanticsFlag>[SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled, SemanticsFlag.isFocusable],
+                          flags: <SemanticsFlag>[SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled, SemanticsFlag.isFocusable, SemanticsFlag.isSlider],
                           actions: <SemanticsAction>[SemanticsAction.increase, SemanticsAction.decrease],
                           value: '40',
                           increasedValue: '60',
diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart
index dd94976..4f4ab2d 100644
--- a/packages/flutter/test/material/text_field_test.dart
+++ b/packages/flutter/test/material/text_field_test.dart
@@ -4331,7 +4331,7 @@
     await tester.sendKeyUpEvent(LogicalKeyboardKey.controlRight);
     await tester.pumpAndSettle();
 
-    const String expected = 'a big a bighouse\njumped over a mouse';
+    const String expected = 'a biga big house\njumped over a mouse';
     expect(find.text(expected), findsOneWidget, reason: 'Because text contains ${controller.text}');
   });
 
diff --git a/packages/flutter/test/material/time_picker_test.dart b/packages/flutter/test/material/time_picker_test.dart
index 1296104..cc13240 100644
--- a/packages/flutter/test/material/time_picker_test.dart
+++ b/packages/flutter/test/material/time_picker_test.dart
@@ -23,7 +23,7 @@
     this.entryMode = TimePickerEntryMode.dial,
   }) : super(key: key);
 
-  final ValueChanged<TimeOfDay> onChanged;
+  final ValueChanged<TimeOfDay?> onChanged;
   final Locale? locale;
   final TimePickerEntryMode entryMode;
 
@@ -55,7 +55,7 @@
 
 Future<Offset?> startPicker(
     WidgetTester tester,
-    ValueChanged<TimeOfDay> onChanged, {
+    ValueChanged<TimeOfDay?> onChanged, {
       TimePickerEntryMode entryMode = TimePickerEntryMode.dial,
     }) async {
   await tester.pumpWidget(_TimePickerLauncher(onChanged: onChanged, locale: const Locale('en', 'US'), entryMode: entryMode));
@@ -82,24 +82,24 @@
 
 void _tests() {
   testWidgets('tap-select an hour', (WidgetTester tester) async {
-    late TimeOfDay result;
+    TimeOfDay? result;
 
-    Offset center = (await startPicker(tester, (TimeOfDay time) { result = time; }))!;
+    Offset center = (await startPicker(tester, (TimeOfDay? time) { result = time; }))!;
     await tester.tapAt(Offset(center.dx, center.dy - 50.0)); // 12:00 AM
     await finishPicker(tester);
     expect(result, equals(const TimeOfDay(hour: 0, minute: 0)));
 
-    center = (await startPicker(tester, (TimeOfDay time) { result = time; }))!;
+    center = (await startPicker(tester, (TimeOfDay? time) { result = time; }))!;
     await tester.tapAt(Offset(center.dx + 50.0, center.dy));
     await finishPicker(tester);
     expect(result, equals(const TimeOfDay(hour: 3, minute: 0)));
 
-    center = (await startPicker(tester, (TimeOfDay time) { result = time; }))!;
+    center = (await startPicker(tester, (TimeOfDay? time) { result = time; }))!;
     await tester.tapAt(Offset(center.dx, center.dy + 50.0));
     await finishPicker(tester);
     expect(result, equals(const TimeOfDay(hour: 6, minute: 0)));
 
-    center = (await startPicker(tester, (TimeOfDay time) { result = time; }))!;
+    center = (await startPicker(tester, (TimeOfDay? time) { result = time; }))!;
     await tester.tapAt(Offset(center.dx, center.dy + 50.0));
     await tester.tapAt(Offset(center.dx - 50, center.dy));
     await finishPicker(tester);
@@ -109,7 +109,7 @@
   testWidgets('drag-select an hour', (WidgetTester tester) async {
     late TimeOfDay result;
 
-    final Offset center = (await startPicker(tester, (TimeOfDay time) { result = time; }))!;
+    final Offset center = (await startPicker(tester, (TimeOfDay? time) { result = time!; }))!;
     final Offset hour0 = Offset(center.dx, center.dy - 50.0); // 12:00 AM
     final Offset hour3 = Offset(center.dx + 50.0, center.dy);
     final Offset hour6 = Offset(center.dx, center.dy + 50.0);
@@ -123,21 +123,21 @@
     await finishPicker(tester);
     expect(result.hour, 0);
 
-    expect(await startPicker(tester, (TimeOfDay time) { result = time; }), equals(center));
+    expect(await startPicker(tester, (TimeOfDay? time) { result = time!; }), equals(center));
     gesture = await tester.startGesture(hour0);
     await gesture.moveBy(hour3 - hour0);
     await gesture.up();
     await finishPicker(tester);
     expect(result.hour, 3);
 
-    expect(await startPicker(tester, (TimeOfDay time) { result = time; }), equals(center));
+    expect(await startPicker(tester, (TimeOfDay? time) { result = time!; }), equals(center));
     gesture = await tester.startGesture(hour3);
     await gesture.moveBy(hour6 - hour3);
     await gesture.up();
     await finishPicker(tester);
     expect(result.hour, equals(6));
 
-    expect(await startPicker(tester, (TimeOfDay time) { result = time; }), equals(center));
+    expect(await startPicker(tester, (TimeOfDay? time) { result = time!; }), equals(center));
     gesture = await tester.startGesture(hour6);
     await gesture.moveBy(hour9 - hour6);
     await gesture.up();
@@ -148,7 +148,7 @@
   testWidgets('tap-select switches from hour to minute', (WidgetTester tester) async {
     late TimeOfDay result;
 
-    final Offset center = (await startPicker(tester, (TimeOfDay time) { result = time; }))!;
+    final Offset center = (await startPicker(tester, (TimeOfDay? time) { result = time!; }))!;
     final Offset hour6 = Offset(center.dx, center.dy + 50.0); // 6:00
     final Offset min45 = Offset(center.dx - 50.0, center.dy); // 45 mins (or 9:00 hours)
 
@@ -162,7 +162,7 @@
   testWidgets('drag-select switches from hour to minute', (WidgetTester tester) async {
     late TimeOfDay result;
 
-    final Offset center = (await startPicker(tester, (TimeOfDay time) { result = time; }))!;
+    final Offset center = (await startPicker(tester, (TimeOfDay? time) { result = time!; }))!;
     final Offset hour3 = Offset(center.dx + 50.0, center.dy);
     final Offset hour6 = Offset(center.dx, center.dy + 50.0);
     final Offset hour9 = Offset(center.dx - 50.0, center.dy);
@@ -181,7 +181,7 @@
   testWidgets('tap-select rounds down to nearest 5 minute increment', (WidgetTester tester) async {
     late TimeOfDay result;
 
-    final Offset center = (await startPicker(tester, (TimeOfDay time) { result = time; }))!;
+    final Offset center = (await startPicker(tester, (TimeOfDay? time) { result = time!; }))!;
     final Offset hour6 = Offset(center.dx, center.dy + 50.0); // 6:00
     final Offset min46 = Offset(center.dx - 50.0, center.dy - 5); // 46 mins
 
@@ -195,7 +195,7 @@
   testWidgets('tap-select rounds up to nearest 5 minute increment', (WidgetTester tester) async {
     late TimeOfDay result;
 
-    final Offset center = (await startPicker(tester, (TimeOfDay time) { result = time; }))!;
+    final Offset center = (await startPicker(tester, (TimeOfDay? time) { result = time!; }))!;
     final Offset hour6 = Offset(center.dx, center.dy + 50.0); // 6:00
     final Offset min48 = Offset(center.dx - 50.0, center.dy - 15); // 48 mins
 
@@ -220,14 +220,14 @@
     });
 
     testWidgets('tap-select vibrates once', (WidgetTester tester) async {
-      final Offset center = (await startPicker(tester, (TimeOfDay time) { }))!;
+      final Offset center = (await startPicker(tester, (TimeOfDay? time) { }))!;
       await tester.tapAt(Offset(center.dx, center.dy - 50.0));
       await finishPicker(tester);
       expect(feedback.hapticCount, 1);
     });
 
     testWidgets('quick successive tap-selects vibrate once', (WidgetTester tester) async {
-      final Offset center = (await startPicker(tester, (TimeOfDay time) { }))!;
+      final Offset center = (await startPicker(tester, (TimeOfDay? time) { }))!;
       await tester.tapAt(Offset(center.dx, center.dy - 50.0));
       await tester.pump(kFastFeedbackInterval);
       await tester.tapAt(Offset(center.dx, center.dy + 50.0));
@@ -236,7 +236,7 @@
     });
 
     testWidgets('slow successive tap-selects vibrate once per tap', (WidgetTester tester) async {
-      final Offset center = (await startPicker(tester, (TimeOfDay time) { }))!;
+      final Offset center = (await startPicker(tester, (TimeOfDay? time) { }))!;
       await tester.tapAt(Offset(center.dx, center.dy - 50.0));
       await tester.pump(kSlowFeedbackInterval);
       await tester.tapAt(Offset(center.dx, center.dy + 50.0));
@@ -247,7 +247,7 @@
     });
 
     testWidgets('drag-select vibrates once', (WidgetTester tester) async {
-      final Offset center = (await startPicker(tester, (TimeOfDay time) { }))!;
+      final Offset center = (await startPicker(tester, (TimeOfDay? time) { }))!;
       final Offset hour0 = Offset(center.dx, center.dy - 50.0);
       final Offset hour3 = Offset(center.dx + 50.0, center.dy);
 
@@ -259,7 +259,7 @@
     });
 
     testWidgets('quick drag-select vibrates once', (WidgetTester tester) async {
-      final Offset center = (await startPicker(tester, (TimeOfDay time) { }))!;
+      final Offset center = (await startPicker(tester, (TimeOfDay? time) { }))!;
       final Offset hour0 = Offset(center.dx, center.dy - 50.0);
       final Offset hour3 = Offset(center.dx + 50.0, center.dy);
 
@@ -275,7 +275,7 @@
     });
 
     testWidgets('slow drag-select vibrates once', (WidgetTester tester) async {
-      final Offset center = (await startPicker(tester, (TimeOfDay time) { }))!;
+      final Offset center = (await startPicker(tester, (TimeOfDay? time) { }))!;
       final Offset hour0 = Offset(center.dx, center.dy - 50.0);
       final Offset hour3 = Offset(center.dx + 50.0, center.dy);
 
@@ -794,7 +794,7 @@
 
   testWidgets('Initial time is the default', (WidgetTester tester) async {
     late TimeOfDay result;
-    await startPicker(tester, (TimeOfDay time) { result = time; }, entryMode: TimePickerEntryMode.input);
+    await startPicker(tester, (TimeOfDay? time) { result = time!; }, entryMode: TimePickerEntryMode.input);
     await finishPicker(tester);
     expect(result, equals(const TimeOfDay(hour: 7, minute: 0)));
   });
@@ -898,7 +898,7 @@
 
   testWidgets('Entered text returns time', (WidgetTester tester) async {
     late TimeOfDay result;
-    await startPicker(tester, (TimeOfDay time) { result = time; }, entryMode: TimePickerEntryMode.input);
+    await startPicker(tester, (TimeOfDay? time) { result = time!; }, entryMode: TimePickerEntryMode.input);
     await tester.enterText(find.byType(TextField).first, '9');
     await tester.enterText(find.byType(TextField).last, '12');
     await finishPicker(tester);
@@ -907,7 +907,7 @@
 
   testWidgets('Toggle to dial mode keeps selected time', (WidgetTester tester) async {
     late TimeOfDay result;
-    await startPicker(tester, (TimeOfDay time) { result = time; }, entryMode: TimePickerEntryMode.input);
+    await startPicker(tester, (TimeOfDay? time) { result = time!; }, entryMode: TimePickerEntryMode.input);
     await tester.enterText(find.byType(TextField).first, '8');
     await tester.enterText(find.byType(TextField).last, '15');
     await tester.tap(find.byIcon(Icons.access_time));
@@ -917,7 +917,7 @@
 
   testWidgets('Invalid text prevents dismissing', (WidgetTester tester) async {
     TimeOfDay? result;
-    await startPicker(tester, (TimeOfDay time) { result = time; }, entryMode: TimePickerEntryMode.input);
+    await startPicker(tester, (TimeOfDay? time) { result = time; }, entryMode: TimePickerEntryMode.input);
 
     // Invalid hour.
     await tester.enterText(find.byType(TextField).first, '88');
@@ -939,7 +939,7 @@
 
   // Fixes regression that was reverted in https://github.com/flutter/flutter/pull/64094#pullrequestreview-469836378.
   testWidgets('Ensure hour/minute fields are top-aligned with the separator', (WidgetTester tester) async {
-    await startPicker(tester, (TimeOfDay time) { }, entryMode: TimePickerEntryMode.input);
+    await startPicker(tester, (TimeOfDay? time) { }, entryMode: TimePickerEntryMode.input);
     final double hourFieldTop = tester.getTopLeft(find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_HourTextField')).dy;
     final double minuteFieldTop = tester.getTopLeft(find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_MinuteTextField')).dy;
     final double separatorTop = tester.getTopLeft(find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_StringFragment')).dy;
diff --git a/packages/flutter/test/material/will_pop_test.dart b/packages/flutter/test/material/will_pop_test.dart
index c1c05a1..9ff3c72 100644
--- a/packages/flutter/test/material/will_pop_test.dart
+++ b/packages/flutter/test/material/will_pop_test.dart
@@ -222,8 +222,8 @@
   });
 
   testWidgets('Form.willPop callbacks do not accumulate', (WidgetTester tester) async {
-    Future<bool> showYesNoAlert(BuildContext context) {
-      return showDialog<bool>(
+    Future<bool> showYesNoAlert(BuildContext context) async {
+      return (await showDialog<bool>(
         context: context,
         builder: (BuildContext context) {
           return AlertDialog(
@@ -239,7 +239,7 @@
             ],
           );
         },
-      );
+      ))!;
     }
 
     Widget buildFrame() {
diff --git a/packages/flutter/test/rendering/editable_test.dart b/packages/flutter/test/rendering/editable_test.dart
index 61f1a01..f0da937 100644
--- a/packages/flutter/test/rendering/editable_test.dart
+++ b/packages/flutter/test/rendering/editable_test.dart
@@ -935,6 +935,68 @@
     expect(delegate.textEditingValue.text, '');
   }, skip: isBrowser); // https://github.com/flutter/flutter/issues/61021
 
+  test('arrow keys with selection text', () async {
+    final TextSelectionDelegate delegate = FakeEditableTextState();
+    final ViewportOffset viewportOffset = ViewportOffset.zero();
+    late TextSelection currentSelection;
+    final RenderEditable editable = RenderEditable(
+      backgroundCursorColor: Colors.grey,
+      selectionColor: Colors.black,
+      textDirection: TextDirection.ltr,
+      cursorColor: Colors.red,
+      offset: viewportOffset,
+      textSelectionDelegate: delegate,
+      onSelectionChanged: (TextSelection selection, RenderEditable renderObject, SelectionChangedCause cause) {
+        currentSelection = selection;
+      },
+      startHandleLayerLink: LayerLink(),
+      endHandleLayerLink: LayerLink(),
+      text: const TextSpan(
+        text: '012345',  // Thumbs up
+        style: TextStyle(height: 1.0, fontSize: 10.0, fontFamily: 'Ahem'),
+      ),
+      selection: const TextSelection.collapsed(
+        offset: 0,
+      ),
+    );
+
+    layout(editable);
+    editable.hasFocus = true;
+
+    editable.selection = const TextSelection(baseOffset: 2, extentOffset: 4);
+    pumpFrame();
+
+    await simulateKeyDownEvent(LogicalKeyboardKey.arrowRight);
+    await simulateKeyUpEvent(LogicalKeyboardKey.arrowRight);
+    expect(currentSelection.isCollapsed, true);
+    expect(currentSelection.baseOffset, 4);
+
+    editable.selection = const TextSelection(baseOffset: 4, extentOffset: 2);
+    pumpFrame();
+
+    await simulateKeyDownEvent(LogicalKeyboardKey.arrowRight);
+    await simulateKeyUpEvent(LogicalKeyboardKey.arrowRight);
+    expect(currentSelection.isCollapsed, true);
+    expect(currentSelection.baseOffset, 4);
+
+    editable.selection = const TextSelection(baseOffset: 2, extentOffset: 4);
+    pumpFrame();
+
+    await simulateKeyDownEvent(LogicalKeyboardKey.arrowLeft);
+    await simulateKeyUpEvent(LogicalKeyboardKey.arrowLeft);
+    expect(currentSelection.isCollapsed, true);
+    expect(currentSelection.baseOffset, 2);
+
+    editable.selection = const TextSelection(baseOffset: 4, extentOffset: 2);
+    pumpFrame();
+
+    await simulateKeyDownEvent(LogicalKeyboardKey.arrowLeft);
+    await simulateKeyUpEvent(LogicalKeyboardKey.arrowLeft);
+    expect(currentSelection.isCollapsed, true);
+    expect(currentSelection.baseOffset, 2);
+
+  }, skip: isBrowser); // https://github.com/flutter/flutter/issues/58068
+
   test('getEndpointsForSelection handles empty characters', () {
     final TextSelectionDelegate delegate = FakeEditableTextState();
     final RenderEditable editable = RenderEditable(
diff --git a/packages/flutter/test/rendering/mock_canvas.dart b/packages/flutter/test/rendering/mock_canvas.dart
index dceae4b..c5165c7 100644
--- a/packages/flutter/test/rendering/mock_canvas.dart
+++ b/packages/flutter/test/rendering/mock_canvas.dart
@@ -823,7 +823,6 @@
       while (predicate.moveNext()) {
         predicate.current.match(call);
       }
-      assert(predicate.current == null);
       // We allow painting more than expected.
     } on _MismatchedCall catch (data) {
       description.writeln(data.message);
diff --git a/packages/flutter/test/services/fake_platform_views.dart b/packages/flutter/test/services/fake_platform_views.dart
index c105787..e91c93d 100644
--- a/packages/flutter/test/services/fake_platform_views.dart
+++ b/packages/flutter/test/services/fake_platform_views.dart
@@ -174,11 +174,11 @@
     final Map<dynamic, dynamic> args = call.arguments as Map<dynamic, dynamic>;
     final int id = args['id'] as int;
     final String viewType = args['viewType'] as String;
-    final double width = args['width'] as double;
-    final double height = args['height'] as double;
+    final double? width = args['width'] as double?;
+    final double? height = args['height'] as double?;
     final int layoutDirection = args['direction'] as int;
-    final bool hybrid = args['hybrid'] as bool;
-    final Uint8List creationParams = args['params'] as Uint8List;
+    final bool? hybrid = args['hybrid'] as bool?;
+    final Uint8List? creationParams = args['params'] as Uint8List?;
 
     if (_views.containsKey(id))
       throw PlatformException(
@@ -197,7 +197,7 @@
     }
 
     _views[id] = FakeAndroidPlatformView(id, viewType,
-        width != null || height != null ? Size(width, height) : null,
+        width != null && height != null ? Size(width, height) : null,
         layoutDirection,
         hybrid,
         creationParams,
@@ -344,7 +344,7 @@
     final Map<dynamic, dynamic> args = call.arguments as Map<dynamic, dynamic>;
     final int id = args['id'] as int;
     final String viewType = args['viewType'] as String;
-    final Uint8List creationParams = args['params'] as Uint8List;
+    final Uint8List? creationParams = args['params'] as Uint8List?;
 
     if (_views.containsKey(id)) {
       throw PlatformException(
diff --git a/packages/flutter/test/services/platform_views_test.dart b/packages/flutter/test/services/platform_views_test.dart
index 1db8c6a..d438692 100644
--- a/packages/flutter/test/services/platform_views_test.dart
+++ b/packages/flutter/test/services/platform_views_test.dart
@@ -2,7 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-
 import 'package:flutter/painting.dart';
 import 'package:flutter/services.dart';
 import 'package:flutter_test/flutter_test.dart' show TestWidgetsFlutterBinding;
diff --git a/packages/flutter/test/services/raw_keyboard_test.dart b/packages/flutter/test/services/raw_keyboard_test.dart
index 87fe0ed..535ba24 100644
--- a/packages/flutter/test/services/raw_keyboard_test.dart
+++ b/packages/flutter/test/services/raw_keyboard_test.dart
@@ -756,7 +756,7 @@
         Focus(
           focusNode: focusNode,
           onKey: (FocusNode node, RawKeyEvent event) {
-            return true; // handle all events.
+            return KeyEventResult.handled; // handle all events.
           },
           child: const SizedBox(),
         ),
diff --git a/packages/flutter/test/widgets/actions_test.dart b/packages/flutter/test/widgets/actions_test.dart
index 5c239f0..6a07c9a 100644
--- a/packages/flutter/test/widgets/actions_test.dart
+++ b/packages/flutter/test/widgets/actions_test.dart
@@ -264,10 +264,7 @@
       );
 
       await tester.pump();
-      final ActionDispatcher dispatcher = Actions.of(
-        containerKey.currentContext!,
-        nullOk: true,
-      );
+      final ActionDispatcher dispatcher = Actions.of(containerKey.currentContext!);
       expect(dispatcher, equals(testDispatcher));
     });
     testWidgets('Action can be found with find', (WidgetTester tester) async {
diff --git a/packages/flutter/test/widgets/custom_paint_test.dart b/packages/flutter/test/widgets/custom_paint_test.dart
index 24e2d3a..68013a3 100644
--- a/packages/flutter/test/widgets/custom_paint_test.dart
+++ b/packages/flutter/test/widgets/custom_paint_test.dart
@@ -58,7 +58,7 @@
 
 void main() {
   testWidgets('Control test for custom painting', (WidgetTester tester) async {
-    final List<String> log = <String>[];
+    final List<String?> log = <String?>[];
     await tester.pumpWidget(CustomPaint(
       painter: TestCustomPainter(
         log: log,
@@ -82,7 +82,7 @@
   testWidgets('Throws FlutterError on custom painter incorrect restore/save calls', (
       WidgetTester tester) async {
     final GlobalKey target = GlobalKey();
-    final List<String> log = <String>[];
+    final List<String?> log = <String?>[];
     await tester.pumpWidget(CustomPaint(
       key: target,
       isComplex: true,
@@ -174,7 +174,7 @@
   testWidgets('Raster cache hints', (WidgetTester tester) async {
     final GlobalKey target = GlobalKey();
 
-    final List<String> log = <String>[];
+    final List<String?> log = <String?>[];
     await tester.pumpWidget(CustomPaint(
       key: target,
       isComplex: true,
diff --git a/packages/flutter/test/widgets/dismissible_test.dart b/packages/flutter/test/widgets/dismissible_test.dart
index 1589ad9..1ac1732 100644
--- a/packages/flutter/test/widgets/dismissible_test.dart
+++ b/packages/flutter/test/widgets/dismissible_test.dart
@@ -18,7 +18,7 @@
 Widget buildTest({
   double? startToEndThreshold,
   TextDirection textDirection = TextDirection.ltr,
-  Future<bool> Function(BuildContext context, DismissDirection direction)? confirmDismiss,
+  Future<bool?> Function(BuildContext context, DismissDirection direction)? confirmDismiss,
 }) {
   return Directionality(
     textDirection: textDirection,
@@ -679,7 +679,7 @@
       return buildTest(
         confirmDismiss: (BuildContext context, DismissDirection dismissDirection) {
           confirmDismissDirection = dismissDirection;
-          return Future<bool>.value(confirmDismissValue);
+          return Future<bool?>.value(confirmDismissValue);
         }
       );
     }
diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart
index 89865be..9c7b2f1 100644
--- a/packages/flutter/test/widgets/editable_text_test.dart
+++ b/packages/flutter/test/widgets/editable_text_test.dart
@@ -3989,7 +3989,6 @@
       reason: 'on $platform',
     );
 
-    // Move forward one character to reset the selection.
     await sendKeys(
       tester,
       <LogicalKeyboardKey>[
@@ -4002,8 +4001,8 @@
       selection,
       equals(
         const TextSelection(
-          baseOffset: 21,
-          extentOffset: 21,
+          baseOffset: 20,
+          extentOffset: 20,
           affinity: TextAffinity.downstream,
         ),
       ),
@@ -4024,8 +4023,8 @@
       selection,
       equals(
         const TextSelection(
-          baseOffset: 21,
-          extentOffset: 40,
+          baseOffset: 20,
+          extentOffset: 39,
           affinity: TextAffinity.downstream,
         ),
       ),
@@ -4049,7 +4048,7 @@
       selection,
       equals(
         const TextSelection(
-          baseOffset: 21,
+          baseOffset: 20,
           extentOffset: testText.length,
           affinity: TextAffinity.downstream,
         ),
@@ -4071,8 +4070,8 @@
       selection,
       equals(
         const TextSelection(
-          baseOffset: 21,
-          extentOffset: 58,
+          baseOffset: 20,
+          extentOffset: 57,
           affinity: TextAffinity.downstream,
         ),
       ),
@@ -4094,7 +4093,7 @@
       selection,
       equals(
         const TextSelection(
-          baseOffset: 21,
+          baseOffset: 20,
           extentOffset: 72,
           affinity: TextAffinity.downstream,
         ),
@@ -4117,7 +4116,7 @@
       selection,
       equals(
         const TextSelection(
-          baseOffset: 21,
+          baseOffset: 20,
           extentOffset: 55,
           affinity: TextAffinity.downstream,
         ),
diff --git a/packages/flutter/test/widgets/focus_manager_test.dart b/packages/flutter/test/widgets/focus_manager_test.dart
index 0cc11a0..d24b195 100644
--- a/packages/flutter/test/widgets/focus_manager_test.dart
+++ b/packages/flutter/test/widgets/focus_manager_test.dart
@@ -852,12 +852,12 @@
     testWidgets('Key handling bubbles up and terminates when handled.', (WidgetTester tester) async {
       final Set<FocusNode> receivedAnEvent = <FocusNode>{};
       final Set<FocusNode> shouldHandle = <FocusNode>{};
-      bool handleEvent(FocusNode node, RawKeyEvent event) {
+      KeyEventResult handleEvent(FocusNode node, RawKeyEvent event) {
         if (shouldHandle.contains(node)) {
           receivedAnEvent.add(node);
-          return true;
+          return KeyEventResult.handled;
         }
-        return false;
+        return KeyEventResult.ignored;
       }
 
       Future<void> sendEvent() async {
diff --git a/packages/flutter/test/widgets/image_test.dart b/packages/flutter/test/widgets/image_test.dart
index b45b963..1046e02 100644
--- a/packages/flutter/test/widgets/image_test.dart
+++ b/packages/flutter/test/widgets/image_test.dart
@@ -1516,7 +1516,7 @@
     expect(imageCache!.liveImageCount, 1);
     expect(imageCache!.containsKey(provider), false);
 
-    final ImageCacheStatus providerLocation = await provider.obtainCacheStatus(configuration: ImageConfiguration.empty);
+    final ImageCacheStatus providerLocation = (await provider.obtainCacheStatus(configuration: ImageConfiguration.empty))!;
 
     expect(providerLocation, isNotNull);
     expect(providerLocation.live, true);
diff --git a/packages/flutter/test/widgets/navigator_test.dart b/packages/flutter/test/widgets/navigator_test.dart
index d0de31e..a08d14b 100644
--- a/packages/flutter/test/widgets/navigator_test.dart
+++ b/packages/flutter/test/widgets/navigator_test.dart
@@ -957,7 +957,7 @@
   });
 
   testWidgets('replaceNamed returned value', (WidgetTester tester) async {
-    late Future<String> value;
+    late Future<String?> value;
 
     final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
       '/' : (BuildContext context) => OnTapPage(id: '/', onTap: () { Navigator.pushNamed(context, '/A'); }),
@@ -1001,7 +1001,7 @@
     expect(find.text('A'), findsNothing);
     expect(find.text('B'), findsNothing);
 
-    final String replaceNamedValue = await value; // replaceNamed result was 'B'
+    final String? replaceNamedValue = await value; // replaceNamed result was 'B'
     expect(replaceNamedValue, 'B');
   });
 
@@ -1092,7 +1092,7 @@
   });
 
   testWidgets('remove a route whose value is awaited', (WidgetTester tester) async {
-    late Future<String> pageValue;
+    late Future<String?> pageValue;
     final Map<String, WidgetBuilder> pageBuilders = <String, WidgetBuilder>{
       '/':  (BuildContext context) => OnTapPage(id: '/', onTap: () { pageValue = Navigator.pushNamed(context, '/A'); }),
       '/A': (BuildContext context) => OnTapPage(id: 'A', onTap: () { Navigator.pop(context, 'A'); }),
@@ -1113,7 +1113,7 @@
 
     await tester.tap(find.text('/')); // pushNamed('/A'), stack becomes /, /A
     await tester.pumpAndSettle();
-    pageValue.then((String value) { assert(false); });
+    pageValue.then((String? value) { assert(false); });
 
     final NavigatorState navigator = tester.state<NavigatorState>(find.byType(Navigator));
     navigator.removeRoute(routes['/A']!); // stack becomes /, pageValue will not complete
diff --git a/packages/flutter/test/widgets/restorable_property_test.dart b/packages/flutter/test/widgets/restorable_property_test.dart
index fa63962..85a10de 100644
--- a/packages/flutter/test/widgets/restorable_property_test.dart
+++ b/packages/flutter/test/widgets/restorable_property_test.dart
@@ -285,7 +285,7 @@
   });
 }
 
-class _TestRestorableValue extends RestorableValue<Object> {
+class _TestRestorableValue extends RestorableValue<Object?> {
   @override
   Object createDefaultValue() {
     return 55;
@@ -300,12 +300,12 @@
   }
 
   @override
-  Object fromPrimitives(Object data) {
+  Object? fromPrimitives(Object? data) {
     return data;
   }
 
   @override
-  Object toPrimitives() {
+  Object? toPrimitives() {
     return value;
   }
 }
diff --git a/packages/flutter/test/widgets/restoration_mixin_test.dart b/packages/flutter/test/widgets/restoration_mixin_test.dart
index 8e0ff5d..f44348d 100644
--- a/packages/flutter/test/widgets/restoration_mixin_test.dart
+++ b/packages/flutter/test/widgets/restoration_mixin_test.dart
@@ -632,7 +632,7 @@
   });
 
   test('RestorableProperty throws after disposed', () {
-    final RestorableProperty<Object> property = _TestRestorableProperty(10);
+    final RestorableProperty<Object?> property = _TestRestorableProperty(10);
     property.dispose();
     expect(() => property.dispose(), throwsFlutterError);
   });
@@ -660,8 +660,8 @@
   _TestRestorableProperty? additionalProperty;
   bool _rerigisterAdditionalProperty = false;
 
-  final List<RestorationBucket?> restoreStateLog = <RestorationBucket>[];
-  final List<RestorationBucket?> toggleBucketLog = <RestorationBucket>[];
+  final List<RestorationBucket?> restoreStateLog = <RestorationBucket?>[];
+  final List<RestorationBucket?> toggleBucketLog = <RestorationBucket?>[];
 
   @override
   void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
@@ -737,7 +737,7 @@
   };
 }
 
-class _TestRestorableProperty extends RestorableProperty<Object> {
+class _TestRestorableProperty extends RestorableProperty<Object?> {
   _TestRestorableProperty(this._value);
 
   List<String> log = <String>[];
@@ -751,35 +751,35 @@
   }
 
   @override
-  Object createDefaultValue() {
+  Object? createDefaultValue() {
     log.add('createDefaultValue');
     return _value;
   }
 
   @override
-  Object fromPrimitives(Object data) {
+  Object? fromPrimitives(Object? data) {
     log.add('fromPrimitives');
     return data;
   }
 
-  Object get value {
+  Object? get value {
     assert(isRegistered);
     return _value;
   }
-  Object _value;
-  set value(Object value) {
+  Object? _value;
+  set value(Object? value) {
     _value = value;
     notifyListeners();
   }
 
   @override
-  void initWithValue(Object v) {
+  void initWithValue(Object? v) {
     log.add('initWithValue');
     _value = v;
   }
 
   @override
-  Object toPrimitives() {
+  Object? toPrimitives() {
     log.add('toPrimitives');
     return _value;
   }
diff --git a/packages/flutter/test/widgets/restoration_scopes_moving_test.dart b/packages/flutter/test/widgets/restoration_scopes_moving_test.dart
index 3c07df3..cac9033 100644
--- a/packages/flutter/test/widgets/restoration_scopes_moving_test.dart
+++ b/packages/flutter/test/widgets/restoration_scopes_moving_test.dart
@@ -209,7 +209,7 @@
 }
 
 class TestWidgetState extends State<TestWidget> with RestorationMixin {
-  List<RestorationBucket?> buckets = <RestorationBucket>[];
+  List<RestorationBucket?> buckets = <RestorationBucket?>[];
   List<bool> flags = <bool>[];
 
   @override
diff --git a/packages/flutter/test/widgets/root_restoration_scope_test.dart b/packages/flutter/test/widgets/root_restoration_scope_test.dart
index 85d57ce..4a09020 100644
--- a/packages/flutter/test/widgets/root_restoration_scope_test.dart
+++ b/packages/flutter/test/widgets/root_restoration_scope_test.dart
@@ -302,7 +302,7 @@
   });
 
   testWidgets('injects null when rootBucket is null', (WidgetTester tester) async {
-    final Completer<RestorationBucket> completer = Completer<RestorationBucket>();
+    final Completer<RestorationBucket?> completer = Completer<RestorationBucket?>();
     binding.restorationManager.rootBucket = completer.future;
 
     await tester.pumpWidget(
diff --git a/packages/flutter/test/widgets/routes_test.dart b/packages/flutter/test/widgets/routes_test.dart
index 2fd08a3..b5fdfb8 100644
--- a/packages/flutter/test/widgets/routes_test.dart
+++ b/packages/flutter/test/widgets/routes_test.dart
@@ -1319,28 +1319,28 @@
       final Finder animatedModalBarrier = find.byType(AnimatedModalBarrier);
       expect(animatedModalBarrier, findsOneWidget);
 
-      Animation<Color> modalBarrierAnimation;
+      Animation<Color?> modalBarrierAnimation;
       modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color;
       expect(modalBarrierAnimation.value, Colors.transparent);
 
       await tester.pump(const Duration(milliseconds: 25));
       modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color;
       expect(
-        modalBarrierAnimation.value.alpha,
+        modalBarrierAnimation.value!.alpha,
         closeTo(_getExpectedBarrierTweenAlphaValue(0.25), 1),
       );
 
       await tester.pump(const Duration(milliseconds: 25));
       modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color;
       expect(
-        modalBarrierAnimation.value.alpha,
+        modalBarrierAnimation.value!.alpha,
         closeTo(_getExpectedBarrierTweenAlphaValue(0.50), 1),
       );
 
       await tester.pump(const Duration(milliseconds: 25));
       modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color;
       expect(
-        modalBarrierAnimation.value.alpha,
+        modalBarrierAnimation.value!.alpha,
         closeTo(_getExpectedBarrierTweenAlphaValue(0.75), 1),
       );
 
@@ -1382,28 +1382,28 @@
       final Finder animatedModalBarrier = find.byType(AnimatedModalBarrier);
       expect(animatedModalBarrier, findsOneWidget);
 
-      Animation<Color> modalBarrierAnimation;
+      Animation<Color?> modalBarrierAnimation;
       modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color;
       expect(modalBarrierAnimation.value, Colors.transparent);
 
       await tester.pump(const Duration(milliseconds: 25));
       modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color;
       expect(
-        modalBarrierAnimation.value.alpha,
+        modalBarrierAnimation.value!.alpha,
         closeTo(_getExpectedBarrierTweenAlphaValue(0.25), 1),
       );
 
       await tester.pump(const Duration(milliseconds: 25));
       modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color;
       expect(
-        modalBarrierAnimation.value.alpha,
+        modalBarrierAnimation.value!.alpha,
         closeTo(_getExpectedBarrierTweenAlphaValue(0.50), 1),
       );
 
       await tester.pump(const Duration(milliseconds: 25));
       modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color;
       expect(
-        modalBarrierAnimation.value.alpha,
+        modalBarrierAnimation.value!.alpha,
         closeTo(_getExpectedBarrierTweenAlphaValue(0.75), 1),
       );
 
diff --git a/packages/flutter/test/widgets/shortcuts_test.dart b/packages/flutter/test/widgets/shortcuts_test.dart
index 05cc15e..595dcda 100644
--- a/packages/flutter/test/widgets/shortcuts_test.dart
+++ b/packages/flutter/test/widgets/shortcuts_test.dart
@@ -28,7 +28,7 @@
   @override
   Object? invokeAction(Action<TestIntent> action, Intent intent, [BuildContext? context]) {
     final Object? result = super.invokeAction(action, intent, context);
-    postInvoke?.call(action: action, intent: intent, context: context, dispatcher: this);
+    postInvoke?.call(action: action, intent: intent, context: context!, dispatcher: this);
     return result;
   }
 }
@@ -43,8 +43,10 @@
   List<LogicalKeyboardKey> keys;
 
   @override
-  bool handleKeypress(BuildContext context, RawKeyEvent event, {LogicalKeySet? keysPressed}) {
-    keys.add(event.logicalKey);
+  KeyEventResult handleKeypress(BuildContext context, RawKeyEvent event, {LogicalKeySet? keysPressed}) {
+    if (event is RawKeyDownEvent) {
+      keys.add(event.logicalKey);
+    }
     return super.handleKeypress(context, event, keysPressed: keysPressed);
   }
 }
@@ -320,6 +322,142 @@
       expect(invoked, isFalse);
       expect(pressedKeys, isEmpty);
     });
+    testWidgets("Shortcuts that aren't bound to an action don't absorb keys meant for text fields", (WidgetTester tester) async {
+      final GlobalKey textFieldKey = GlobalKey();
+      final List<LogicalKeyboardKey> pressedKeys = <LogicalKeyboardKey>[];
+      final TestShortcutManager testManager = TestShortcutManager(pressedKeys);
+      await tester.pumpWidget(
+        MaterialApp(
+          home: Material(
+            child: Shortcuts(
+              manager: testManager,
+              shortcuts: <LogicalKeySet, Intent>{
+                LogicalKeySet(LogicalKeyboardKey.keyA): const TestIntent(),
+              },
+              child: TextField(key: textFieldKey, autofocus: true),
+            ),
+          ),
+        ),
+      );
+      await tester.pump();
+      expect(Shortcuts.of(textFieldKey.currentContext!), isNotNull);
+      final bool handled = await tester.sendKeyEvent(LogicalKeyboardKey.keyA);
+      expect(handled, isFalse);
+      expect(pressedKeys, equals(<LogicalKeyboardKey>[LogicalKeyboardKey.keyA]));
+    });
+    testWidgets('Shortcuts that are bound to an action do override text fields', (WidgetTester tester) async {
+      final GlobalKey textFieldKey = GlobalKey();
+      final List<LogicalKeyboardKey> pressedKeys = <LogicalKeyboardKey>[];
+      final TestShortcutManager testManager = TestShortcutManager(pressedKeys);
+      bool invoked = false;
+      await tester.pumpWidget(
+        MaterialApp(
+          home: Material(
+            child: Shortcuts(
+              manager: testManager,
+              shortcuts: <LogicalKeySet, Intent>{
+                LogicalKeySet(LogicalKeyboardKey.keyA): const TestIntent(),
+              },
+              child: Actions(
+                actions: <Type, Action<Intent>>{
+                  TestIntent: TestAction(
+                    onInvoke: (Intent intent) {
+                      invoked = true;
+                      return invoked;
+                    },
+                  ),
+                },
+                child: TextField(key: textFieldKey, autofocus: true),
+              ),
+            ),
+          ),
+        ),
+      );
+      await tester.pump();
+      expect(Shortcuts.of(textFieldKey.currentContext!), isNotNull);
+      final bool result = await tester.sendKeyEvent(LogicalKeyboardKey.keyA);
+      expect(result, isTrue);
+      expect(pressedKeys, equals(<LogicalKeyboardKey>[LogicalKeyboardKey.keyA]));
+      expect(invoked, isTrue);
+    });
+    testWidgets('Shortcuts can override intents that apply to text fields', (WidgetTester tester) async {
+      final GlobalKey textFieldKey = GlobalKey();
+      final List<LogicalKeyboardKey> pressedKeys = <LogicalKeyboardKey>[];
+      final TestShortcutManager testManager = TestShortcutManager(pressedKeys);
+      bool invoked = false;
+      await tester.pumpWidget(
+        MaterialApp(
+          home: Material(
+            child: Shortcuts(
+              manager: testManager,
+              shortcuts: <LogicalKeySet, Intent>{
+                LogicalKeySet(LogicalKeyboardKey.keyA): const TestIntent(),
+              },
+              child: Actions(
+                actions: <Type, Action<Intent>>{
+                  TestIntent: TestAction(
+                    onInvoke: (Intent intent) {
+                      invoked = true;
+                      return invoked;
+                    },
+                  ),
+                },
+                child: Actions(
+                  actions: <Type, Action<Intent>>{
+                    TestIntent: DoNothingAction(consumesKey: false),
+                  },
+                  child: TextField(key: textFieldKey, autofocus: true),
+                ),
+              ),
+            ),
+          ),
+        ),
+      );
+      await tester.pump();
+      expect(Shortcuts.of(textFieldKey.currentContext!), isNotNull);
+      final bool result = await tester.sendKeyEvent(LogicalKeyboardKey.keyA);
+      expect(result, isFalse);
+      expect(invoked, isFalse);
+    });
+    testWidgets('Shortcuts can override intents that apply to text fields with DoNothingAndStopPropagationIntent', (WidgetTester tester) async {
+      final GlobalKey textFieldKey = GlobalKey();
+      final List<LogicalKeyboardKey> pressedKeys = <LogicalKeyboardKey>[];
+      final TestShortcutManager testManager = TestShortcutManager(pressedKeys);
+      bool invoked = false;
+      await tester.pumpWidget(
+        MaterialApp(
+          home: Material(
+            child: Shortcuts(
+              manager: testManager,
+              shortcuts: <LogicalKeySet, Intent>{
+                LogicalKeySet(LogicalKeyboardKey.keyA): const TestIntent(),
+              },
+              child: Actions(
+                actions: <Type, Action<Intent>>{
+                  TestIntent: TestAction(
+                    onInvoke: (Intent intent) {
+                      invoked = true;
+                      return invoked;
+                    },
+                  ),
+                },
+                child: Shortcuts(
+                  shortcuts: <LogicalKeySet, Intent>{
+                    LogicalKeySet(LogicalKeyboardKey.keyA): DoNothingAndStopPropagationIntent(),
+                  },
+                  child: TextField(key: textFieldKey, autofocus: true),
+                ),
+              ),
+            ),
+          ),
+        ),
+      );
+      await tester.pump();
+      expect(Shortcuts.of(textFieldKey.currentContext!), isNotNull);
+      final bool result = await tester.sendKeyEvent(LogicalKeyboardKey.keyA);
+      expect(result, isFalse);
+      expect(invoked, isFalse);
+    });
     test('Shortcuts diagnostics work.', () {
       final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
 
diff --git a/packages/flutter/test/widgets/widget_inspector_test.dart b/packages/flutter/test/widgets/widget_inspector_test.dart
index 4e4546e..ed6e282 100644
--- a/packages/flutter/test/widgets/widget_inspector_test.dart
+++ b/packages/flutter/test/widgets/widget_inspector_test.dart
@@ -780,7 +780,7 @@
       final String bId = service.toId(elementB, group)!;
       final Object? jsonList = json.decode(service.getParentChain(bId, group));
       expect(jsonList, isList);
-      final List<Object> chainElements = jsonList! as List<Object>;
+      final List<Object?> chainElements = jsonList! as List<Object?>;
       final List<Element> expectedChain = elementB.debugGetDiagnosticChain().reversed.toList();
       // Sanity check that the chain goes back to the root.
       expect(expectedChain.first, tester.binding.renderViewElement);
@@ -788,15 +788,15 @@
       expect(chainElements.length, equals(expectedChain.length));
       for (int i = 0; i < expectedChain.length; i += 1) {
         expect(chainElements[i], isMap);
-        final Map<String, Object> chainNode = chainElements[i] as Map<String, Object>;
+        final Map<String, Object?> chainNode = chainElements[i]! as Map<String, Object?>;
         final Element element = expectedChain[i];
         expect(chainNode['node'], isMap);
-        final Map<String, Object> jsonNode = chainNode['node']! as Map<String, Object>;
+        final Map<String, Object?> jsonNode = chainNode['node']! as Map<String, Object?>;
         expect(service.toObject(jsonNode['valueId']! as String), equals(element));
         expect(service.toObject(jsonNode['objectId']! as String), isA<DiagnosticsNode>());
 
         expect(chainNode['children'], isList);
-        final List<Object> jsonChildren = chainNode['children']! as List<Object>;
+        final List<Object?> jsonChildren = chainNode['children']! as List<Object?>;
         final List<Element> childrenElements = <Element>[];
         element.visitChildren(childrenElements.add);
         expect(jsonChildren.length, equals(childrenElements.length));
@@ -807,7 +807,7 @@
         }
         for (int j = 0; j < childrenElements.length; j += 1) {
           expect(jsonChildren[j], isMap);
-          final Map<String, Object> childJson = jsonChildren[j] as Map<String, Object>;
+          final Map<String, Object?> childJson = jsonChildren[j]! as Map<String, Object?>;
           expect(service.toObject(childJson['valueId']! as String), equals(childrenElements[j]));
           expect(service.toObject(childJson['objectId']! as String), isA<DiagnosticsNode>());
         }
@@ -819,12 +819,12 @@
       const String group = 'group';
       service.disposeAllGroups();
       final String id = service.toId(diagnostic, group)!;
-      final List<Object> propertiesJson = json.decode(service.getProperties(id, group)) as List<Object>;
+      final List<Object?> propertiesJson = json.decode(service.getProperties(id, group)) as List<Object?>;
       final List<DiagnosticsNode> properties = diagnostic.getProperties();
       expect(properties, isNotEmpty);
       expect(propertiesJson.length, equals(properties.length));
       for (int i = 0; i < propertiesJson.length; ++i) {
-        final Map<String, Object> propertyJson = propertiesJson[i] as Map<String, Object>;
+        final Map<String, Object?> propertyJson = propertiesJson[i]! as Map<String, Object?>;
         expect(service.toObject(propertyJson['valueId'] as String?), equals(properties[i].value));
         expect(service.toObject(propertyJson['objectId']! as String), isA<DiagnosticsNode>());
       }
@@ -848,12 +848,12 @@
       final DiagnosticsNode diagnostic = find.byType(Stack).evaluate().first.toDiagnosticsNode();
       service.disposeAllGroups();
       final String id = service.toId(diagnostic, group)!;
-      final List<Object> propertiesJson = json.decode(service.getChildren(id, group)) as List<Object>;
+      final List<Object?> propertiesJson = json.decode(service.getChildren(id, group)) as List<Object?>;
       final List<DiagnosticsNode> children = diagnostic.getChildren();
       expect(children.length, equals(3));
       expect(propertiesJson.length, equals(children.length));
       for (int i = 0; i < propertiesJson.length; ++i) {
-        final Map<String, Object> propertyJson = propertiesJson[i] as Map<String, Object>;
+        final Map<String, Object?> propertyJson = propertiesJson[i]! as Map<String, Object?>;
         expect(service.toObject(propertyJson['valueId']! as String), equals(children[i].value));
         expect(service.toObject(propertyJson['objectId']! as String), isA<DiagnosticsNode>());
       }
@@ -878,22 +878,22 @@
       service.disposeAllGroups();
       service.setPubRootDirectories(<String>[]);
       service.setSelection(elementA, 'my-group');
-      final Map<String, Object> jsonA = json.decode(service.getSelectedWidget(null, 'my-group')) as Map<String, Object>;
-      final Map<String, Object> creationLocationA = jsonA['creationLocation']! as Map<String, Object>;
+      final Map<String, Object?> jsonA = json.decode(service.getSelectedWidget(null, 'my-group')) as Map<String, Object?>;
+      final Map<String, Object?> creationLocationA = jsonA['creationLocation']! as Map<String, Object?>;
       expect(creationLocationA, isNotNull);
       final String fileA = creationLocationA['file']! as String;
       final int lineA = creationLocationA['line']! as int;
       final int columnA = creationLocationA['column']! as int;
-      final List<Object> parameterLocationsA = creationLocationA['parameterLocations']! as List<Object>;
+      final List<Object?> parameterLocationsA = creationLocationA['parameterLocations']! as List<Object?>;
 
       service.setSelection(elementB, 'my-group');
-      final Map<String, Object> jsonB = json.decode(service.getSelectedWidget(null, 'my-group')) as Map<String, Object>;
-      final Map<String, Object> creationLocationB = jsonB['creationLocation']! as Map<String, Object>;
+      final Map<String, Object?> jsonB = json.decode(service.getSelectedWidget(null, 'my-group')) as Map<String, Object?>;
+      final Map<String, Object?> creationLocationB = jsonB['creationLocation']! as Map<String, Object?>;
       expect(creationLocationB, isNotNull);
       final String fileB = creationLocationB['file']! as String;
       final int lineB = creationLocationB['line']! as int;
       final int columnB = creationLocationB['column']! as int;
-      final List<Object> parameterLocationsB = creationLocationB['parameterLocations']! as List<Object>;
+      final List<Object?> parameterLocationsB = creationLocationB['parameterLocations']! as List<Object?>;
       expect(fileA, endsWith('widget_inspector_test.dart'));
       expect(fileA, equals(fileB));
       // We don't hardcode the actual lines the widgets are created on as that
@@ -903,17 +903,17 @@
       expect(columnA, equals(15));
       expect(columnA, equals(columnB));
       expect(parameterLocationsA.length, equals(1));
-      final Map<String, Object> paramA = parameterLocationsA[0] as Map<String, Object>;
+      final Map<String, Object?> paramA = parameterLocationsA[0]! as Map<String, Object?>;
       expect(paramA['name'], equals('data'));
       expect(paramA['line'], equals(lineA));
       expect(paramA['column'], equals(20));
 
       expect(parameterLocationsB.length, equals(2));
-      final Map<String, Object> paramB1 = parameterLocationsB[0] as Map<String, Object>;
+      final Map<String, Object?> paramB1 = parameterLocationsB[0]! as Map<String, Object?>;
       expect(paramB1['name'], equals('data'));
       expect(paramB1['line'], equals(lineB));
       expect(paramB1['column'], equals(20));
-      final Map<String, Object> paramB2 = parameterLocationsB[1] as Map<String, Object>;
+      final Map<String, Object?> paramB2 = parameterLocationsB[1]! as Map<String, Object?>;
       expect(paramB2['name'], equals('textDirection'));
       expect(paramB2['line'], equals(lineB));
       expect(paramB2['column'], equals(25));
@@ -936,9 +936,9 @@
       final Element elementA = find.text('a').evaluate().first;
       late String pubRootTest;
       if (widgetTracked) {
-        final Map<String, Object> jsonObject = json.decode(
-          service.getSelectedWidget(null, 'my-group')) as Map<String, Object>;
-        final Map<String, Object> creationLocation = jsonObject['creationLocation']! as Map<String, Object>;
+        final Map<String, Object?> jsonObject = json.decode(
+          service.getSelectedWidget(null, 'my-group')) as Map<String, Object?>;
+        final Map<String, Object?> creationLocation = jsonObject['creationLocation']! as Map<String, Object?>;
         expect(creationLocation, isNotNull);
         final String fileA = creationLocation['file']! as String;
         expect(fileA, endsWith('widget_inspector_test.dart'));
@@ -998,9 +998,9 @@
       final Element elementA = find.text('a').evaluate().first;
       late String pubRootTest;
       if (widgetTracked) {
-        final Map<String, Object> jsonObject = json.decode(
-          service.getSelectedWidget(null, 'my-group')) as Map<String, Object>;
-        final Map<String, Object> creationLocation = jsonObject['creationLocation']! as Map<String, Object>;
+        final Map<String, Object?> jsonObject = json.decode(
+          service.getSelectedWidget(null, 'my-group')) as Map<String, Object?>;
+        final Map<String, Object?> creationLocation = jsonObject['creationLocation']! as Map<String, Object?>;
         expect(creationLocation, isNotNull);
         final String fileA = creationLocation['file']! as String;
         expect(fileA, endsWith('widget_inspector_test.dart'));
@@ -1061,8 +1061,8 @@
       service.disposeAllGroups();
       service.setPubRootDirectories(<String>[]);
       service.setSelection(elementA, 'my-group');
-      Map<String, Object> jsonObject = json.decode(service.getSelectedWidget(null, 'my-group')) as Map<String, Object>;
-      Map<String, Object> creationLocation = jsonObject['creationLocation']! as Map<String, Object>;
+      Map<String, Object?> jsonObject = json.decode(service.getSelectedWidget(null, 'my-group')) as Map<String, Object?>;
+      Map<String, Object?> creationLocation = jsonObject['creationLocation']! as Map<String, Object?>;
       expect(creationLocation, isNotNull);
       final String fileA = creationLocation['file']! as String;
       expect(fileA, endsWith('widget_inspector_test.dart'));
@@ -1099,9 +1099,9 @@
       ).evaluate().first;
       service.setSelection(richText, 'my-group');
       service.setPubRootDirectories(<String>[pubRootTest]);
-      jsonObject = json.decode(service.getSelectedWidget(null, 'my-group')) as Map<String, Object>;
+      jsonObject = json.decode(service.getSelectedWidget(null, 'my-group')) as Map<String, Object?>;
       expect(jsonObject, isNot(contains('createdByLocalProject')));
-      creationLocation = jsonObject['creationLocation']! as Map<String, Object>;
+      creationLocation = jsonObject['creationLocation']! as Map<String, Object?>;
       expect(creationLocation, isNotNull);
       // This RichText widget is created by the build method of the Text widget
       // thus the creation location is in text.dart not basic.dart
@@ -1220,7 +1220,7 @@
       final String bId = service.toId(elementB, group)!;
       final Object? jsonList = await service.testExtension('getParentChain', <String, String>{'arg': bId, 'objectGroup': group});
       expect(jsonList, isList);
-      final List<Object> chainElements = jsonList! as List<Object>;
+      final List<Object?> chainElements = jsonList! as List<Object?>;
       final List<Element> expectedChain = elementB.debugGetDiagnosticChain().reversed.toList();
       // Sanity check that the chain goes back to the root.
       expect(expectedChain.first, tester.binding.renderViewElement);
@@ -1228,15 +1228,15 @@
       expect(chainElements.length, equals(expectedChain.length));
       for (int i = 0; i < expectedChain.length; i += 1) {
         expect(chainElements[i], isMap);
-        final Map<String, Object> chainNode = chainElements[i] as Map<String, Object>;
+        final Map<String, Object?> chainNode = chainElements[i]! as Map<String, Object?>;
         final Element element = expectedChain[i];
         expect(chainNode['node'], isMap);
-        final Map<String, Object> jsonNode = chainNode['node']! as Map<String, Object>;
+        final Map<String, Object?> jsonNode = chainNode['node']! as Map<String, Object?>;
         expect(service.toObject(jsonNode['valueId']! as String), equals(element));
         expect(service.toObject(jsonNode['objectId']! as String), isA<DiagnosticsNode>());
 
         expect(chainNode['children'], isList);
-        final List<Object> jsonChildren = chainNode['children']! as List<Object>;
+        final List<Object?> jsonChildren = chainNode['children']! as List<Object?>;
         final List<Element> childrenElements = <Element>[];
         element.visitChildren(childrenElements.add);
         expect(jsonChildren.length, equals(childrenElements.length));
@@ -1247,7 +1247,7 @@
         }
         for (int j = 0; j < childrenElements.length; j += 1) {
           expect(jsonChildren[j], isMap);
-          final Map<String, Object> childJson = jsonChildren[j] as Map<String, Object>;
+          final Map<String, Object?> childJson = jsonChildren[j]! as Map<String, Object?>;
           expect(service.toObject(childJson['valueId']! as String), equals(childrenElements[j]));
           expect(service.toObject(childJson['objectId']! as String), isA<DiagnosticsNode>());
         }
@@ -1258,12 +1258,12 @@
       final DiagnosticsNode diagnostic = const Text('a', textDirection: TextDirection.ltr).toDiagnosticsNode();
       const String group = 'group';
       final String id = service.toId(diagnostic, group)!;
-      final List<Object> propertiesJson = (await service.testExtension('getProperties', <String, String>{'arg': id, 'objectGroup': group}))! as List<Object>;
+      final List<Object?> propertiesJson = (await service.testExtension('getProperties', <String, String>{'arg': id, 'objectGroup': group}))! as List<Object?>;
       final List<DiagnosticsNode> properties = diagnostic.getProperties();
       expect(properties, isNotEmpty);
       expect(propertiesJson.length, equals(properties.length));
       for (int i = 0; i < propertiesJson.length; ++i) {
-        final Map<String, Object> propertyJson = propertiesJson[i] as Map<String, Object>;
+        final Map<String, Object?> propertyJson = propertiesJson[i]! as Map<String, Object?>;
         expect(service.toObject(propertyJson['valueId'] as String?), equals(properties[i].value));
         expect(service.toObject(propertyJson['objectId']! as String), isA<DiagnosticsNode>());
       }
@@ -1286,12 +1286,12 @@
       );
       final DiagnosticsNode diagnostic = find.byType(Stack).evaluate().first.toDiagnosticsNode();
       final String id = service.toId(diagnostic, group)!;
-      final List<Object> propertiesJson = (await service.testExtension('getChildren', <String, String>{'arg': id, 'objectGroup': group}))! as List<Object>;
+      final List<Object?> propertiesJson = (await service.testExtension('getChildren', <String, String>{'arg': id, 'objectGroup': group}))! as List<Object?>;
       final List<DiagnosticsNode> children = diagnostic.getChildren();
       expect(children.length, equals(3));
       expect(propertiesJson.length, equals(children.length));
       for (int i = 0; i < propertiesJson.length; ++i) {
-        final Map<String, Object> propertyJson = propertiesJson[i] as Map<String, Object>;
+        final Map<String, Object?> propertyJson = propertiesJson[i]! as Map<String, Object?>;
         expect(service.toObject(propertyJson['valueId']! as String), equals(children[i].value));
         expect(service.toObject(propertyJson['objectId']! as String), isA<DiagnosticsNode>());
       }
@@ -1314,18 +1314,18 @@
       );
       final DiagnosticsNode diagnostic = find.byType(Stack).evaluate().first.toDiagnosticsNode();
       final String id = service.toId(diagnostic, group)!;
-      final List<Object> childrenJson = (await service.testExtension('getChildrenDetailsSubtree', <String, String>{'arg': id, 'objectGroup': group}))! as List<Object>;
+      final List<Object?> childrenJson = (await service.testExtension('getChildrenDetailsSubtree', <String, String>{'arg': id, 'objectGroup': group}))! as List<Object?>;
       final List<DiagnosticsNode> children = diagnostic.getChildren();
       expect(children.length, equals(3));
       expect(childrenJson.length, equals(children.length));
       for (int i = 0; i < childrenJson.length; ++i) {
-        final Map<String, Object> childJson = childrenJson[i] as Map<String, Object>;
+        final Map<String, Object?> childJson = childrenJson[i]! as Map<String, Object?>;
         expect(service.toObject(childJson['valueId']! as String), equals(children[i].value));
         expect(service.toObject(childJson['objectId']! as String), isA<DiagnosticsNode>());
-        final List<Object> propertiesJson = childJson['properties']! as List<Object>;
+        final List<Object?> propertiesJson = childJson['properties']! as List<Object?>;
         final DiagnosticsNode diagnosticsNode = service.toObject(childJson['objectId']! as String)! as DiagnosticsNode;
         final List<DiagnosticsNode> expectedProperties = diagnosticsNode.getProperties();
-        for (final Map<String, Object> propertyJson in propertiesJson.cast<Map<String, Object>>()) {
+        for (final Map<String, Object?> propertyJson in propertiesJson.cast<Map<String, Object?>>()) {
           final Object? property = service.toObject(propertyJson['objectId']! as String);
           expect(property, isA<DiagnosticsNode>());
           expect(expectedProperties, contains(property));
@@ -1350,37 +1350,37 @@
       );
       final DiagnosticsNode diagnostic = find.byType(Stack).evaluate().first.toDiagnosticsNode();
       final String id = service.toId(diagnostic, group)!;
-      final Map<String, Object> subtreeJson = (await service.testExtension('getDetailsSubtree', <String, String>{'arg': id, 'objectGroup': group}))! as Map<String, Object>;
+      final Map<String, Object?> subtreeJson = (await service.testExtension('getDetailsSubtree', <String, String>{'arg': id, 'objectGroup': group}))! as Map<String, Object?>;
       expect(subtreeJson['objectId'], equals(id));
-      final List<Object> childrenJson = subtreeJson['children']! as List<Object>;
+      final List<Object?> childrenJson = subtreeJson['children']! as List<Object?>;
       final List<DiagnosticsNode> children = diagnostic.getChildren();
       expect(children.length, equals(3));
       expect(childrenJson.length, equals(children.length));
       for (int i = 0; i < childrenJson.length; ++i) {
-        final Map<String, Object> childJson = childrenJson[i] as Map<String, Object>;
+        final Map<String, Object?> childJson = childrenJson[i]! as Map<String, Object?>;
         expect(service.toObject(childJson['valueId']! as String), equals(children[i].value));
         expect(service.toObject(childJson['objectId']! as String), isA<DiagnosticsNode>());
-        final List<Object> propertiesJson = childJson['properties']! as List<Object>;
-        for (final Map<String, Object> propertyJson in propertiesJson.cast<Map<String, Object>>()) {
+        final List<Object?> propertiesJson = childJson['properties']! as List<Object?>;
+        for (final Map<String, Object?> propertyJson in propertiesJson.cast<Map<String, Object?>>()) {
           expect(propertyJson, isNot(contains('children')));
         }
         final DiagnosticsNode diagnosticsNode = service.toObject(childJson['objectId']! as String)! as DiagnosticsNode;
         final List<DiagnosticsNode> expectedProperties = diagnosticsNode.getProperties();
-        for (final Map<String, Object> propertyJson in propertiesJson.cast<Map<String, Object>>()) {
+        for (final Map<String, Object?> propertyJson in propertiesJson.cast<Map<String, Object?>>()) {
           final Object property = service.toObject(propertyJson['objectId']! as String)!;
           expect(property, isA<DiagnosticsNode>());
           expect(expectedProperties, contains(property));
         }
       }
 
-      final Map<String, Object> deepSubtreeJson = (await service.testExtension(
+      final Map<String, Object?> deepSubtreeJson = (await service.testExtension(
         'getDetailsSubtree',
         <String, String>{'arg': id, 'objectGroup': group, 'subtreeDepth': '3'},
-      ))! as Map<String, Object>;
-      final List<Object> deepChildrenJson = deepSubtreeJson['children']! as List<Object>;
-      for (final Map<String, Object> childJson in deepChildrenJson.cast<Map<String, Object>>()) {
-        final List<Object> propertiesJson = childJson['properties']! as List<Object>;
-        for (final Map<String, Object> propertyJson in propertiesJson.cast<Map<String, Object>>()) {
+      ))! as Map<String, Object?>;
+      final List<Object?> deepChildrenJson = deepSubtreeJson['children']! as List<Object?>;
+      for (final Map<String, Object?> childJson in deepChildrenJson.cast<Map<String, Object?>>()) {
+        final List<Object?> propertiesJson = childJson['properties']! as List<Object?>;
+        for (final Map<String, Object?> propertyJson in propertiesJson.cast<Map<String, Object?>>()) {
           expect(propertyJson, contains('children'));
         }
       }
@@ -1396,20 +1396,20 @@
 
       final DiagnosticsNode diagnostic = a.toDiagnosticsNode();
       final String id = service.toId(diagnostic, group)!;
-      final Map<String, Object> subtreeJson = (await service.testExtension('getDetailsSubtree', <String, String>{'arg': id, 'objectGroup': group}))! as Map<String, Object>;
+      final Map<String, Object?> subtreeJson = (await service.testExtension('getDetailsSubtree', <String, String>{'arg': id, 'objectGroup': group}))! as Map<String, Object?>;
       expect(subtreeJson['objectId'], equals(id));
       expect(subtreeJson, contains('children'));
-      final List<Object> propertiesJson = subtreeJson['properties']! as List<Object>;
+      final List<Object?> propertiesJson = subtreeJson['properties']! as List<Object?>;
       expect(propertiesJson.length, equals(1));
-      final Map<String, Object> relatedProperty = propertiesJson.first as Map<String, Object>;
+      final Map<String, Object?> relatedProperty = propertiesJson.first! as Map<String, Object?>;
       expect(relatedProperty['name'], equals('related'));
       expect(relatedProperty['description'], equals('CyclicDiagnostic-b'));
       expect(relatedProperty, contains('isDiagnosticableValue'));
       expect(relatedProperty, isNot(contains('children')));
       expect(relatedProperty, contains('properties'));
-      final List<Object> relatedWidgetProperties = relatedProperty['properties']! as List<Object>;
+      final List<Object?> relatedWidgetProperties = relatedProperty['properties']! as List<Object?>;
       expect(relatedWidgetProperties.length, equals(1));
-      final Map<String, Object> nestedRelatedProperty = relatedWidgetProperties.first as Map<String, Object>;
+      final Map<String, Object?> nestedRelatedProperty = relatedWidgetProperties.first! as Map<String, Object?>;
       expect(nestedRelatedProperty['name'], equals('related'));
       // Make sure we do not include properties or children for diagnostic a
       // which we already included as the root node as that would indicate a
@@ -1440,20 +1440,20 @@
       service.disposeAllGroups();
       await service.testExtension('setPubRootDirectories', <String, String>{});
       service.setSelection(elementA, 'my-group');
-      final Map<String, Object?> jsonA = (await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}))! as Map<String, Object?>;
+      final Map<String, dynamic?> jsonA = (await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}))! as Map<String, dynamic?>;
 
       await service.testExtension('setPubRootDirectories', <String, String>{});
-      Map<String, Object> rootJson = (await service.testExtension('getRootWidgetSummaryTree', <String, String>{'objectGroup': group}))! as Map<String, Object>;
+      Map<String, Object?> rootJson = (await service.testExtension('getRootWidgetSummaryTree', <String, String>{'objectGroup': group}))! as Map<String, Object?>;
       // We haven't yet properly specified which directories are summary tree
       // directories so we get an empty tree other than the root that is always
       // included.
       final Object? rootWidget = service.toObject(rootJson['valueId']! as String);
       expect(rootWidget, equals(WidgetsBinding.instance?.renderViewElement));
-      List<Object> childrenJson = rootJson['children']! as List<Object>;
+      List<Object?> childrenJson = rootJson['children']! as List<Object?>;
       // There are no summary tree children.
       expect(childrenJson.length, equals(0));
 
-      final Map<String, Object> creationLocation = jsonA['creationLocation']! as Map<String, Object>;
+      final Map<String, Object?> creationLocation = jsonA['creationLocation']! as Map<String, Object?>;
       expect(creationLocation, isNotNull);
       final String testFile = creationLocation['file']! as String;
       expect(testFile, endsWith('widget_inspector_test.dart'));
@@ -1463,42 +1463,42 @@
       final String pubRootTest = '/' + segments.take(segments.length - 2).join('/');
       await service.testExtension('setPubRootDirectories', <String, String>{'arg0': pubRootTest});
 
-      rootJson = (await service.testExtension('getRootWidgetSummaryTree', <String, String>{'objectGroup': group}))! as Map<String, Object>;
-      childrenJson = rootJson['children']! as List<Object>;
+      rootJson = (await service.testExtension('getRootWidgetSummaryTree', <String, String>{'objectGroup': group}))! as Map<String, Object?>;
+      childrenJson = rootJson['children']! as List<Object?>;
       // The tree of nodes returned contains all widgets created directly by the
       // test.
-      childrenJson = rootJson['children']! as List<Object>;
+      childrenJson = rootJson['children']! as List<Object?>;
       expect(childrenJson.length, equals(1));
 
-      List<Object> alternateChildrenJson = (await service.testExtension('getChildrenSummaryTree', <String, String>{'arg': rootJson['objectId']! as String, 'objectGroup': group}))! as List<Object>;
+      List<Object?> alternateChildrenJson = (await service.testExtension('getChildrenSummaryTree', <String, String>{'arg': rootJson['objectId']! as String, 'objectGroup': group}))! as List<Object?>;
       expect(alternateChildrenJson.length, equals(1));
-      Map<String, Object> childJson = childrenJson[0] as Map<String, Object>;
-      Map<String, Object> alternateChildJson = alternateChildrenJson[0] as Map<String, Object>;
+      Map<String, Object?> childJson = childrenJson[0]! as Map<String, Object?>;
+      Map<String, Object?> alternateChildJson = alternateChildrenJson[0]! as Map<String, Object?>;
       expect(childJson['description'], startsWith('Directionality'));
       expect(alternateChildJson['description'], startsWith('Directionality'));
       expect(alternateChildJson['valueId'], equals(childJson['valueId']));
 
-      childrenJson = childJson['children']! as List<Object>;
-      alternateChildrenJson = (await service.testExtension('getChildrenSummaryTree', <String, String>{'arg': childJson['objectId']! as String, 'objectGroup': group}))! as List<Object>;
+      childrenJson = childJson['children']! as List<Object?>;
+      alternateChildrenJson = (await service.testExtension('getChildrenSummaryTree', <String, String>{'arg': childJson['objectId']! as String, 'objectGroup': group}))! as List<Object?>;
       expect(alternateChildrenJson.length, equals(1));
       expect(childrenJson.length, equals(1));
-      alternateChildJson = alternateChildrenJson[0] as Map<String, Object>;
-      childJson = childrenJson[0] as Map<String, Object>;
+      alternateChildJson = alternateChildrenJson[0]! as Map<String, Object?>;
+      childJson = childrenJson[0]! as Map<String, Object?>;
       expect(childJson['description'], startsWith('Stack'));
       expect(alternateChildJson['description'], startsWith('Stack'));
       expect(alternateChildJson['valueId'], equals(childJson['valueId']));
-      childrenJson = childJson['children']! as List<Object>;
+      childrenJson = childJson['children']! as List<Object?>;
 
-      childrenJson = childJson['children']! as List<Object>;
-      alternateChildrenJson = (await service.testExtension('getChildrenSummaryTree', <String, String>{'arg': childJson['objectId']! as String, 'objectGroup': group}))! as List<Object>;
+      childrenJson = childJson['children']! as List<Object?>;
+      alternateChildrenJson = (await service.testExtension('getChildrenSummaryTree', <String, String>{'arg': childJson['objectId']! as String, 'objectGroup': group}))! as List<Object?>;
       expect(alternateChildrenJson.length, equals(3));
       expect(childrenJson.length, equals(3));
-      alternateChildJson = alternateChildrenJson[2] as Map<String, Object>;
-      childJson = childrenJson[2] as Map<String, Object>;
+      alternateChildJson = alternateChildrenJson[2]! as Map<String, Object?>;
+      childJson = childrenJson[2]! as Map<String, Object?>;
       expect(childJson['description'], startsWith('Text'));
       expect(alternateChildJson['description'], startsWith('Text'));
       expect(alternateChildJson['valueId'], equals(childJson['valueId']));
-      alternateChildrenJson = (await service.testExtension('getChildrenSummaryTree', <String, String>{'arg': childJson['objectId']! as String, 'objectGroup': group}))! as List<Object>;
+      alternateChildrenJson = (await service.testExtension('getChildrenSummaryTree', <String, String>{'arg': childJson['objectId']! as String, 'objectGroup': group}))! as List<Object?>;
       expect(alternateChildrenJson.length , equals(0));
       expect(childJson['chidlren'], isNull);
     }, skip: !WidgetInspectorService.instance.isWidgetCreationTracked()); // Test requires --track-widget-creation flag.
@@ -1527,16 +1527,16 @@
       service.disposeAllGroups();
       await service.testExtension('setPubRootDirectories', <String, String>{});
       service.setSelection(elementA, 'my-group');
-      final Map<String, Object> jsonA = (await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}))! as Map<String, Object>;
+      final Map<String, Object?> jsonA = (await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}))! as Map<String, Object?>;
       service.setSelection(richTextDiagnostic.value, 'my-group');
 
       await service.testExtension('setPubRootDirectories', <String, String>{});
-      Map<String, Object>? summarySelection = await service.testExtension('getSelectedSummaryWidget', <String, String>{'objectGroup': group}) as Map<String, Object>?;
+      Map<String, Object?>? summarySelection = await service.testExtension('getSelectedSummaryWidget', <String, String>{'objectGroup': group}) as Map<String, Object?>?;
       // No summary selection because we haven't set the pub root directories
       // yet to indicate what directories are in the summary tree.
       expect(summarySelection, isNull);
 
-      final Map<String, Object> creationLocation = jsonA['creationLocation']! as Map<String, Object>;
+      final Map<String, Object?> creationLocation = jsonA['creationLocation']! as Map<String, Object?>;
       expect(creationLocation, isNotNull);
       final String testFile = creationLocation['file']! as String;
       expect(testFile, endsWith('widget_inspector_test.dart'));
@@ -1546,7 +1546,7 @@
       final String pubRootTest = '/' + segments.take(segments.length - 2).join('/');
       await service.testExtension('setPubRootDirectories', <String, String>{'arg0': pubRootTest});
 
-      summarySelection = (await service.testExtension('getSelectedSummaryWidget', <String, String>{'objectGroup': group}))! as Map<String, Object>;
+      summarySelection = (await service.testExtension('getSelectedSummaryWidget', <String, String>{'objectGroup': group}))! as Map<String, Object?>;
       expect(summarySelection['valueId'], isNotNull);
       // We got the Text element instead of the selected RichText element
       // because only the RichText element is part of the summary tree.
@@ -1554,7 +1554,7 @@
 
       // Verify tha the regular getSelectedWidget method still returns
       // the RichText object not the Text element.
-      final Map<String, Object> regularSelection = (await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}))! as Map<String, Object>;
+      final Map<String, Object?> regularSelection = (await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}))! as Map<String, Object?>;
       expect(service.toObject(regularSelection['valueId']! as String), richTextDiagnostic.value);
     }, skip: !WidgetInspectorService.instance.isWidgetCreationTracked()); // Test requires --track-widget-creation flag.
 
@@ -1577,22 +1577,22 @@
       service.disposeAllGroups();
       await service.testExtension('setPubRootDirectories', <String, String>{});
       service.setSelection(elementA, 'my-group');
-      final Map<String, Object> jsonA = (await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}))! as Map<String, Object>;
-      final Map<String, Object> creationLocationA = jsonA['creationLocation']! as Map<String, Object>;
+      final Map<String, Object?> jsonA = (await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}))! as Map<String, Object?>;
+      final Map<String, Object?> creationLocationA = jsonA['creationLocation']! as Map<String, Object?>;
       expect(creationLocationA, isNotNull);
       final String fileA = creationLocationA['file']! as String;
       final int lineA = creationLocationA['line']! as int;
       final int columnA = creationLocationA['column']! as int;
-      final List<Object> parameterLocationsA = creationLocationA['parameterLocations']! as List<Object>;
+      final List<Object?> parameterLocationsA = creationLocationA['parameterLocations']! as List<Object?>;
 
       service.setSelection(elementB, 'my-group');
-      final Map<String, Object> jsonB = (await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}))! as Map<String, Object>;
-      final Map<String, Object> creationLocationB = jsonB['creationLocation']! as Map<String, Object>;
+      final Map<String, Object?> jsonB = (await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}))! as Map<String, Object?>;
+      final Map<String, Object?> creationLocationB = jsonB['creationLocation']! as Map<String, Object?>;
       expect(creationLocationB, isNotNull);
       final String fileB = creationLocationB['file']! as String;
       final int lineB = creationLocationB['line']! as int;
       final int columnB = creationLocationB['column']! as int;
-      final List<Object> parameterLocationsB = creationLocationB['parameterLocations']! as List<Object>;
+      final List<Object?> parameterLocationsB = creationLocationB['parameterLocations']! as List<Object?>;
       expect(fileA, endsWith('widget_inspector_test.dart'));
       expect(fileA, equals(fileB));
       // We don't hardcode the actual lines the widgets are created on as that
@@ -1602,17 +1602,17 @@
       expect(columnA, equals(15));
       expect(columnA, equals(columnB));
       expect(parameterLocationsA.length, equals(1));
-      final Map<String, Object> paramA = parameterLocationsA[0] as Map<String, Object>;
+      final Map<String, Object?> paramA = parameterLocationsA[0]! as Map<String, Object?>;
       expect(paramA['name'], equals('data'));
       expect(paramA['line'], equals(lineA));
       expect(paramA['column'], equals(20));
 
       expect(parameterLocationsB.length, equals(2));
-      final Map<String, Object> paramB1 = parameterLocationsB[0] as Map<String, Object>;
+      final Map<String, Object?> paramB1 = parameterLocationsB[0]! as Map<String, Object?>;
       expect(paramB1['name'], equals('data'));
       expect(paramB1['line'], equals(lineB));
       expect(paramB1['column'], equals(20));
-      final Map<String, Object> paramB2 = parameterLocationsB[1] as Map<String, Object>;
+      final Map<String, Object?> paramB2 = parameterLocationsB[1]! as Map<String, Object?>;
       expect(paramB2['name'], equals('textDirection'));
       expect(paramB2['line'], equals(lineB));
       expect(paramB2['column'], equals(25));
@@ -1635,8 +1635,8 @@
 
       await service.testExtension('setPubRootDirectories', <String, String>{});
       service.setSelection(elementA, 'my-group');
-      Map<String, Object> jsonObject = (await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}))! as Map<String, Object>;
-      Map<String, Object> creationLocation = jsonObject['creationLocation']! as Map<String, Object>;
+      Map<String, Object?> jsonObject = (await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}))! as Map<String, Object?>;
+      Map<String, Object?> creationLocation = jsonObject['creationLocation']! as Map<String, Object?>;
       expect(creationLocation, isNotNull);
       final String fileA = creationLocation['file']! as String;
       expect(fileA, endsWith('widget_inspector_test.dart'));
@@ -1674,9 +1674,9 @@
       ).evaluate().first;
       service.setSelection(richText, 'my-group');
       service.setPubRootDirectories(<String>[pubRootTest]);
-      jsonObject = json.decode(service.getSelectedWidget(null, 'my-group')) as Map<String, Object>;
+      jsonObject = json.decode(service.getSelectedWidget(null, 'my-group')) as Map<String, Object?>;
       expect(jsonObject, isNot(contains('createdByLocalProject')));
-      creationLocation = jsonObject['creationLocation']! as Map<String, Object>;
+      creationLocation = jsonObject['creationLocation']! as Map<String, Object?>;
       expect(creationLocation, isNotNull);
       // This RichText widget is created by the build method of the Text widget
       // thus the creation location is in text.dart not basic.dart
@@ -1697,6 +1697,58 @@
       expect(await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}), contains('createdByLocalProject'));
     }, skip: !WidgetInspectorService.instance.isWidgetCreationTracked() || isBrowser); // Test requires --track-widget-creation flag.
 
+    testWidgets('ext.flutter.inspector.setPubRootDirectories extra args regression test', (WidgetTester tester) async {
+      // Ensure that passing the isolate id as an argument won't break
+      // setPubRootDirectories command.
+      await tester.pumpWidget(
+        Directionality(
+          textDirection: TextDirection.ltr,
+          child: Stack(
+            children: const <Widget>[
+              Text('a'),
+              Text('b', textDirection: TextDirection.ltr),
+              Text('c', textDirection: TextDirection.ltr),
+            ],
+          ),
+        ),
+      );
+      final Element elementA = find.text('a').evaluate().first;
+
+      await service.testExtension('setPubRootDirectories', <String, String>{'isolateId': '34'});
+      service.setSelection(elementA, 'my-group');
+      final Map<String, Object?> jsonObject = (await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}))! as Map<String, Object?>;
+      final Map<String, Object?> creationLocation = jsonObject['creationLocation']! as Map<String, Object?>;
+      expect(creationLocation, isNotNull);
+      final String fileA = creationLocation['file']! as String;
+      expect(fileA, endsWith('widget_inspector_test.dart'));
+      expect(jsonObject, isNot(contains('createdByLocalProject')));
+      final List<String> segments = Uri.parse(fileA).pathSegments;
+      // Strip a couple subdirectories away to generate a plausible pub root
+      // directory.
+      final String pubRootTest = '/' + segments.take(segments.length - 2).join('/');
+      await service.testExtension('setPubRootDirectories', <String, String>{'arg0': pubRootTest, 'isolateId': '34'});
+
+      service.setSelection(elementA, 'my-group');
+      expect(await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}), contains('createdByLocalProject'));
+
+      await service.testExtension('setPubRootDirectories', <String, String>{'arg0': '/invalid/$pubRootTest', 'isolateId': '34'});
+      expect(await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}), isNot(contains('createdByLocalProject')));
+
+      await service.testExtension('setPubRootDirectories', <String, String>{'arg0': 'file://$pubRootTest', 'isolateId': '34'});
+      expect(await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}), contains('createdByLocalProject'));
+
+      await service.testExtension('setPubRootDirectories', <String, String>{'arg0': '$pubRootTest/different', 'isolateId': '34'});
+      expect(await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}), isNot(contains('createdByLocalProject')));
+
+      await service.testExtension('setPubRootDirectories', <String, String>{
+        'arg0': '/unrelated/$pubRootTest',
+        'isolateId': '34',
+        'arg1': 'file://$pubRootTest',
+      });
+
+      expect(await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}), contains('createdByLocalProject'));
+    }, skip: !WidgetInspectorService.instance.isWidgetCreationTracked() || isBrowser); // Test requires --track-widget-creation flag.
+
     testWidgets('ext.flutter.inspector.trackRebuildDirtyWidgets', (WidgetTester tester) async {
       service.rebuildCount = 0;
 
@@ -1705,11 +1757,11 @@
       final Element clockDemoElement = find.byType(ClockDemo).evaluate().first;
 
       service.setSelection(clockDemoElement, 'my-group');
-      final Map<String, Object> jsonObject = (await service.testExtension(
+      final Map<String, Object?> jsonObject = (await service.testExtension(
         'getSelectedWidget',
         <String, String>{'objectGroup': 'my-group'},
-      ))! as Map<String, Object>;
-      final Map<String, Object> creationLocation = jsonObject['creationLocation']! as Map<String, Object>;
+      ))! as Map<String, Object?>;
+      final Map<String, Object?> creationLocation = jsonObject['creationLocation']! as Map<String, Object?>;
       expect(creationLocation, isNotNull);
       final String file = creationLocation['file']! as String;
       expect(file, endsWith('widget_inspector_test.dart'));
@@ -1901,11 +1953,11 @@
       final Element clockDemoElement = find.byType(ClockDemo).evaluate().first;
 
       service.setSelection(clockDemoElement, 'my-group');
-      final Map<String, Object> jsonObject = (await service.testExtension(
+      final Map<String, Object?> jsonObject = (await service.testExtension(
           'getSelectedWidget',
-          <String, String>{'objectGroup': 'my-group'}))! as Map<String, Object>;
-      final Map<String, Object> creationLocation =
-          jsonObject['creationLocation']! as Map<String, Object>;
+          <String, String>{'objectGroup': 'my-group'}))! as Map<String, Object?>;
+      final Map<String, Object?> creationLocation =
+          jsonObject['creationLocation']! as Map<String, Object?>;
       expect(creationLocation, isNotNull);
       final String file = creationLocation['file']! as String;
       expect(file, endsWith('widget_inspector_test.dart'));
@@ -2637,11 +2689,11 @@
       );
 
       // Figure out the pubRootDirectory
-      final Map<String, Object> jsonObject = (await service.testExtension(
+      final Map<String, Object?> jsonObject = (await service.testExtension(
           'getSelectedWidget',
           <String, String>{'objectGroup': 'my-group'},
-      ))! as Map<String, Object>;
-      final Map<String, Object> creationLocation = jsonObject['creationLocation']! as Map<String, Object>;
+      ))! as Map<String, Object?>;
+      final Map<String, Object?> creationLocation = jsonObject['creationLocation']! as Map<String, Object?>;
       expect(creationLocation, isNotNull);
       final String file = creationLocation['file']! as String;
       expect(file, endsWith('widget_inspector_test.dart'));
@@ -2651,22 +2703,22 @@
       service.setPubRootDirectories(<String>[pubRootTest]);
 
       final String summary = service.getRootWidgetSummaryTree('foo1');
-      final List<dynamic> childrenOfRoot = json.decode(summary)['children'] as List<dynamic>;
-      final List<dynamic> childrenOfMaterialApp = childrenOfRoot.first['children'] as List<dynamic>;
-      final Map<String, Object> scaffold = childrenOfMaterialApp.first as Map<String, Object>;
+      final List<Object?> childrenOfRoot = json.decode(summary)['children'] as List<Object?>;
+      final List<Object?> childrenOfMaterialApp = (childrenOfRoot.first! as Map<String, Object?>)['children']! as List<Object?>;
+      final Map<String, Object?> scaffold = childrenOfMaterialApp.first! as Map<String, Object?>;
       expect(scaffold['description'], 'Scaffold');
       final String objectId = scaffold['objectId']! as String;
       final String details = service.getDetailsSubtree(objectId, 'foo2');
-      final List<dynamic> detailedChildren = json.decode(details)['children'] as List<dynamic>;
+      final List<Object?> detailedChildren = json.decode(details)['children'] as List<Object?>;
 
-      final List<Map<String, Object>> appBars = <Map<String, Object>>[];
-      void visitChildren(List<dynamic> children) {
-        for (final Map<String, Object> child in children.cast<Map<String, Object>>()) {
+      final List<Map<String, Object?>> appBars = <Map<String, Object?>>[];
+      void visitChildren(List<Object?> children) {
+        for (final Map<String, Object?> child in children.cast<Map<String, Object?>>()) {
           if (child['description'] == 'AppBar') {
             appBars.add(child);
           }
           if (child.containsKey('children')) {
-            visitChildren(child['children']! as List<dynamic>);
+            visitChildren(child['children']! as List<Object?>);
           }
         }
       }
@@ -2720,8 +2772,8 @@
       final Map<String, Object?> json = node.toJsonMap(delegate);
       expect(json['callbackExecuted'], true);
       expect(json.containsKey('renderObject'), true);
-      expect(json['renderObject'], isA<Map<String, dynamic>>());
-      final Map<String, dynamic> renderObjectJson = json['renderObject']! as Map<String, dynamic>;
+      expect(json['renderObject'], isA<Map<String, Object?>>());
+      final Map<String, Object?> renderObjectJson = json['renderObject']! as Map<String, Object?>;
       expect(renderObjectJson['description'], startsWith('RenderFlex'));
 
       final InspectorSerializationDelegate emptyDelegate =
diff --git a/packages/flutter/test/widgets/widget_inspector_test_utils.dart b/packages/flutter/test/widgets/widget_inspector_test_utils.dart
index be12aed..5ec33fd 100644
--- a/packages/flutter/test/widgets/widget_inspector_test_utils.dart
+++ b/packages/flutter/test/widgets/widget_inspector_test_utils.dart
@@ -30,7 +30,7 @@
   }
 
   List<Map<Object, Object?>> getEventsDispatched(String eventKind) {
-    return eventsDispatched.putIfAbsent(eventKind, () => <Map<Object, Object>>[]);
+    return eventsDispatched.putIfAbsent(eventKind, () => <Map<Object, Object?>>[]);
   }
 
   Iterable<Map<Object, Object?>> getServiceExtensionStateChangedEvents(String extensionName) {
diff --git a/packages/flutter_driver/lib/driver_extension.dart b/packages/flutter_driver/lib/driver_extension.dart
index 13ae916..fce9ad8 100644
--- a/packages/flutter_driver/lib/driver_extension.dart
+++ b/packages/flutter_driver/lib/driver_extension.dart
@@ -24,5 +24,6 @@
 ///     }
 library flutter_driver_extension;
 
-export 'src/common/create_finder_factory.dart';
+export 'src/common/deserialization_factory.dart';
+export 'src/common/handler_factory.dart';
 export 'src/extension/extension.dart';
diff --git a/packages/flutter_driver/lib/flutter_driver.dart b/packages/flutter_driver/lib/flutter_driver.dart
index 46ebb8c..c7eddfe 100644
--- a/packages/flutter_driver/lib/flutter_driver.dart
+++ b/packages/flutter_driver/lib/flutter_driver.dart
@@ -13,6 +13,7 @@
 /// Protractor (Angular), Espresso (Android) or Earl Gray (iOS).
 library flutter_driver;
 
+export 'src/common/deserialization_factory.dart';
 export 'src/common/diagnostics_tree.dart';
 export 'src/common/enum_util.dart';
 export 'src/common/error.dart';
diff --git a/packages/flutter_driver/lib/src/common/create_finder_factory.dart b/packages/flutter_driver/lib/src/common/create_finder_factory.dart
deleted file mode 100644
index 36c3d27..0000000
--- a/packages/flutter_driver/lib/src/common/create_finder_factory.dart
+++ /dev/null
@@ -1,117 +0,0 @@
-// Copyright 2014 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:flutter_test/flutter_test.dart';
-import 'package:flutter/widgets.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/cupertino.dart';
-
-import 'error.dart';
-import 'find.dart';
-
-/// A factory which creates [Finder]s from [SerializableFinder]s.
-mixin CreateFinderFactory {
-  /// Creates the flutter widget finder from [SerializableFinder].
-  Finder createFinder(SerializableFinder finder) {
-    final String finderType = finder.finderType;
-    switch (finderType) {
-      case 'ByText':
-        return _createByTextFinder(finder as ByText);
-      case 'ByTooltipMessage':
-        return _createByTooltipMessageFinder(finder as ByTooltipMessage);
-      case 'BySemanticsLabel':
-        return _createBySemanticsLabelFinder(finder as BySemanticsLabel);
-      case 'ByValueKey':
-        return _createByValueKeyFinder(finder as ByValueKey);
-      case 'ByType':
-        return _createByTypeFinder(finder as ByType);
-      case 'PageBack':
-        return _createPageBackFinder();
-      case 'Ancestor':
-        return _createAncestorFinder(finder as Ancestor);
-      case 'Descendant':
-        return _createDescendantFinder(finder as Descendant);
-      default:
-        throw DriverError('Unsupported search specification type $finderType');
-    }
-  }
-
-  Finder _createByTextFinder(ByText arguments) {
-    return find.text(arguments.text);
-  }
-
-  Finder _createByTooltipMessageFinder(ByTooltipMessage arguments) {
-    return find.byElementPredicate((Element element) {
-      final Widget widget = element.widget;
-      if (widget is Tooltip) {
-        return widget.message == arguments.text;
-      }
-      return false;
-    }, description: 'widget with text tooltip "${arguments.text}"');
-  }
-
-  Finder _createBySemanticsLabelFinder(BySemanticsLabel arguments) {
-    return find.byElementPredicate((Element element) {
-      if (element is! RenderObjectElement) {
-        return false;
-      }
-      final String? semanticsLabel = element.renderObject.debugSemantics?.label;
-      if (semanticsLabel == null) {
-        return false;
-      }
-      final Pattern label = arguments.label;
-      return label is RegExp
-          ? label.hasMatch(semanticsLabel)
-          : label == semanticsLabel;
-    }, description: 'widget with semantic label "${arguments.label}"');
-  }
-
-  Finder _createByValueKeyFinder(ByValueKey arguments) {
-    switch (arguments.keyValueType) {
-      case 'int':
-        return find.byKey(ValueKey<int>(arguments.keyValue as int));
-      case 'String':
-        return find.byKey(ValueKey<String>(arguments.keyValue as String));
-      default:
-        throw 'Unsupported ByValueKey type: ${arguments.keyValueType}';
-    }
-  }
-
-  Finder _createByTypeFinder(ByType arguments) {
-    return find.byElementPredicate((Element element) {
-      return element.widget.runtimeType.toString() == arguments.type;
-    }, description: 'widget with runtimeType "${arguments.type}"');
-  }
-
-  Finder _createPageBackFinder() {
-    return find.byElementPredicate((Element element) {
-      final Widget widget = element.widget;
-      if (widget is Tooltip) {
-        return widget.message == 'Back';
-      }
-      if (widget is CupertinoNavigationBarBackButton) {
-        return true;
-      }
-      return false;
-    }, description: 'Material or Cupertino back button');
-  }
-
-  Finder _createAncestorFinder(Ancestor arguments) {
-    final Finder finder = find.ancestor(
-      of: createFinder(arguments.of),
-      matching: createFinder(arguments.matching),
-      matchRoot: arguments.matchRoot,
-    );
-    return arguments.firstMatchOnly ? finder.first : finder;
-  }
-
-  Finder _createDescendantFinder(Descendant arguments) {
-    final Finder finder = find.descendant(
-      of: createFinder(arguments.of),
-      matching: createFinder(arguments.matching),
-      matchRoot: arguments.matchRoot,
-    );
-    return arguments.firstMatchOnly ? finder.first : finder;
-  }
-}
diff --git a/packages/flutter_driver/lib/src/common/deserialization_factory.dart b/packages/flutter_driver/lib/src/common/deserialization_factory.dart
new file mode 100644
index 0000000..b2e8b95
--- /dev/null
+++ b/packages/flutter_driver/lib/src/common/deserialization_factory.dart
@@ -0,0 +1,70 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'diagnostics_tree.dart';
+import 'error.dart';
+import 'find.dart';
+import 'frame_sync.dart';
+import 'geometry.dart';
+import 'gesture.dart';
+import 'health.dart';
+import 'layer_tree.dart';
+import 'message.dart';
+import 'render_tree.dart';
+import 'request_data.dart';
+import 'semantics.dart';
+import 'text.dart';
+import 'wait.dart';
+
+/// A factory for deserializing [Finder]s.
+mixin DeserializeFinderFactory {
+  /// Deserializes the finder from JSON generated by [SerializableFinder.serialize].
+  SerializableFinder deserializeFinder(Map<String, String> json) {
+    final String? finderType = json['finderType'];
+    switch (finderType) {
+      case 'ByType': return ByType.deserialize(json);
+      case 'ByValueKey': return ByValueKey.deserialize(json);
+      case 'ByTooltipMessage': return ByTooltipMessage.deserialize(json);
+      case 'BySemanticsLabel': return BySemanticsLabel.deserialize(json);
+      case 'ByText': return ByText.deserialize(json);
+      case 'PageBack': return const PageBack();
+      case 'Descendant': return Descendant.deserialize(json, this);
+      case 'Ancestor': return Ancestor.deserialize(json, this);
+    }
+    throw DriverError('Unsupported search specification type $finderType');
+  }
+}
+
+/// A factory for deserializing [Command]s.
+mixin DeserializeCommandFactory {
+  /// Deserializes the finder from JSON generated by [Command.serialize] or [CommandWithTarget.serialize].
+  Command deserializeCommand(Map<String, String> params, DeserializeFinderFactory finderFactory) {
+    final String? kind = params['command'];
+    switch(kind) {
+      case 'get_health': return GetHealth.deserialize(params);
+      case 'get_layer_tree': return GetLayerTree.deserialize(params);
+      case 'get_render_tree': return GetRenderTree.deserialize(params);
+      case 'enter_text': return EnterText.deserialize(params);
+      case 'get_text': return GetText.deserialize(params, finderFactory);
+      case 'request_data': return RequestData.deserialize(params);
+      case 'scroll': return Scroll.deserialize(params, finderFactory);
+      case 'scrollIntoView': return ScrollIntoView.deserialize(params, finderFactory);
+      case 'set_frame_sync': return SetFrameSync.deserialize(params);
+      case 'set_semantics': return SetSemantics.deserialize(params);
+      case 'set_text_entry_emulation': return SetTextEntryEmulation.deserialize(params);
+      case 'tap': return Tap.deserialize(params, finderFactory);
+      case 'waitFor': return WaitFor.deserialize(params, finderFactory);
+      case 'waitForAbsent': return WaitForAbsent.deserialize(params, finderFactory);
+      case 'waitForCondition': return WaitForCondition.deserialize(params);
+      case 'waitUntilNoTransientCallbacks': return WaitUntilNoTransientCallbacks.deserialize(params);
+      case 'waitUntilNoPendingFrame': return WaitUntilNoPendingFrame.deserialize(params);
+      case 'waitUntilFirstFrameRasterized': return WaitUntilFirstFrameRasterized.deserialize(params);
+      case 'get_semantics_id': return GetSemanticsId.deserialize(params, finderFactory);
+      case 'get_offset': return GetOffset.deserialize(params, finderFactory);
+      case 'get_diagnostics_tree': return GetDiagnosticsTree.deserialize(params, finderFactory);
+    }
+
+    throw DriverError('Unsupported command kind $kind');
+  }
+}
diff --git a/packages/flutter_driver/lib/src/common/diagnostics_tree.dart b/packages/flutter_driver/lib/src/common/diagnostics_tree.dart
index ec4b53d..5bcaa8a 100644
--- a/packages/flutter_driver/lib/src/common/diagnostics_tree.dart
+++ b/packages/flutter_driver/lib/src/common/diagnostics_tree.dart
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'deserialization_factory.dart';
 import 'enum_util.dart';
 import 'find.dart';
 import 'message.dart';
diff --git a/packages/flutter_driver/lib/src/common/find.dart b/packages/flutter_driver/lib/src/common/find.dart
index bdb6ac2..01bd838 100644
--- a/packages/flutter_driver/lib/src/common/find.dart
+++ b/packages/flutter_driver/lib/src/common/find.dart
@@ -6,28 +6,10 @@
 
 import 'package:meta/meta.dart';
 
+import 'deserialization_factory.dart';
 import 'error.dart';
 import 'message.dart';
 
-/// A factory for deserializing [Finder]s.
-mixin DeserializeFinderFactory {
-  /// Deserializes the finder from JSON generated by [SerializableFinder.serialize].
-  SerializableFinder deserializeFinder(Map<String, String> json) {
-    final String? finderType = json['finderType'];
-    switch (finderType) {
-      case 'ByType': return ByType.deserialize(json);
-      case 'ByValueKey': return ByValueKey.deserialize(json);
-      case 'ByTooltipMessage': return ByTooltipMessage.deserialize(json);
-      case 'BySemanticsLabel': return BySemanticsLabel.deserialize(json);
-      case 'ByText': return ByText.deserialize(json);
-      case 'PageBack': return const PageBack();
-      case 'Descendant': return Descendant.deserialize(json, this);
-      case 'Ancestor': return Ancestor.deserialize(json, this);
-    }
-    throw DriverError('Unsupported search specification type $finderType');
-  }
-}
-
 const List<Type> _supportedKeyValueTypes = <Type>[String, int];
 
 DriverError _createInvalidKeyValueTypeError(String invalidType) {
diff --git a/packages/flutter_driver/lib/src/common/geometry.dart b/packages/flutter_driver/lib/src/common/geometry.dart
index c8e4a32..aafcd06 100644
--- a/packages/flutter_driver/lib/src/common/geometry.dart
+++ b/packages/flutter_driver/lib/src/common/geometry.dart
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'deserialization_factory.dart';
 import 'enum_util.dart';
 import 'find.dart';
 import 'message.dart';
diff --git a/packages/flutter_driver/lib/src/common/gesture.dart b/packages/flutter_driver/lib/src/common/gesture.dart
index 5a66767..edd87c8 100644
--- a/packages/flutter_driver/lib/src/common/gesture.dart
+++ b/packages/flutter_driver/lib/src/common/gesture.dart
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'deserialization_factory.dart';
 import 'find.dart';
 import 'message.dart';
 
diff --git a/packages/flutter_driver/lib/src/common/handler_factory.dart b/packages/flutter_driver/lib/src/common/handler_factory.dart
new file mode 100644
index 0000000..22eb52f
--- /dev/null
+++ b/packages/flutter_driver/lib/src/common/handler_factory.dart
@@ -0,0 +1,492 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+import 'package:flutter/scheduler.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_driver/driver_extension.dart';
+import 'package:flutter_driver/src/extension/wait_conditions.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import 'diagnostics_tree.dart';
+import 'error.dart';
+import 'find.dart';
+import 'frame_sync.dart';
+import 'geometry.dart';
+import 'gesture.dart';
+import 'health.dart';
+import 'layer_tree.dart';
+import 'message.dart';
+import 'render_tree.dart';
+import 'request_data.dart';
+import 'semantics.dart';
+import 'text.dart';
+import 'wait.dart';
+
+/// A factory which creates [Finder]s from [SerializableFinder]s.
+mixin CreateFinderFactory {
+  /// Creates the flutter widget finder from [SerializableFinder].
+  Finder createFinder(SerializableFinder finder) {
+    final String finderType = finder.finderType;
+    switch (finderType) {
+      case 'ByText':
+        return _createByTextFinder(finder as ByText);
+      case 'ByTooltipMessage':
+        return _createByTooltipMessageFinder(finder as ByTooltipMessage);
+      case 'BySemanticsLabel':
+        return _createBySemanticsLabelFinder(finder as BySemanticsLabel);
+      case 'ByValueKey':
+        return _createByValueKeyFinder(finder as ByValueKey);
+      case 'ByType':
+        return _createByTypeFinder(finder as ByType);
+      case 'PageBack':
+        return _createPageBackFinder();
+      case 'Ancestor':
+        return _createAncestorFinder(finder as Ancestor);
+      case 'Descendant':
+        return _createDescendantFinder(finder as Descendant);
+      default:
+        throw DriverError('Unsupported search specification type $finderType');
+    }
+  }
+
+  Finder _createByTextFinder(ByText arguments) {
+    return find.text(arguments.text);
+  }
+
+  Finder _createByTooltipMessageFinder(ByTooltipMessage arguments) {
+    return find.byElementPredicate((Element element) {
+      final Widget widget = element.widget;
+      if (widget is Tooltip) {
+        return widget.message == arguments.text;
+      }
+      return false;
+    }, description: 'widget with text tooltip "${arguments.text}"');
+  }
+
+  Finder _createBySemanticsLabelFinder(BySemanticsLabel arguments) {
+    return find.byElementPredicate((Element element) {
+      if (element is! RenderObjectElement) {
+        return false;
+      }
+      final String? semanticsLabel = element.renderObject.debugSemantics?.label;
+      if (semanticsLabel == null) {
+        return false;
+      }
+      final Pattern label = arguments.label;
+      return label is RegExp
+          ? label.hasMatch(semanticsLabel)
+          : label == semanticsLabel;
+    }, description: 'widget with semantic label "${arguments.label}"');
+  }
+
+  Finder _createByValueKeyFinder(ByValueKey arguments) {
+    switch (arguments.keyValueType) {
+      case 'int':
+        return find.byKey(ValueKey<int>(arguments.keyValue as int));
+      case 'String':
+        return find.byKey(ValueKey<String>(arguments.keyValue as String));
+      default:
+        throw 'Unsupported ByValueKey type: ${arguments.keyValueType}';
+    }
+  }
+
+  Finder _createByTypeFinder(ByType arguments) {
+    return find.byElementPredicate((Element element) {
+      return element.widget.runtimeType.toString() == arguments.type;
+    }, description: 'widget with runtimeType "${arguments.type}"');
+  }
+
+  Finder _createPageBackFinder() {
+    return find.byElementPredicate((Element element) {
+      final Widget widget = element.widget;
+      if (widget is Tooltip) {
+        return widget.message == 'Back';
+      }
+      if (widget is CupertinoNavigationBarBackButton) {
+        return true;
+      }
+      return false;
+    }, description: 'Material or Cupertino back button');
+  }
+
+  Finder _createAncestorFinder(Ancestor arguments) {
+    final Finder finder = find.ancestor(
+      of: createFinder(arguments.of),
+      matching: createFinder(arguments.matching),
+      matchRoot: arguments.matchRoot,
+    );
+    return arguments.firstMatchOnly ? finder.first : finder;
+  }
+
+  Finder _createDescendantFinder(Descendant arguments) {
+    final Finder finder = find.descendant(
+      of: createFinder(arguments.of),
+      matching: createFinder(arguments.matching),
+      matchRoot: arguments.matchRoot,
+    );
+    return arguments.firstMatchOnly ? finder.first : finder;
+  }
+}
+
+/// A factory for [Command] handlers.
+mixin CommandHandlerFactory {
+  /// With [_frameSync] enabled, Flutter Driver will wait to perform an action
+  /// until there are no pending frames in the app under test.
+  bool _frameSync = true;
+
+  /// Gets [DataHandler] for result delivery.
+  @protected
+  DataHandler? getDataHandler() => null;
+
+  /// Registers text input emulation.
+  @protected
+  void registerTextInput() {
+    _testTextInput.register();
+  }
+
+  final TestTextInput _testTextInput = TestTextInput();
+
+  /// Deserializes the finder from JSON generated by [Command.serialize] or [CommandWithTarget.serialize].
+  Future<Result?> handleCommand(Command command, WidgetController prober, CreateFinderFactory finderFactory) {
+    switch(command.kind) {
+      case 'get_health': return _getHealth(command);
+      case 'get_layer_tree': return _getLayerTree(command);
+      case 'get_render_tree': return _getRenderTree(command);
+      case 'enter_text': return _enterText(command);
+      case 'get_text': return _getText(command, finderFactory);
+      case 'request_data': return _requestData(command);
+      case 'scroll': return _scroll(command, prober, finderFactory);
+      case 'scrollIntoView': return _scrollIntoView(command, finderFactory);
+      case 'set_frame_sync': return _setFrameSync(command);
+      case 'set_semantics': return _setSemantics(command);
+      case 'set_text_entry_emulation': return _setTextEntryEmulation(command);
+      case 'tap': return _tap(command, prober, finderFactory);
+      case 'waitFor': return _waitFor(command, finderFactory);
+      case 'waitForAbsent': return _waitForAbsent(command, finderFactory);
+      case 'waitForCondition': return _waitForCondition(command);
+      case 'waitUntilNoTransientCallbacks': return _waitUntilNoTransientCallbacks(command);
+      case 'waitUntilNoPendingFrame': return _waitUntilNoPendingFrame(command);
+      case 'waitUntilFirstFrameRasterized': return _waitUntilFirstFrameRasterized(command);
+      case 'get_semantics_id': return _getSemanticsId(command, finderFactory);
+      case 'get_offset': return _getOffset(command, finderFactory);
+      case 'get_diagnostics_tree': return _getDiagnosticsTree(command, finderFactory);
+    }
+
+    throw DriverError('Unsupported command kind ${command.kind}');
+  }
+
+  Future<Health> _getHealth(Command command) async => const Health(HealthStatus.ok);
+
+  Future<LayerTree> _getLayerTree(Command command) async {
+    return LayerTree(RendererBinding.instance?.renderView.debugLayer?.toStringDeep());
+  }
+
+  Future<RenderTree> _getRenderTree(Command command) async {
+    return RenderTree(RendererBinding.instance?.renderView.toStringDeep());
+  }
+
+  Future<EnterTextResult> _enterText(Command command) async {
+    if (!_testTextInput.isRegistered) {
+      throw 'Unable to fulfill `FlutterDriver.enterText`. Text emulation is '
+            'disabled. You can enable it using `FlutterDriver.setTextEntryEmulation`.';
+    }
+    final EnterText enterTextCommand = command as EnterText;
+    _testTextInput.enterText(enterTextCommand.text);
+    return const EnterTextResult();
+  }
+
+  Future<RequestDataResult> _requestData(Command command) async {
+    final RequestData requestDataCommand = command as RequestData;
+    final DataHandler? dataHandler = getDataHandler();
+    return RequestDataResult(dataHandler == null
+      ? 'No requestData Extension registered'
+      : await dataHandler(requestDataCommand.message));
+  }
+
+  Future<SetFrameSyncResult> _setFrameSync(Command command) async {
+    final SetFrameSync setFrameSyncCommand = command as SetFrameSync;
+    _frameSync = setFrameSyncCommand.enabled;
+    return const SetFrameSyncResult();
+  }
+
+  Future<TapResult> _tap(Command command, WidgetController prober, CreateFinderFactory finderFactory) async {
+    final Tap tapCommand = command as Tap;
+    final Finder computedFinder = await waitForElement(
+      finderFactory.createFinder(tapCommand.finder).hitTestable(),
+    );
+    await prober.tap(computedFinder);
+    return const TapResult();
+  }
+
+  Future<WaitForResult> _waitFor(Command command, CreateFinderFactory finderFactory) async {
+    final WaitFor waitForCommand = command as WaitFor;
+    await waitForElement(finderFactory.createFinder(waitForCommand.finder));
+    return const WaitForResult();
+  }
+
+  Future<WaitForAbsentResult> _waitForAbsent(Command command, CreateFinderFactory finderFactory) async {
+    final WaitForAbsent waitForAbsentCommand = command as WaitForAbsent;
+    await waitForAbsentElement(finderFactory.createFinder(waitForAbsentCommand.finder));
+    return const WaitForAbsentResult();
+  }
+
+  Future<Result?> _waitForCondition(Command command) async {
+    assert(command != null);
+    final WaitForCondition waitForConditionCommand = command as WaitForCondition;
+    final WaitCondition condition = deserializeCondition(waitForConditionCommand.condition);
+    await condition.wait();
+    return null;
+  }
+
+  @Deprecated(
+    'This method has been deprecated in favor of _waitForCondition. '
+    'This feature was deprecated after v1.9.3.'
+  )
+  Future<Result?> _waitUntilNoTransientCallbacks(Command command) async {
+    if (SchedulerBinding.instance!.transientCallbackCount != 0)
+      await _waitUntilFrame(() => SchedulerBinding.instance!.transientCallbackCount == 0);
+    return null;
+  }
+
+  /// Returns a future that waits until no pending frame is scheduled (frame is synced).
+  ///
+  /// Specifically, it checks:
+  /// * Whether the count of transient callbacks is zero.
+  /// * Whether there's no pending request for scheduling a new frame.
+  ///
+  /// We consider the frame is synced when both conditions are met.
+  ///
+  /// This method relies on a Flutter Driver mechanism called "frame sync",
+  /// which waits for transient animations to finish. Persistent animations will
+  /// cause this to wait forever.
+  ///
+  /// If a test needs to interact with the app while animations are running, it
+  /// should avoid this method and instead disable the frame sync using
+  /// `set_frame_sync` method. See [FlutterDriver.runUnsynchronized] for more
+  /// details on how to do this. Note, disabling frame sync will require the
+  /// test author to use some other method to avoid flakiness.
+  ///
+  /// This method has been deprecated in favor of [_waitForCondition].
+  @Deprecated(
+    'This method has been deprecated in favor of _waitForCondition. '
+    'This feature was deprecated after v1.9.3.'
+  )
+  Future<Result?> _waitUntilNoPendingFrame(Command command) async {
+    await _waitUntilFrame(() {
+      return SchedulerBinding.instance!.transientCallbackCount == 0
+          && !SchedulerBinding.instance!.hasScheduledFrame;
+    });
+    return null;
+  }
+
+  Future<GetSemanticsIdResult> _getSemanticsId(Command command, CreateFinderFactory finderFactory) async {
+    final GetSemanticsId semanticsCommand = command as GetSemanticsId;
+    final Finder target = await waitForElement(finderFactory.createFinder(semanticsCommand.finder));
+    final Iterable<Element> elements = target.evaluate();
+    if (elements.length > 1) {
+      throw StateError('Found more than one element with the same ID: $elements');
+    }
+    final Element element = elements.single;
+    RenderObject? renderObject = element.renderObject;
+    SemanticsNode? node;
+    while (renderObject != null && node == null) {
+      node = renderObject.debugSemantics;
+      renderObject = renderObject.parent as RenderObject?;
+    }
+    if (node == null)
+      throw StateError('No semantics data found');
+    return GetSemanticsIdResult(node.id);
+  }
+
+  Future<GetOffsetResult> _getOffset(Command command, CreateFinderFactory finderFactory) async {
+    final GetOffset getOffsetCommand = command as GetOffset;
+    final Finder finder = await waitForElement(finderFactory.createFinder(getOffsetCommand.finder));
+    final Element element = finder.evaluate().single;
+    final RenderBox box = (element.renderObject as RenderBox?)!;
+    Offset localPoint;
+    switch (getOffsetCommand.offsetType) {
+      case OffsetType.topLeft:
+        localPoint = Offset.zero;
+        break;
+      case OffsetType.topRight:
+        localPoint = box.size.topRight(Offset.zero);
+        break;
+      case OffsetType.bottomLeft:
+        localPoint = box.size.bottomLeft(Offset.zero);
+        break;
+      case OffsetType.bottomRight:
+        localPoint = box.size.bottomRight(Offset.zero);
+        break;
+      case OffsetType.center:
+        localPoint = box.size.center(Offset.zero);
+        break;
+    }
+    final Offset globalPoint = box.localToGlobal(localPoint);
+    return GetOffsetResult(dx: globalPoint.dx, dy: globalPoint.dy);
+  }
+
+  Future<DiagnosticsTreeResult> _getDiagnosticsTree(Command command, CreateFinderFactory finderFactory) async {
+    final GetDiagnosticsTree diagnosticsCommand = command as GetDiagnosticsTree;
+    final Finder finder = await waitForElement(finderFactory.createFinder(diagnosticsCommand.finder));
+    final Element element = finder.evaluate().single;
+    DiagnosticsNode diagnosticsNode;
+    switch (diagnosticsCommand.diagnosticsType) {
+      case DiagnosticsType.renderObject:
+        diagnosticsNode = element.renderObject!.toDiagnosticsNode();
+        break;
+      case DiagnosticsType.widget:
+        diagnosticsNode = element.toDiagnosticsNode();
+        break;
+    }
+    return DiagnosticsTreeResult(diagnosticsNode.toJsonMap(DiagnosticsSerializationDelegate(
+      subtreeDepth: diagnosticsCommand.subtreeDepth,
+      includeProperties: diagnosticsCommand.includeProperties,
+    )));
+  }
+
+  Future<ScrollResult> _scroll(Command command, WidgetController _prober, CreateFinderFactory finderFactory) async {
+    final Scroll scrollCommand = command as Scroll;
+    final Finder target = await waitForElement(finderFactory.createFinder(scrollCommand.finder));
+    final int totalMoves = scrollCommand.duration.inMicroseconds * scrollCommand.frequency ~/ Duration.microsecondsPerSecond;
+    final Offset delta = Offset(scrollCommand.dx, scrollCommand.dy) / totalMoves.toDouble();
+    final Duration pause = scrollCommand.duration ~/ totalMoves;
+    final Offset startLocation = _prober.getCenter(target);
+    Offset currentLocation = startLocation;
+    final TestPointer pointer = TestPointer(1);
+    _prober.binding.handlePointerEvent(pointer.down(startLocation));
+    await Future<void>.value(); // so that down and move don't happen in the same microtask
+    for (int moves = 0; moves < totalMoves; moves += 1) {
+      currentLocation = currentLocation + delta;
+      _prober.binding.handlePointerEvent(pointer.move(currentLocation));
+      await Future<void>.delayed(pause);
+    }
+    _prober.binding.handlePointerEvent(pointer.up());
+
+    return const ScrollResult();
+  }
+
+  Future<ScrollResult> _scrollIntoView(Command command, CreateFinderFactory finderFactory) async {
+    final ScrollIntoView scrollIntoViewCommand = command as ScrollIntoView;
+    final Finder target = await waitForElement(finderFactory.createFinder(scrollIntoViewCommand.finder));
+    await Scrollable.ensureVisible(target.evaluate().single, duration: const Duration(milliseconds: 100), alignment: scrollIntoViewCommand.alignment);
+    return const ScrollResult();
+  }
+
+  Future<GetTextResult> _getText(Command command, CreateFinderFactory finderFactory) async {
+    final GetText getTextCommand = command as GetText;
+    final Finder target = await waitForElement(finderFactory.createFinder(getTextCommand.finder));
+
+    final Widget widget = target.evaluate().single.widget;
+    String? text;
+
+    if (widget.runtimeType == Text) {
+      text = (widget as Text).data;
+    } else if (widget.runtimeType == RichText) {
+      final RichText richText = widget as RichText;
+      if (richText.text.runtimeType == TextSpan) {
+        text = (richText.text as TextSpan).text;
+      }
+    } else if (widget.runtimeType == TextField) {
+      text = (widget as TextField).controller?.text;
+    } else if (widget.runtimeType == TextFormField) {
+      text = (widget as TextFormField).controller?.text;
+    } else if (widget.runtimeType == EditableText) {
+      text = (widget as EditableText).controller.text;
+    }
+
+    if (text == null) {
+      throw UnsupportedError('Type ${widget.runtimeType.toString()} is currently not supported by getText');
+    }
+
+    return GetTextResult(text);
+  }
+
+  Future<SetTextEntryEmulationResult> _setTextEntryEmulation(Command command) async {
+    final SetTextEntryEmulation setTextEntryEmulationCommand = command as SetTextEntryEmulation;
+    if (setTextEntryEmulationCommand.enabled) {
+      _testTextInput.register();
+    } else {
+      _testTextInput.unregister();
+    }
+    return const SetTextEntryEmulationResult();
+  }
+
+  SemanticsHandle? _semantics;
+  bool get _semanticsIsEnabled => RendererBinding.instance!.pipelineOwner.semanticsOwner != null;
+
+  Future<SetSemanticsResult> _setSemantics(Command command) async {
+    final SetSemantics setSemanticsCommand = command as SetSemantics;
+    final bool semanticsWasEnabled = _semanticsIsEnabled;
+    if (setSemanticsCommand.enabled && _semantics == null) {
+      _semantics = RendererBinding.instance!.pipelineOwner.ensureSemantics();
+      if (!semanticsWasEnabled) {
+        // wait for the first frame where semantics is enabled.
+        final Completer<void> completer = Completer<void>();
+        SchedulerBinding.instance!.addPostFrameCallback((Duration d) {
+          completer.complete();
+        });
+        await completer.future;
+      }
+    } else if (!setSemanticsCommand.enabled && _semantics != null) {
+      _semantics!.dispose();
+      _semantics = null;
+    }
+    return SetSemanticsResult(semanticsWasEnabled != _semanticsIsEnabled);
+  }
+
+  // This can be used to wait for the first frame being rasterized during app launch.
+  @Deprecated(
+    'This method has been deprecated in favor of _waitForCondition. '
+    'This feature was deprecated after v1.9.3.'
+  )
+  Future<Result?> _waitUntilFirstFrameRasterized(Command command) async {
+    await WidgetsBinding.instance!.waitUntilFirstFrameRasterized;
+    return null;
+  }
+
+  /// Runs `finder` repeatedly until it finds one or more [Element]s.
+  Future<Finder> waitForElement(Finder finder) async {
+    if (_frameSync)
+      await _waitUntilFrame(() => SchedulerBinding.instance!.transientCallbackCount == 0);
+
+    await _waitUntilFrame(() => finder.evaluate().isNotEmpty);
+
+    if (_frameSync)
+      await _waitUntilFrame(() => SchedulerBinding.instance!.transientCallbackCount == 0);
+
+    return finder;
+  }
+
+  /// Runs `finder` repeatedly until it finds zero [Element]s.
+  Future<Finder> waitForAbsentElement(Finder finder) async {
+    if (_frameSync)
+      await _waitUntilFrame(() => SchedulerBinding.instance!.transientCallbackCount == 0);
+
+    await _waitUntilFrame(() => finder.evaluate().isEmpty);
+
+    if (_frameSync)
+      await _waitUntilFrame(() => SchedulerBinding.instance!.transientCallbackCount == 0);
+
+    return finder;
+  }
+
+  // Waits until at the end of a frame the provided [condition] is [true].
+  Future<void> _waitUntilFrame(bool condition(), [ Completer<void>? completer ]) {
+    completer ??= Completer<void>();
+    if (!condition()) {
+      SchedulerBinding.instance!.addPostFrameCallback((Duration timestamp) {
+        _waitUntilFrame(condition, completer);
+      });
+    } else {
+      completer.complete();
+    }
+    return completer.future;
+  }
+}
diff --git a/packages/flutter_driver/lib/src/common/text.dart b/packages/flutter_driver/lib/src/common/text.dart
index 6ccaf84..a394fbe 100644
--- a/packages/flutter_driver/lib/src/common/text.dart
+++ b/packages/flutter_driver/lib/src/common/text.dart
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'deserialization_factory.dart';
 import 'find.dart';
 import 'message.dart';
 
diff --git a/packages/flutter_driver/lib/src/extension/extension.dart b/packages/flutter_driver/lib/src/extension/extension.dart
index 44bc962..324decc 100644
--- a/packages/flutter_driver/lib/src/extension/extension.dart
+++ b/packages/flutter_driver/lib/src/extension/extension.dart
@@ -9,33 +9,21 @@
 import 'package:flutter/foundation.dart';
 import 'package:flutter/gestures.dart';
 import 'package:flutter/material.dart';
-import 'package:flutter/rendering.dart' show RendererBinding, SemanticsHandle;
+import 'package:flutter/rendering.dart' show RendererBinding;
 import 'package:flutter/scheduler.dart';
 import 'package:flutter/semantics.dart';
 import 'package:flutter/services.dart';
 import 'package:flutter/widgets.dart';
 import 'package:flutter_test/flutter_test.dart';
 
-import '../common/create_finder_factory.dart';
-import '../common/diagnostics_tree.dart';
+import '../common/deserialization_factory.dart';
 import '../common/error.dart';
 import '../common/find.dart';
-import '../common/frame_sync.dart';
-import '../common/geometry.dart';
-import '../common/gesture.dart';
-import '../common/health.dart';
-import '../common/layer_tree.dart';
+import '../common/handler_factory.dart';
 import '../common/message.dart';
-import '../common/render_tree.dart';
-import '../common/request_data.dart';
-import '../common/semantics.dart';
-import '../common/text.dart';
-import '../common/wait.dart';
 import '_extension_io.dart' if (dart.library.html) '_extension_web.dart';
-import 'wait_conditions.dart';
 
 const String _extensionMethodName = 'driver';
-const String _extensionMethod = 'ext.flutter.$_extensionMethodName';
 
 /// Signature for the handler passed to [enableFlutterDriverExtension].
 ///
@@ -44,16 +32,17 @@
 typedef DataHandler = Future<String> Function(String? message);
 
 class _DriverBinding extends BindingBase with SchedulerBinding, ServicesBinding, GestureBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
-  _DriverBinding(this._handler, this._silenceErrors, this.finders);
+  _DriverBinding(this._handler, this._silenceErrors, this.finders, this.commands);
 
   final DataHandler? _handler;
   final bool _silenceErrors;
   final List<FinderExtension>? finders;
+  final List<CommandExtension>? commands;
 
   @override
   void initServiceExtensions() {
     super.initServiceExtensions();
-    final FlutterDriverExtension extension = FlutterDriverExtension(_handler, _silenceErrors, finders: finders ?? const <FinderExtension>[]);
+    final FlutterDriverExtension extension = FlutterDriverExtension(_handler, _silenceErrors, finders: finders ?? const <FinderExtension>[], commands: commands ?? const <CommandExtension>[]);
     registerServiceExtension(
       name: _extensionMethodName,
       callback: extension.call,
@@ -89,24 +78,36 @@
 /// will still be returned in the `response` field of the result JSON along
 /// with an `isError` boolean.
 ///
-/// The `finders` parameter are used to add custom finders, as in the following example.
+/// The `finders` and `commands` parameters are optional and used to add custom
+/// finders or commands, as in the following example.
 ///
 /// ```dart main
 /// void main() {
-///   enableFlutterDriverExtension(finders: <FinderExtension>[ SomeFinderExtension() ]);
+///   enableFlutterDriverExtension(
+///     finders: <FinderExtension>[ SomeFinderExtension() ],
+///     commands: <CommandExtension>[ SomeCommandExtension() ],
+///   );
 ///
 ///   app.main();
 /// }
 /// ```
 ///
 /// ```dart
-/// class Some extends SerializableFinder {
-///   const Some(this.title);
+/// driver.sendCommand(SomeCommand(ByValueKey('Button'), 7));
+/// ```
+///
+/// Note: SomeFinder and SomeFinderExtension must be placed in different files
+/// to avoid `dart:ui` import issue. Imports relative to `dart:ui` can't be
+/// accessed from host runner, where flutter runtime is not accessible.
+///
+/// ```dart
+/// class SomeFinder extends SerializableFinder {
+///   const SomeFinder(this.title);
 ///
 ///   final String title;
 ///
 ///   @override
-///   String get finderType => 'Some';
+///   String get finderType => 'SomeFinder';
 ///
 ///   @override
 ///   Map<String, String> serialize() => super.serialize()..addAll(<String, String>{
@@ -118,14 +119,14 @@
 /// ```dart
 /// class SomeFinderExtension extends FinderExtension {
 ///
-///  String get finderType => 'Some';
+///  String get finderType => 'SomeFinder';
 ///
 ///  SerializableFinder deserialize(Map<String, String> params, DeserializeFinderFactory finderFactory) {
-///    return Some(json['title']);
+///    return SomeFinder(json['title']);
 ///  }
 ///
 ///  Finder createFinder(SerializableFinder finder, CreateFinderFactory finderFactory) {
-///    Some someFinder = finder as Some;
+///    Some someFinder = finder as SomeFinder;
 ///
 ///    return find.byElementPredicate((Element element) {
 ///      final Widget widget = element.widget;
@@ -138,9 +139,87 @@
 /// }
 /// ```
 ///
-void enableFlutterDriverExtension({ DataHandler? handler, bool silenceErrors = false, List<FinderExtension>? finders}) {
+/// Note: SomeCommand, SomeResult and SomeCommandExtension must be placed in
+/// different files to avoid `dart:ui` import issue. Imports relative to `dart:ui`
+/// can't be accessed from host runner, where flutter runtime is not accessible.
+///
+/// ```dart
+/// class SomeCommand extends CommandWithTarget {
+///   SomeCommand(SerializableFinder finder, this.times, {Duration? timeout})
+///       : super(finder, timeout: timeout);
+///
+///   SomeCommand.deserialize(Map<String, String> json, DeserializeFinderFactory finderFactory)
+///       : times = int.parse(json['times']!),
+///         super.deserialize(json, finderFactory);
+///
+///   @override
+///   Map<String, String> serialize() {
+///     return super.serialize()..addAll(<String, String>{'times': '$times'});
+///   }
+///
+///   @override
+///   String get kind => 'SomeCommand';
+///
+///   final int times;
+/// }
+///```
+///
+/// ```dart
+/// class SomeCommandResult extends Result {
+///   const SomeCommandResult(this.resultParam);
+///
+///   final String resultParam;
+///
+///   @override
+///   Map<String, dynamic> toJson() {
+///     return <String, dynamic>{
+///       'resultParam': resultParam,
+///     };
+///   }
+/// }
+/// ```
+///
+/// ```dart
+/// class SomeCommandExtension extends CommandExtension {
+///   @override
+///   String get commandKind => 'SomeCommand';
+///
+///   @override
+///   Future<Result> call(Command command, WidgetController prober, CreateFinderFactory finderFactory, CommandHandlerFactory handlerFactory) async {
+///     final SomeCommand someCommand = command as SomeCommand;
+///
+///     // Deserialize [Finder]:
+///     final Finder finder = finderFactory.createFinder(stubCommand.finder);
+///
+///     // Wait for [Element]:
+///     handlerFactory.waitForElement(finder);
+///
+///     // Alternatively, wait for [Element] absence:
+///     handlerFactory.waitForAbsentElement(finder);
+///
+///     // Submit known [Command]s:
+///     for (int index = 0; i < someCommand.times; index++) {
+///       await handlerFactory.handleCommand(Tap(someCommand.finder), prober, finderFactory);
+///     }
+///
+///     // Alternatively, use [WidgetController]:
+///     for (int index = 0; i < stubCommand.times; index++) {
+///       await prober.tap(finder);
+///     }
+///
+///     return const SomeCommandResult('foo bar');
+///   }
+///
+///   @override
+///   Command deserialize(Map<String, String> params, DeserializeFinderFactory finderFactory, DeserializeCommandFactory commandFactory) {
+///     return SomeCommand.deserialize(params, finderFactory);
+///   }
+/// }
+/// ```
+///
+void enableFlutterDriverExtension({ DataHandler? handler, bool silenceErrors = false, List<FinderExtension>? finders, List<CommandExtension>? commands}) {
   assert(WidgetsBinding.instance == null);
-  _DriverBinding(handler, silenceErrors, finders ?? <FinderExtension>[]);
+  _DriverBinding(handler, silenceErrors, finders ?? <FinderExtension>[], commands ?? <CommandExtension>[]);
   assert(WidgetsBinding.instance is _DriverBinding);
 }
 
@@ -150,107 +229,119 @@
 /// Signature for functions that deserialize a JSON map to a command object.
 typedef CommandDeserializerCallback = Command Function(Map<String, String> params);
 
-/// Used to expand the new Finder
+/// Used to expand the new [Finder].
 abstract class FinderExtension {
 
   /// Identifies the type of finder to be used by the driver extension.
   String get finderType;
 
   /// Deserializes the finder from JSON generated by [SerializableFinder.serialize].
-  /// [finderFactory] could be used to deserialize nested finders.
+  ///
+  /// Use [finderFactory] to deserialize nested [Finder]s.
+  ///
+  /// See also:
+  ///   * [Ancestor], a finder that uses other [Finder]s as parameters.
   SerializableFinder deserialize(Map<String, String> params, DeserializeFinderFactory finderFactory);
 
   /// Signature for functions that run the given finder and return the [Element]
   /// found, if any, or null otherwise.
-  /// [finderFactory] could be used to create nested finders.
+  ///
+  /// Call [finderFactory] to create known, nested [Finder]s from [SerializableFinder]s.
   Finder createFinder(SerializableFinder finder, CreateFinderFactory finderFactory);
 }
 
+/// Used to expand the new [Command].
+///
+/// See also:
+///   * [CommandWithTarget], a base class for [Command]s with [Finder]s.
+abstract class CommandExtension {
+
+  /// Identifies the type of command to be used by the driver extension.
+  String get commandKind;
+
+  /// Deserializes the command from JSON generated by [Command.serialize].
+  ///
+  /// Use [finderFactory] to deserialize nested [Finder]s.
+  /// Usually used for [CommandWithTarget]s.
+  ///
+  /// Call [commandFactory] to deserialize commands specified as parameters.
+  ///
+  /// See also:
+  ///   * [CommandWithTarget], a base class for commands with target finders.
+  ///   * [Tap], a command that uses [Finder]s as parameter.
+  Command deserialize(Map<String, String> params, DeserializeFinderFactory finderFactory, DeserializeCommandFactory commandFactory);
+
+  /// Calls action for given [command].
+  /// Returns action [Result].
+  /// Invoke [prober] functions to perform widget actions.
+  /// Use [finderFactory] to create [Finder]s from [SerializableFinder].
+  /// Call [handlerFactory] to invoke other [Command]s or [CommandWithTarget]s.
+  ///
+  /// The following example shows invoking nested command with [handlerFactory].
+  ///
+  /// ```dart
+  /// @override
+  /// Future<Result> call(Command command, WidgetController prober, CreateFinderFactory finderFactory, CommandHandlerFactory handlerFactory) async {
+  ///   final StubNestedCommand stubCommand = command as StubNestedCommand;
+  ///   for (int index = 0; i < stubCommand.times; index++) {
+  ///     await handlerFactory.handleCommand(Tap(stubCommand.finder), prober, finderFactory);
+  ///   }
+  ///   return const StubCommandResult('stub response');
+  /// }
+  /// ```
+  ///
+  /// Check the example below for direct [WidgetController] usage with [prober]:
+  ///
+  /// ```dart
+  ///   @override
+  /// Future<Result> call(Command command, WidgetController prober, CreateFinderFactory finderFactory, CommandHandlerFactory handlerFactory) async {
+  ///   final StubProberCommand stubCommand = command as StubProberCommand;
+  ///   for (int index = 0; i < stubCommand.times; index++) {
+  ///     await prober.tap(finderFactory.createFinder(stubCommand.finder));
+  ///   }
+  ///   return const StubCommandResult('stub response');
+  /// }
+  /// ```
+  Future<Result> call(Command command, WidgetController prober, CreateFinderFactory finderFactory, CommandHandlerFactory handlerFactory);
+}
+
 /// The class that manages communication between a Flutter Driver test and the
 /// application being remote-controlled, on the application side.
 ///
 /// This is not normally used directly. It is instantiated automatically when
 /// calling [enableFlutterDriverExtension].
 @visibleForTesting
-class FlutterDriverExtension with DeserializeFinderFactory, CreateFinderFactory {
+class FlutterDriverExtension with DeserializeFinderFactory, CreateFinderFactory, DeserializeCommandFactory, CommandHandlerFactory {
   /// Creates an object to manage a Flutter Driver connection.
   FlutterDriverExtension(
     this._requestDataHandler,
     this._silenceErrors, {
     List<FinderExtension> finders = const <FinderExtension>[],
+    List<CommandExtension> commands = const <CommandExtension>[],
   }) : assert(finders != null) {
-    _testTextInput.register();
-
-    _commandHandlers.addAll(<String, CommandHandlerCallback>{
-      'get_health': _getHealth,
-      'get_layer_tree': _getLayerTree,
-      'get_render_tree': _getRenderTree,
-      'enter_text': _enterText,
-      'get_text': _getText,
-      'request_data': _requestData,
-      'scroll': _scroll,
-      'scrollIntoView': _scrollIntoView,
-      'set_frame_sync': _setFrameSync,
-      'set_semantics': _setSemantics,
-      'set_text_entry_emulation': _setTextEntryEmulation,
-      'tap': _tap,
-      'waitFor': _waitFor,
-      'waitForAbsent': _waitForAbsent,
-      'waitForCondition': _waitForCondition,
-      'waitUntilNoTransientCallbacks': _waitUntilNoTransientCallbacks,
-      'waitUntilNoPendingFrame': _waitUntilNoPendingFrame,
-      'waitUntilFirstFrameRasterized': _waitUntilFirstFrameRasterized,
-      'get_semantics_id': _getSemanticsId,
-      'get_offset': _getOffset,
-      'get_diagnostics_tree': _getDiagnosticsTree,
-    });
-
-    _commandDeserializers.addAll(<String, CommandDeserializerCallback>{
-      'get_health': (Map<String, String> params) => GetHealth.deserialize(params),
-      'get_layer_tree': (Map<String, String> params) => GetLayerTree.deserialize(params),
-      'get_render_tree': (Map<String, String> params) => GetRenderTree.deserialize(params),
-      'enter_text': (Map<String, String> params) => EnterText.deserialize(params),
-      'get_text': (Map<String, String> params) => GetText.deserialize(params, this),
-      'request_data': (Map<String, String> params) => RequestData.deserialize(params),
-      'scroll': (Map<String, String> params) => Scroll.deserialize(params, this),
-      'scrollIntoView': (Map<String, String> params) => ScrollIntoView.deserialize(params, this),
-      'set_frame_sync': (Map<String, String> params) => SetFrameSync.deserialize(params),
-      'set_semantics': (Map<String, String> params) => SetSemantics.deserialize(params),
-      'set_text_entry_emulation': (Map<String, String> params) => SetTextEntryEmulation.deserialize(params),
-      'tap': (Map<String, String> params) => Tap.deserialize(params, this),
-      'waitFor': (Map<String, String> params) => WaitFor.deserialize(params, this),
-      'waitForAbsent': (Map<String, String> params) => WaitForAbsent.deserialize(params, this),
-      'waitForCondition': (Map<String, String> params) => WaitForCondition.deserialize(params),
-      'waitUntilNoTransientCallbacks': (Map<String, String> params) => WaitUntilNoTransientCallbacks.deserialize(params),
-      'waitUntilNoPendingFrame': (Map<String, String> params) => WaitUntilNoPendingFrame.deserialize(params),
-      'waitUntilFirstFrameRasterized': (Map<String, String> params) => WaitUntilFirstFrameRasterized.deserialize(params),
-      'get_semantics_id': (Map<String, String> params) => GetSemanticsId.deserialize(params, this),
-      'get_offset': (Map<String, String> params) => GetOffset.deserialize(params, this),
-      'get_diagnostics_tree': (Map<String, String> params) => GetDiagnosticsTree.deserialize(params, this),
-    });
+    registerTextInput();
 
     for(final FinderExtension finder in finders) {
       _finderExtensions[finder.finderType] = finder;
     }
+
+    for(final CommandExtension command in commands) {
+      _commandExtensions[command.commandKind] = command;
+    }
   }
 
-  final TestTextInput _testTextInput = TestTextInput();
+  final WidgetController _prober = LiveWidgetController(WidgetsBinding.instance!);
 
   final DataHandler? _requestDataHandler;
+
   final bool _silenceErrors;
 
   void _log(String message) {
     driverLog('FlutterDriverExtension', message);
   }
 
-  final WidgetController _prober = LiveWidgetController(WidgetsBinding.instance!);
-  final Map<String, CommandHandlerCallback> _commandHandlers = <String, CommandHandlerCallback>{};
-  final Map<String, CommandDeserializerCallback> _commandDeserializers = <String, CommandDeserializerCallback>{};
   final Map<String, FinderExtension> _finderExtensions = <String, FinderExtension>{};
-
-  /// With [_frameSync] enabled, Flutter Driver will wait to perform an action
-  /// until there are no pending frames in the app under test.
-  bool _frameSync = true;
+  final Map<String, CommandExtension> _commandExtensions = <String, CommandExtension>{};
 
   /// Processes a driver command configured by [params] and returns a result
   /// as an arbitrary JSON object.
@@ -266,15 +357,10 @@
   Future<Map<String, dynamic>> call(Map<String, String> params) async {
     final String commandKind = params['command']!;
     try {
-      final CommandHandlerCallback commandHandler = _commandHandlers[commandKind]!;
-      final CommandDeserializerCallback commandDeserializer =
-          _commandDeserializers[commandKind]!;
-      if (commandHandler == null || commandDeserializer == null)
-        throw 'Extension $_extensionMethod does not support command $commandKind';
-      final Command command = commandDeserializer(params);
+      final Command command = deserializeCommand(params, this);
       assert(WidgetsBinding.instance!.isRootWidgetAttached || !command.requiresRootWidgetAttached,
           'No root widget is attached; have you remembered to call runApp()?');
-      Future<Result?> responseFuture = commandHandler(command);
+      Future<Result?> responseFuture = handleCommand(command, _prober, this);
       if (command.timeout != null)
         responseFuture = responseFuture.timeout(command.timeout ?? Duration.zero);
       final Result? response = await responseFuture;
@@ -298,65 +384,6 @@
     };
   }
 
-  Future<Health> _getHealth(Command command) async => const Health(HealthStatus.ok);
-
-  Future<LayerTree> _getLayerTree(Command command) async {
-    return LayerTree(RendererBinding.instance?.renderView.debugLayer?.toStringDeep());
-  }
-
-  Future<RenderTree> _getRenderTree(Command command) async {
-    return RenderTree(RendererBinding.instance?.renderView.toStringDeep());
-  }
-
-  // This can be used to wait for the first frame being rasterized during app launch.
-  @Deprecated(
-    'This method has been deprecated in favor of _waitForCondition. '
-    'This feature was deprecated after v1.9.3.'
-  )
-  Future<Result?> _waitUntilFirstFrameRasterized(Command command) async {
-    await WidgetsBinding.instance!.waitUntilFirstFrameRasterized;
-    return null;
-  }
-
-  // Waits until at the end of a frame the provided [condition] is [true].
-  Future<void> _waitUntilFrame(bool condition(), [ Completer<void>? completer ]) {
-    completer ??= Completer<void>();
-    if (!condition()) {
-      SchedulerBinding.instance!.addPostFrameCallback((Duration timestamp) {
-        _waitUntilFrame(condition, completer);
-      });
-    } else {
-      completer.complete();
-    }
-    return completer.future;
-  }
-
-  /// Runs `finder` repeatedly until it finds one or more [Element]s.
-  Future<Finder> _waitForElement(Finder finder) async {
-    if (_frameSync)
-      await _waitUntilFrame(() => SchedulerBinding.instance!.transientCallbackCount == 0);
-
-    await _waitUntilFrame(() => finder.evaluate().isNotEmpty);
-
-    if (_frameSync)
-      await _waitUntilFrame(() => SchedulerBinding.instance!.transientCallbackCount == 0);
-
-    return finder;
-  }
-
-  /// Runs `finder` repeatedly until it finds zero [Element]s.
-  Future<Finder> _waitForAbsentElement(Finder finder) async {
-    if (_frameSync)
-      await _waitUntilFrame(() => SchedulerBinding.instance!.transientCallbackCount == 0);
-
-    await _waitUntilFrame(() => finder.evaluate().isEmpty);
-
-    if (_frameSync)
-      await _waitUntilFrame(() => SchedulerBinding.instance!.transientCallbackCount == 0);
-
-    return finder;
-  }
-
   @override
   SerializableFinder deserializeFinder(Map<String, String> json) {
     final String? finderType = json['finderType'];
@@ -369,258 +396,37 @@
 
   @override
   Finder createFinder(SerializableFinder finder) {
-    if (_finderExtensions.containsKey(finder.finderType)) {
-      return _finderExtensions[finder.finderType]!.createFinder(finder, this);
+    final String finderType = finder.finderType;
+    if (_finderExtensions.containsKey(finderType)) {
+      return _finderExtensions[finderType]!.createFinder(finder, this);
     }
 
     return super.createFinder(finder);
   }
 
-  Future<TapResult> _tap(Command command) async {
-    final Tap tapCommand = command as Tap;
-    final Finder computedFinder = await _waitForElement(
-      createFinder(tapCommand.finder).hitTestable()
-    );
-    await _prober.tap(computedFinder);
-    return const TapResult();
-  }
-
-  Future<WaitForResult> _waitFor(Command command) async {
-    final WaitFor waitForCommand = command as WaitFor;
-    await _waitForElement(createFinder(waitForCommand.finder));
-    return const WaitForResult();
-  }
-
-  Future<WaitForAbsentResult> _waitForAbsent(Command command) async {
-    final WaitForAbsent waitForAbsentCommand = command as WaitForAbsent;
-    await _waitForAbsentElement(createFinder(waitForAbsentCommand.finder));
-    return const WaitForAbsentResult();
-  }
-
-  Future<Result?> _waitForCondition(Command command) async {
-    assert(command != null);
-    final WaitForCondition waitForConditionCommand = command as WaitForCondition;
-    final WaitCondition condition = deserializeCondition(waitForConditionCommand.condition);
-    await condition.wait();
-    return null;
-  }
-
-  @Deprecated(
-    'This method has been deprecated in favor of _waitForCondition. '
-    'This feature was deprecated after v1.9.3.'
-  )
-  Future<Result?> _waitUntilNoTransientCallbacks(Command command) async {
-    if (SchedulerBinding.instance!.transientCallbackCount != 0)
-      await _waitUntilFrame(() => SchedulerBinding.instance!.transientCallbackCount == 0);
-    return null;
-  }
-
-  /// Returns a future that waits until no pending frame is scheduled (frame is synced).
-  ///
-  /// Specifically, it checks:
-  /// * Whether the count of transient callbacks is zero.
-  /// * Whether there's no pending request for scheduling a new frame.
-  ///
-  /// We consider the frame is synced when both conditions are met.
-  ///
-  /// This method relies on a Flutter Driver mechanism called "frame sync",
-  /// which waits for transient animations to finish. Persistent animations will
-  /// cause this to wait forever.
-  ///
-  /// If a test needs to interact with the app while animations are running, it
-  /// should avoid this method and instead disable the frame sync using
-  /// `set_frame_sync` method. See [FlutterDriver.runUnsynchronized] for more
-  /// details on how to do this. Note, disabling frame sync will require the
-  /// test author to use some other method to avoid flakiness.
-  ///
-  /// This method has been deprecated in favor of [_waitForCondition].
-  @Deprecated(
-    'This method has been deprecated in favor of _waitForCondition. '
-    'This feature was deprecated after v1.9.3.'
-  )
-  Future<Result?> _waitUntilNoPendingFrame(Command command) async {
-    await _waitUntilFrame(() {
-      return SchedulerBinding.instance!.transientCallbackCount == 0
-          && !SchedulerBinding.instance!.hasScheduledFrame;
-    });
-    return null;
-  }
-
-  Future<GetSemanticsIdResult> _getSemanticsId(Command command) async {
-    final GetSemanticsId semanticsCommand = command as GetSemanticsId;
-    final Finder target = await _waitForElement(createFinder(semanticsCommand.finder));
-    final Iterable<Element> elements = target.evaluate();
-    if (elements.length > 1) {
-      throw StateError('Found more than one element with the same ID: $elements');
-    }
-    final Element element = elements.single;
-    RenderObject? renderObject = element.renderObject;
-    SemanticsNode? node;
-    while (renderObject != null && node == null) {
-      node = renderObject.debugSemantics;
-      renderObject = renderObject.parent as RenderObject?;
-    }
-    if (node == null)
-      throw StateError('No semantics data found');
-    return GetSemanticsIdResult(node.id);
-  }
-
-  Future<GetOffsetResult> _getOffset(Command command) async {
-    final GetOffset getOffsetCommand = command as GetOffset;
-    final Finder finder = await _waitForElement(createFinder(getOffsetCommand.finder));
-    final Element element = finder.evaluate().single;
-    final RenderBox box = (element.renderObject as RenderBox?)!;
-    Offset localPoint;
-    switch (getOffsetCommand.offsetType) {
-      case OffsetType.topLeft:
-        localPoint = Offset.zero;
-        break;
-      case OffsetType.topRight:
-        localPoint = box.size.topRight(Offset.zero);
-        break;
-      case OffsetType.bottomLeft:
-        localPoint = box.size.bottomLeft(Offset.zero);
-        break;
-      case OffsetType.bottomRight:
-        localPoint = box.size.bottomRight(Offset.zero);
-        break;
-      case OffsetType.center:
-        localPoint = box.size.center(Offset.zero);
-        break;
-    }
-    final Offset globalPoint = box.localToGlobal(localPoint);
-    return GetOffsetResult(dx: globalPoint.dx, dy: globalPoint.dy);
-  }
-
-  Future<DiagnosticsTreeResult> _getDiagnosticsTree(Command command) async {
-    final GetDiagnosticsTree diagnosticsCommand = command as GetDiagnosticsTree;
-    final Finder finder = await _waitForElement(createFinder(diagnosticsCommand.finder));
-    final Element element = finder.evaluate().single;
-    DiagnosticsNode diagnosticsNode;
-    switch (diagnosticsCommand.diagnosticsType) {
-      case DiagnosticsType.renderObject:
-        diagnosticsNode = element.renderObject!.toDiagnosticsNode();
-        break;
-      case DiagnosticsType.widget:
-        diagnosticsNode = element.toDiagnosticsNode();
-        break;
-    }
-    return DiagnosticsTreeResult(diagnosticsNode.toJsonMap(DiagnosticsSerializationDelegate(
-      subtreeDepth: diagnosticsCommand.subtreeDepth,
-      includeProperties: diagnosticsCommand.includeProperties,
-    )));
-  }
-
-  Future<ScrollResult> _scroll(Command command) async {
-    final Scroll scrollCommand = command as Scroll;
-    final Finder target = await _waitForElement(createFinder(scrollCommand.finder));
-    final int totalMoves = scrollCommand.duration.inMicroseconds * scrollCommand.frequency ~/ Duration.microsecondsPerSecond;
-    final Offset delta = Offset(scrollCommand.dx, scrollCommand.dy) / totalMoves.toDouble();
-    final Duration pause = scrollCommand.duration ~/ totalMoves;
-    final Offset startLocation = _prober.getCenter(target);
-    Offset currentLocation = startLocation;
-    final TestPointer pointer = TestPointer(1);
-    _prober.binding.handlePointerEvent(pointer.down(startLocation));
-    await Future<void>.value(); // so that down and move don't happen in the same microtask
-    for (int moves = 0; moves < totalMoves; moves += 1) {
-      currentLocation = currentLocation + delta;
-      _prober.binding.handlePointerEvent(pointer.move(currentLocation));
-      await Future<void>.delayed(pause);
-    }
-    _prober.binding.handlePointerEvent(pointer.up());
-
-    return const ScrollResult();
-  }
-
-  Future<ScrollResult> _scrollIntoView(Command command) async {
-    final ScrollIntoView scrollIntoViewCommand = command as ScrollIntoView;
-    final Finder target = await _waitForElement(createFinder(scrollIntoViewCommand.finder));
-    await Scrollable.ensureVisible(target.evaluate().single, duration: const Duration(milliseconds: 100), alignment: scrollIntoViewCommand.alignment);
-    return const ScrollResult();
-  }
-
-  Future<GetTextResult> _getText(Command command) async {
-    final GetText getTextCommand = command as GetText;
-    final Finder target = await _waitForElement(createFinder(getTextCommand.finder));
-
-    final Widget widget = target.evaluate().single.widget;
-    String? text;
-
-    if (widget.runtimeType == Text) {
-      text = (widget as Text).data;
-    } else if (widget.runtimeType == RichText) {
-      final RichText richText = widget as RichText;
-      if (richText.text.runtimeType == TextSpan) {
-        text = (richText.text as TextSpan).text;
-      }
-    } else if (widget.runtimeType == TextField) {
-      text = (widget as TextField).controller?.text;
-    } else if (widget.runtimeType == TextFormField) {
-      text = (widget as TextFormField).controller?.text;
-    } else if (widget.runtimeType == EditableText) {
-      text = (widget as EditableText).controller.text;
+  @override
+  Command deserializeCommand(Map<String, String> params, DeserializeFinderFactory finderFactory) {
+    final String? kind = params['command'];
+    if(_commandExtensions.containsKey(kind)) {
+      return _commandExtensions[kind]!.deserialize(params, finderFactory, this);
     }
 
-    if (text == null) {
-      throw UnsupportedError('Type ${widget.runtimeType.toString()} is currently not supported by getText');
+    return super.deserializeCommand(params, finderFactory);
+  }
+
+  @override
+  @protected
+  DataHandler? getDataHandler() {
+    return _requestDataHandler;
+  }
+
+  @override
+  Future<Result?> handleCommand(Command command, WidgetController prober, CreateFinderFactory finderFactory) {
+    final String kind = command.kind;
+    if(_commandExtensions.containsKey(kind)) {
+      return _commandExtensions[kind]!.call(command, prober, finderFactory, this);
     }
 
-    return GetTextResult(text);
-  }
-
-  Future<SetTextEntryEmulationResult> _setTextEntryEmulation(Command command) async {
-    final SetTextEntryEmulation setTextEntryEmulationCommand = command as SetTextEntryEmulation;
-    if (setTextEntryEmulationCommand.enabled) {
-      _testTextInput.register();
-    } else {
-      _testTextInput.unregister();
-    }
-    return const SetTextEntryEmulationResult();
-  }
-
-  Future<EnterTextResult> _enterText(Command command) async {
-    if (!_testTextInput.isRegistered) {
-      throw 'Unable to fulfill `FlutterDriver.enterText`. Text emulation is '
-            'disabled. You can enable it using `FlutterDriver.setTextEntryEmulation`.';
-    }
-    final EnterText enterTextCommand = command as EnterText;
-    _testTextInput.enterText(enterTextCommand.text);
-    return const EnterTextResult();
-  }
-
-  Future<RequestDataResult> _requestData(Command command) async {
-    final RequestData requestDataCommand = command as RequestData;
-    return RequestDataResult(_requestDataHandler == null
-      ? 'No requestData Extension registered'
-      : await _requestDataHandler!(requestDataCommand.message));
-  }
-
-  Future<SetFrameSyncResult> _setFrameSync(Command command) async {
-    final SetFrameSync setFrameSyncCommand = command as SetFrameSync;
-    _frameSync = setFrameSyncCommand.enabled;
-    return const SetFrameSyncResult();
-  }
-
-  SemanticsHandle? _semantics;
-  bool get _semanticsIsEnabled => RendererBinding.instance!.pipelineOwner.semanticsOwner != null;
-
-  Future<SetSemanticsResult> _setSemantics(Command command) async {
-    final SetSemantics setSemanticsCommand = command as SetSemantics;
-    final bool semanticsWasEnabled = _semanticsIsEnabled;
-    if (setSemanticsCommand.enabled && _semantics == null) {
-      _semantics = RendererBinding.instance!.pipelineOwner.ensureSemantics();
-      if (!semanticsWasEnabled) {
-        // wait for the first frame where semantics is enabled.
-        final Completer<void> completer = Completer<void>();
-        SchedulerBinding.instance!.addPostFrameCallback((Duration d) {
-          completer.complete();
-        });
-        await completer.future;
-      }
-    } else if (!setSemanticsCommand.enabled && _semantics != null) {
-      _semantics!.dispose();
-      _semantics = null;
-    }
-    return SetSemanticsResult(semanticsWasEnabled != _semanticsIsEnabled);
+    return super.handleCommand(command, prober, finderFactory);
   }
 }
diff --git a/packages/flutter_driver/test/src/real_tests/extension_test.dart b/packages/flutter_driver/test/src/real_tests/extension_test.dart
index 87d4a25..899bc16 100644
--- a/packages/flutter_driver/test/src/real_tests/extension_test.dart
+++ b/packages/flutter_driver/test/src/real_tests/extension_test.dart
@@ -20,6 +20,8 @@
 import 'package:flutter_driver/src/extension/extension.dart';
 import 'package:flutter_test/flutter_test.dart';
 
+import 'stubs/stub_command.dart';
+import 'stubs/stub_command_extension.dart';
 import 'stubs/stub_finder.dart';
 import 'stubs/stub_finder_extension.dart';
 
@@ -984,6 +986,100 @@
     });
   });
 
+  group('extension commands', () {
+    int invokes = 0;
+    final VoidCallback stubCallback = () => invokes++;
+
+    final Widget debugTree = Directionality(
+      textDirection: TextDirection.ltr,
+      child: Center(
+        child: Column(
+          children: <Widget>[
+            TextButton(
+              child: const Text('Whatever'),
+              key: const ValueKey<String>('Button'),
+              onPressed: stubCallback,
+            ),
+          ],
+        ),
+      ),
+    );
+
+    setUp(() {
+      invokes = 0;
+    });
+
+    testWidgets('unknown extension command', (WidgetTester tester) async {
+      final FlutterDriverExtension driverExtension = FlutterDriverExtension(
+        (String arg) async => '',
+        true,
+        commands: <CommandExtension>[],
+      );
+
+      Future<Map<String, dynamic>> invokeCommand(SerializableFinder finder, int times) async {
+        final Map<String, String> arguments = StubNestedCommand(finder, times).serialize();
+        return await driverExtension.call(arguments);
+      }
+
+      await tester.pumpWidget(debugTree);
+
+      final Map<String, dynamic> result = await invokeCommand(ByValueKey('Button'), 10);
+      expect(result['isError'], true);
+      expect(result['response'] is String, true);
+      expect(result['response'] as String, contains('Unsupported command kind StubNestedCommand'));
+    });
+
+    testWidgets('nested command', (WidgetTester tester) async {
+      final FlutterDriverExtension driverExtension = FlutterDriverExtension(
+        (String arg) async => '',
+        true,
+        commands: <CommandExtension>[
+          StubNestedCommandExtension(),
+        ],
+      );
+
+      Future<StubCommandResult> invokeCommand(SerializableFinder finder, int times) async {
+        await driverExtension.call(const SetFrameSync(false).serialize()); // disable frame sync for test to avoid lock
+        final Map<String, String> arguments = StubNestedCommand(finder, times, timeout: const Duration(seconds: 1)).serialize();
+        final Map<String, dynamic> response = await driverExtension.call(arguments);
+        final Map<String, dynamic> commandResponse = response['response'] as Map<String, dynamic>;
+        return StubCommandResult(commandResponse['resultParam'] as String);
+      }
+
+      await tester.pumpWidget(debugTree);
+
+      const int times = 10;
+      final StubCommandResult result = await invokeCommand(ByValueKey('Button'), times);
+      expect(result.resultParam, 'stub response');
+      expect(invokes, times);
+    });
+
+    testWidgets('prober command', (WidgetTester tester) async {
+      final FlutterDriverExtension driverExtension = FlutterDriverExtension(
+        (String arg) async => '',
+        true,
+        commands: <CommandExtension>[
+          StubProberCommandExtension(),
+        ],
+      );
+
+      Future<StubCommandResult> invokeCommand(SerializableFinder finder, int times) async {
+        await driverExtension.call(const SetFrameSync(false).serialize()); // disable frame sync for test to avoid lock
+        final Map<String, String> arguments = StubProberCommand(finder, times, timeout: const Duration(seconds: 1)).serialize();
+        final Map<String, dynamic> response = await driverExtension.call(arguments);
+        final Map<String, dynamic> commandResponse = response['response'] as Map<String, dynamic>;
+        return StubCommandResult(commandResponse['resultParam'] as String);
+      }
+
+      await tester.pumpWidget(debugTree);
+
+      const int times = 10;
+      final StubCommandResult result = await invokeCommand(ByValueKey('Button'), times);
+      expect(result.resultParam, 'stub response');
+      expect(invokes, times);
+    });
+  });
+
   group('waitUntilFrameSync', () {
     FlutterDriverExtension driverExtension;
     Map<String, dynamic> result;
diff --git a/packages/flutter_driver/test/src/real_tests/find_test.dart b/packages/flutter_driver/test/src/real_tests/find_test.dart
index df41576..fccf5b5 100644
--- a/packages/flutter_driver/test/src/real_tests/find_test.dart
+++ b/packages/flutter_driver/test/src/real_tests/find_test.dart
@@ -4,6 +4,7 @@
 
 // @dart = 2.8
 
+import 'package:flutter_driver/driver_extension.dart';
 import 'package:flutter_driver/flutter_driver.dart';
 import 'package:flutter_driver/src/common/find.dart';
 import 'package:mockito/mockito.dart';
diff --git a/packages/flutter_driver/test/src/real_tests/stubs/stub_command.dart b/packages/flutter_driver/test/src/real_tests/stubs/stub_command.dart
new file mode 100644
index 0000000..779bd15
--- /dev/null
+++ b/packages/flutter_driver/test/src/real_tests/stubs/stub_command.dart
@@ -0,0 +1,58 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter_driver/driver_extension.dart';
+import 'package:flutter_driver/flutter_driver.dart';
+
+class StubNestedCommand extends CommandWithTarget {
+  StubNestedCommand(SerializableFinder finder, this.times, {Duration? timeout})
+      : super(finder, timeout: timeout);
+
+  StubNestedCommand.deserialize(
+      Map<String, String> json, DeserializeFinderFactory finderFactory)
+      : times = int.parse(json['times']!),
+        super.deserialize(json, finderFactory);
+
+  @override
+  Map<String, String> serialize() {
+    return super.serialize()..addAll(<String, String>{'times': '$times'});
+  }
+
+  @override
+  String get kind => 'StubNestedCommand';
+
+  final int times;
+}
+
+class StubProberCommand extends CommandWithTarget {
+  StubProberCommand(SerializableFinder finder, this.times, {Duration? timeout})
+      : super(finder, timeout: timeout);
+
+  StubProberCommand.deserialize(Map<String, String> json, DeserializeFinderFactory finderFactory)
+      : times = int.parse(json['times']!),
+        super.deserialize(json, finderFactory);
+
+  @override
+  Map<String, String> serialize() {
+    return super.serialize()..addAll(<String, String>{'times': '$times'});
+  }
+
+  @override
+  String get kind => 'StubProberCommand';
+
+  final int times;
+}
+
+class StubCommandResult extends Result {
+  const StubCommandResult(this.resultParam);
+
+  final String resultParam;
+
+  @override
+  Map<String, dynamic> toJson() {
+    return <String, dynamic>{
+      'resultParam': resultParam,
+    };
+  }
+}
diff --git a/packages/flutter_driver/test/src/real_tests/stubs/stub_command_extension.dart b/packages/flutter_driver/test/src/real_tests/stubs/stub_command_extension.dart
new file mode 100644
index 0000000..20d9e64
--- /dev/null
+++ b/packages/flutter_driver/test/src/real_tests/stubs/stub_command_extension.dart
@@ -0,0 +1,51 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter_driver/driver_extension.dart';
+import 'package:flutter_driver/flutter_driver.dart';
+import 'package:flutter_driver/src/common/message.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import 'stub_command.dart';
+
+class StubNestedCommandExtension extends CommandExtension {
+  @override
+  String get commandKind => 'StubNestedCommand';
+
+  @override
+  Future<Result> call(Command command, WidgetController prober, CreateFinderFactory finderFactory, CommandHandlerFactory handlerFactory) async {
+    final StubNestedCommand stubCommand = command as StubNestedCommand;
+    handlerFactory.waitForElement(finderFactory.createFinder(stubCommand.finder));
+    for (int index = 0; index < stubCommand.times; index++) {
+      await handlerFactory.handleCommand(Tap(stubCommand.finder), prober, finderFactory);
+    }
+    return const StubCommandResult('stub response');
+  }
+
+  @override
+  Command deserialize(Map<String, String> params, DeserializeFinderFactory finderFactory, DeserializeCommandFactory commandFactory) {
+    return StubNestedCommand.deserialize(params, finderFactory);
+  }
+}
+
+class StubProberCommandExtension extends CommandExtension {
+  @override
+  String get commandKind => 'StubProberCommand';
+
+  @override
+  Future<Result> call(Command command, WidgetController prober, CreateFinderFactory finderFactory, CommandHandlerFactory handlerFactory) async {
+    final StubProberCommand stubCommand = command as StubProberCommand;
+    final Finder finder = finderFactory.createFinder(stubCommand.finder);
+    handlerFactory.waitForElement(finder);
+    for (int index = 0; index < stubCommand.times; index++) {
+      await prober.tap(finder);
+    }
+    return const StubCommandResult('stub response');
+  }
+
+  @override
+  Command deserialize(Map<String, String> params, DeserializeFinderFactory finderFactory, DeserializeCommandFactory commandFactory) {
+    return StubProberCommand.deserialize(params, finderFactory);
+  }
+}
diff --git a/packages/flutter_driver/test/src/real_tests/stubs/stub_finder_extension.dart b/packages/flutter_driver/test/src/real_tests/stubs/stub_finder_extension.dart
index 6b5694e..b24e554 100644
--- a/packages/flutter_driver/test/src/real_tests/stubs/stub_finder_extension.dart
+++ b/packages/flutter_driver/test/src/real_tests/stubs/stub_finder_extension.dart
@@ -3,9 +3,9 @@
 // found in the LICENSE file.
 
 import 'package:flutter/src/widgets/framework.dart';
-import 'package:flutter_driver/driver_extension.dart';
-import 'package:flutter_driver/src/common/create_finder_factory.dart';
 import 'package:flutter_test/src/finders.dart';
+import 'package:flutter_driver/driver_extension.dart';
+import 'package:flutter_driver/src/common/handler_factory.dart';
 import 'package:flutter_driver/src/common/find.dart';
 
 import 'stub_finder.dart';
diff --git a/packages/flutter_test/lib/src/_matchers_io.dart b/packages/flutter_test/lib/src/_matchers_io.dart
index 9c1f022..80d6919 100644
--- a/packages/flutter_test/lib/src/_matchers_io.dart
+++ b/packages/flutter_test/lib/src/_matchers_io.dart
@@ -49,27 +49,31 @@
 
   @override
   Future<String?> matchAsync(dynamic item) async {
-    Future<ui.Image> imageFuture;
-    if (item is Future<ui.Image>) {
+    Future<ui.Image?> imageFuture;
+    if (item is Future<ui.Image?>) {
       imageFuture = item;
     } else if (item is ui.Image) {
       imageFuture = Future<ui.Image>.value(item);
-    } else {
-      final Finder finder = item as Finder;
-      final Iterable<Element> elements = finder.evaluate();
+    } else if (item is Finder) {
+      final Iterable<Element> elements = item.evaluate();
       if (elements.isEmpty) {
         return 'could not be rendered because no widget was found';
       } else if (elements.length > 1) {
         return 'matched too many widgets';
       }
       imageFuture = captureImage(elements.single);
+    } else {
+      throw 'must provide a Finder, Image, or Future<Image>';
     }
 
     final Uri testNameUri = goldenFileComparator.getTestUri(key, version);
 
     final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized() as TestWidgetsFlutterBinding;
     return binding.runAsync<String?>(() async {
-      final ui.Image image = await imageFuture;
+      final ui.Image? image = await imageFuture;
+      if (image == null) {
+        throw 'Future<Image> completed to null';
+      }
       final ByteData? bytes = await image.toByteData(format: ui.ImageByteFormat.png);
       if (bytes == null)
         return 'could not encode screenshot.';
diff --git a/packages/flutter_test/lib/src/binding.dart b/packages/flutter_test/lib/src/binding.dart
index 0b76a0b..116746a 100644
--- a/packages/flutter_test/lib/src/binding.dart
+++ b/packages/flutter_test/lib/src/binding.dart
@@ -1011,17 +1011,19 @@
 
     addTime(additionalTime);
 
-    return realAsyncZone.run<Future<T>>(() {
+    return realAsyncZone.run<Future<T?>>(() async {
       _pendingAsyncTasks = Completer<void>();
-      return callback().catchError((Object exception, StackTrace stack) {
+      T? result;
+      try {
+        result = await callback();
+      } catch (exception, stack) {
         FlutterError.reportError(FlutterErrorDetails(
           exception: exception,
           stack: stack,
           library: 'Flutter test framework',
           context: ErrorDescription('while running async test code'),
         ));
-        return null;
-      }).whenComplete(() {
+      } finally {
         // We complete the _pendingAsyncTasks future successfully regardless of
         // whether an exception occurred because in the case of an exception,
         // we already reported the exception to FlutterError. Moreover,
@@ -1029,7 +1031,8 @@
         // exception due to zone error boundaries.
         _pendingAsyncTasks!.complete();
         _pendingAsyncTasks = null;
-      });
+      }
+      return result;
     });
   }
 
diff --git a/packages/flutter_test/lib/src/controller.dart b/packages/flutter_test/lib/src/controller.dart
index 63c35a7..b0b2c96 100644
--- a/packages/flutter_test/lib/src/controller.dart
+++ b/packages/flutter_test/lib/src/controller.dart
@@ -841,15 +841,18 @@
   /// key press. To simulate individual down and/or up events, see
   /// [sendKeyDownEvent] and [sendKeyUpEvent].
   ///
+  /// Returns true if the key down event was handled by the framework.
+  ///
   /// See also:
   ///
   ///  - [sendKeyDownEvent] to simulate only a key down event.
   ///  - [sendKeyUpEvent] to simulate only a key up event.
-  Future<void> sendKeyEvent(LogicalKeyboardKey key, { String platform = 'android' }) async {
+  Future<bool> sendKeyEvent(LogicalKeyboardKey key, { String platform = 'android' }) async {
     assert(platform != null);
-    await simulateKeyDownEvent(key, platform: platform);
+    final bool handled = await simulateKeyDownEvent(key, platform: platform);
     // Internally wrapped in async guard.
-    return simulateKeyUpEvent(key, platform: platform);
+    await simulateKeyUpEvent(key, platform: platform);
+    return handled;
   }
 
   /// Simulates sending a physical key down event through the system channel.
@@ -864,11 +867,13 @@
   ///
   /// Keys that are down when the test completes are cleared after each test.
   ///
+  /// Returns true if the key event was handled by the framework.
+  ///
   /// See also:
   ///
   ///  - [sendKeyUpEvent] to simulate the corresponding key up event.
   ///  - [sendKeyEvent] to simulate both the key up and key down in the same call.
-  Future<void> sendKeyDownEvent(LogicalKeyboardKey key, { String platform = 'android' }) async {
+  Future<bool> sendKeyDownEvent(LogicalKeyboardKey key, { String platform = 'android' }) async {
     assert(platform != null);
     // Internally wrapped in async guard.
     return simulateKeyDownEvent(key, platform: platform);
@@ -883,11 +888,13 @@
   /// [Platform.operatingSystem] to make the event appear to be from that type
   /// of system. Defaults to "android". May not be null.
   ///
+  /// Returns true if the key event was handled by the framework.
+  ///
   /// See also:
   ///
   ///  - [sendKeyDownEvent] to simulate the corresponding key down event.
   ///  - [sendKeyEvent] to simulate both the key up and key down in the same call.
-  Future<void> sendKeyUpEvent(LogicalKeyboardKey key, { String platform = 'android' }) async {
+  Future<bool> sendKeyUpEvent(LogicalKeyboardKey key, { String platform = 'android' }) async {
     assert(platform != null);
     // Internally wrapped in async guard.
     return simulateKeyUpEvent(key, platform: platform);
diff --git a/packages/flutter_test/lib/src/event_simulation.dart b/packages/flutter_test/lib/src/event_simulation.dart
index dc51a72..d633e3b 100644
--- a/packages/flutter_test/lib/src/event_simulation.dart
+++ b/packages/flutter_test/lib/src/event_simulation.dart
@@ -516,20 +516,32 @@
   ///
   /// Keys that are down when the test completes are cleared after each test.
   ///
+  /// Returns true if the key event was handled by the framework.
+  ///
   /// See also:
   ///
   ///  - [simulateKeyUpEvent] to simulate the corresponding key up event.
-  static Future<void> simulateKeyDownEvent(LogicalKeyboardKey key, {String? platform, PhysicalKeyboardKey? physicalKey}) async {
-    return TestAsyncUtils.guard<void>(() async {
+  static Future<bool> simulateKeyDownEvent(LogicalKeyboardKey key, {String? platform, PhysicalKeyboardKey? physicalKey}) async {
+    return await TestAsyncUtils.guard<bool>(() async {
       platform ??= Platform.operatingSystem;
       assert(_osIsSupported(platform!), 'Platform $platform not supported for key simulation');
 
       final Map<String, dynamic> data = getKeyData(key, platform: platform!, isDown: true, physicalKey: physicalKey);
+      bool result = false;
       await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
         SystemChannels.keyEvent.name,
         SystemChannels.keyEvent.codec.encodeMessage(data),
-            (ByteData? data) { },
+        (ByteData? data) {
+          if (data == null) {
+            return;
+          }
+          final Map<String, dynamic> decoded = SystemChannels.keyEvent.codec.decodeMessage(data) as Map<String, dynamic>;
+          if (decoded['handled'] as bool) {
+            result = true;
+          }
+        }
       );
+      return result;
     });
   }
 
@@ -543,20 +555,32 @@
   /// system. Defaults to the operating system that the test is running on. Some
   /// platforms (e.g. Windows, iOS) are not yet supported.
   ///
+  /// Returns true if the key event was handled by the framework.
+  ///
   /// See also:
   ///
   ///  - [simulateKeyDownEvent] to simulate the corresponding key down event.
-  static Future<void> simulateKeyUpEvent(LogicalKeyboardKey key, {String? platform, PhysicalKeyboardKey? physicalKey}) async {
-    return TestAsyncUtils.guard<void>(() async {
+  static Future<bool> simulateKeyUpEvent(LogicalKeyboardKey key, {String? platform, PhysicalKeyboardKey? physicalKey}) async {
+    return TestAsyncUtils.guard<bool>(() async {
       platform ??= Platform.operatingSystem;
       assert(_osIsSupported(platform!), 'Platform $platform not supported for key simulation');
 
       final Map<String, dynamic> data = getKeyData(key, platform: platform!, isDown: false, physicalKey: physicalKey);
+      bool result = false;
       await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
         SystemChannels.keyEvent.name,
         SystemChannels.keyEvent.codec.encodeMessage(data),
-            (ByteData? data) { },
+        (ByteData? data) {
+          if (data == null) {
+            return;
+          }
+          final Map<String, dynamic> decoded = SystemChannels.keyEvent.codec.decodeMessage(data) as Map<String, dynamic>;
+          if (decoded['handled'] as bool) {
+            result = true;
+          }
+        }
       );
+      return result;
     });
   }
 }
@@ -576,10 +600,12 @@
 ///
 /// Keys that are down when the test completes are cleared after each test.
 ///
+/// Returns true if the key event was handled by the framework.
+///
 /// See also:
 ///
 ///  - [simulateKeyUpEvent] to simulate the corresponding key up event.
-Future<void> simulateKeyDownEvent(LogicalKeyboardKey key, {String? platform, PhysicalKeyboardKey? physicalKey}) {
+Future<bool> simulateKeyDownEvent(LogicalKeyboardKey key, {String? platform, PhysicalKeyboardKey? physicalKey}) {
   return KeyEventSimulator.simulateKeyDownEvent(key, platform: platform, physicalKey: physicalKey);
 }
 
@@ -596,9 +622,11 @@
 /// system. Defaults to the operating system that the test is running on. Some
 /// platforms (e.g. Windows, iOS) are not yet supported.
 ///
+/// Returns true if the key event was handled by the framework.
+///
 /// See also:
 ///
 ///  - [simulateKeyDownEvent] to simulate the corresponding key down event.
-Future<void> simulateKeyUpEvent(LogicalKeyboardKey key, {String? platform, PhysicalKeyboardKey? physicalKey}) {
+Future<bool> simulateKeyUpEvent(LogicalKeyboardKey key, {String? platform, PhysicalKeyboardKey? physicalKey}) {
   return KeyEventSimulator.simulateKeyUpEvent(key, platform: platform, physicalKey: physicalKey);
 }
diff --git a/packages/flutter_test/lib/src/matchers.dart b/packages/flutter_test/lib/src/matchers.dart
index 46a15b2..0a4f912 100644
--- a/packages/flutter_test/lib/src/matchers.dart
+++ b/packages/flutter_test/lib/src/matchers.dart
@@ -468,6 +468,7 @@
   bool hasToggledState = false,
   bool isToggled = false,
   bool hasImplicitScrolling = false,
+  bool isSlider = false,
   // Actions //
   bool hasTapAction = false,
   bool hasLongPressAction = false,
@@ -519,6 +520,7 @@
     if (hasToggledState) SemanticsFlag.hasToggledState,
     if (isToggled) SemanticsFlag.isToggled,
     if (hasImplicitScrolling) SemanticsFlag.hasImplicitScrolling,
+    if (isSlider) SemanticsFlag.isSlider
   ];
 
   final List<SemanticsAction> actions = <SemanticsAction>[
diff --git a/packages/flutter_test/test/test_config/config_test_utils.dart b/packages/flutter_test/test/test_config/config_test_utils.dart
index 6c17239..d86cf7b 100644
--- a/packages/flutter_test/test/test_config/config_test_utils.dart
+++ b/packages/flutter_test/test/test_config/config_test_utils.dart
@@ -11,7 +11,7 @@
   String? expectedStringValue, {
   Map<Type, dynamic> otherExpectedValues = const <Type, dynamic>{int: isNull},
 }) {
-  final String actualStringValue = Zone.current[String] as String;
+  final String? actualStringValue = Zone.current[String] as String?;
   final Map<Type, dynamic> otherActualValues = otherExpectedValues.map<Type, dynamic>(
     (Type key, dynamic value) {
       return MapEntry<Type, dynamic>(key, Zone.current[key]);
diff --git a/packages/flutter_tools/bin/tool_backend.dart b/packages/flutter_tools/bin/tool_backend.dart
index 9df4aa0..678d10a 100644
--- a/packages/flutter_tools/bin/tool_backend.dart
+++ b/packages/flutter_tools/bin/tool_backend.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.
 
+// @dart = 2.9
+
 // Do not add package imports to this file.
 import 'dart:convert'; // ignore: dart_convert_import.
 import 'dart:io'; // ignore: dart_io_import.
diff --git a/packages/flutter_tools/build.yaml b/packages/flutter_tools/build.yaml
deleted file mode 100644
index c2d33c80..0000000
--- a/packages/flutter_tools/build.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
-targets:
-  $default:
-    builders:
-      build_web_compilers|entrypoint:
-        enabled: false
-    sources:
-      exclude:
-        - "test/data/**"
diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart
index 9645a4e..b1846b6 100644
--- a/packages/flutter_tools/lib/src/android/gradle.dart
+++ b/packages/flutter_tools/lib/src/android/gradle.dart
@@ -279,6 +279,9 @@
   } else {
     command.add('-q');
   }
+  if (!buildInfo.androidGradleDaemon) {
+    command.add('--no-daemon');
+  }
   if (globals.artifacts is LocalEngineArtifacts) {
     final LocalEngineArtifacts localEngineArtifacts = globals.artifacts as LocalEngineArtifacts;
     final Directory localEngineRepo = _getLocalEngineRepo(
@@ -592,6 +595,9 @@
   } else {
     command.add('-q');
   }
+  if (!buildInfo.androidGradleDaemon) {
+    command.add('--no-daemon');
+  }
 
   if (target != null && target.isNotEmpty) {
     command.add('-Ptarget=$target');
diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart
index 17d9b9d..16927d3 100644
--- a/packages/flutter_tools/lib/src/build_info.dart
+++ b/packages/flutter_tools/lib/src/build_info.dart
@@ -34,6 +34,7 @@
     this.packagesPath = '.packages',
     this.nullSafetyMode = NullSafetyMode.autodetect,
     this.codeSizeDirectory,
+    this.androidGradleDaemon = true,
   });
 
   final BuildMode mode;
@@ -118,6 +119,20 @@
   /// will be written for code size profiling.
   final String codeSizeDirectory;
 
+  /// Whether to enable the Gradle daemon when performing an Android build.
+  ///
+  /// Starting the daemon is the default behavior of the gradle wrapper script created
+  /// in a Flutter project. Setting this value to false will cause the tool to pass
+  /// `--no-daemon` to the gradle wrapper script, preventing it from spawning a daemon
+  /// process.
+  ///
+  /// For one-off builds or CI systems, preventing the daemon from spawning will
+  /// reduce system resource usage, at the cost of any subsequent builds starting
+  /// up slightly slower.
+  ///
+  /// The Gradle daemon may also be disabled in the Android application's properties file.
+  final bool androidGradleDaemon;
+
   static const BuildInfo debug = BuildInfo(BuildMode.debug, null, treeShakeIcons: false);
   static const BuildInfo profile = BuildInfo(BuildMode.profile, null, treeShakeIcons: kIconTreeShakerEnabledDefault);
   static const BuildInfo jitRelease = BuildInfo(BuildMode.jitRelease, null, treeShakeIcons: kIconTreeShakerEnabledDefault);
diff --git a/packages/flutter_tools/lib/src/commands/build_aar.dart b/packages/flutter_tools/lib/src/commands/build_aar.dart
index 5824a73..1f256e5 100644
--- a/packages/flutter_tools/lib/src/commands/build_aar.dart
+++ b/packages/flutter_tools/lib/src/commands/build_aar.dart
@@ -43,6 +43,7 @@
     usesTrackWidgetCreation(verboseHelp: false);
     addNullSafetyModeOptions(hide: !verboseHelp);
     addEnableExperimentation(hide: !verboseHelp);
+    addAndroidSpecificBuildOptions(hide: !verboseHelp);
     argParser
       ..addMultiOption(
         'target-platform',
diff --git a/packages/flutter_tools/lib/src/commands/build_apk.dart b/packages/flutter_tools/lib/src/commands/build_apk.dart
index 7cdee98..750fc15 100644
--- a/packages/flutter_tools/lib/src/commands/build_apk.dart
+++ b/packages/flutter_tools/lib/src/commands/build_apk.dart
@@ -33,6 +33,7 @@
     addBuildPerformanceFile(hide: !verboseHelp);
     addNullSafetyModeOptions(hide: !verboseHelp);
     usesAnalyzeSizeFlag();
+    addAndroidSpecificBuildOptions(hide: !verboseHelp);
     argParser
       ..addFlag('split-per-abi',
         negatable: false,
diff --git a/packages/flutter_tools/lib/src/commands/build_appbundle.dart b/packages/flutter_tools/lib/src/commands/build_appbundle.dart
index c84ef66..97c1ccf 100644
--- a/packages/flutter_tools/lib/src/commands/build_appbundle.dart
+++ b/packages/flutter_tools/lib/src/commands/build_appbundle.dart
@@ -33,6 +33,7 @@
     addNullSafetyModeOptions(hide: !verboseHelp);
     addEnableExperimentation(hide: !verboseHelp);
     usesAnalyzeSizeFlag();
+    addAndroidSpecificBuildOptions(hide: !verboseHelp);
     argParser.addMultiOption('target-platform',
         splitCommas: true,
         defaultsTo: <String>['android-arm', 'android-arm64', 'android-x64'],
diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart
index b474a07..362aea9 100644
--- a/packages/flutter_tools/lib/src/commands/create.dart
+++ b/packages/flutter_tools/lib/src/commands/create.dart
@@ -546,7 +546,10 @@
         generateSyntheticPackage: false,
       );
       final FlutterProject project = FlutterProject.fromDirectory(directory);
-      await project.ensureReadyForPlatformSpecificTooling(checkProjects: false);
+      await project.ensureReadyForPlatformSpecificTooling(
+        androidPlatform: true,
+        iosPlatform: true,
+      );
     }
     return generatedCount;
   }
@@ -641,7 +644,6 @@
       }
     }
 
-
     final FlutterProject project = FlutterProject.fromDirectory(directory);
     final bool generateAndroid = templateContext['android'] == true;
     if (generateAndroid) {
@@ -680,7 +682,15 @@
         offline: boolArg('offline'),
         generateSyntheticPackage: false,
       );
-      await project.ensureReadyForPlatformSpecificTooling(checkProjects: pluginExampleApp);
+
+      await project.ensureReadyForPlatformSpecificTooling(
+        androidPlatform: templateContext['android'] as bool ?? false,
+        iosPlatform: templateContext['ios'] as bool ?? false,
+        linuxPlatform: templateContext['linux'] as bool ?? false,
+        macOSPlatform: templateContext['macos'] as bool ?? false,
+        windowsPlatform: templateContext['windows'] as bool ?? false,
+        webPlatform: templateContext['web'] as bool ?? false,
+      );
     }
     if (templateContext['android'] == true) {
       gradle.updateLocalProperties(project: project, requireAndroidSdk: false);
diff --git a/packages/flutter_tools/lib/src/commands/drive.dart b/packages/flutter_tools/lib/src/commands/drive.dart
index 0d59fcb..d966bac 100644
--- a/packages/flutter_tools/lib/src/commands/drive.dart
+++ b/packages/flutter_tools/lib/src/commands/drive.dart
@@ -246,7 +246,19 @@
         webUri = residentRunner.uri;
       }
 
-      final LaunchResult result = await appStarter(this, webUri, package, applicationBinary != null);
+      // Attempt to launch the application up to 3 times, to validate whether it
+      // is possible to reduce flakiness by hardnening the launch code.
+      int attempt = 0;
+      LaunchResult result;
+      while (attempt < 3) {
+        // On attempts past 1, assume the application is built correctly and re-use it.
+        result = await appStarter(this, webUri, package, applicationBinary != null || attempt > 0);
+        if (result != null) {
+          break;
+        }
+        attempt += 1;
+        globals.printError('Application failed to start on attempt: $attempt');
+      }
       if (result == null) {
         throwToolExit('Application failed to start. Will not run test. Quitting.', exitCode: 1);
       }
@@ -374,6 +386,8 @@
         globals.printStatus('Stopping application instance.');
         await appStopper(this, package);
       }
+
+      await device?.dispose();
     }
 
     return FlutterCommandResult.success();
diff --git a/packages/flutter_tools/lib/src/commands/packages.dart b/packages/flutter_tools/lib/src/commands/packages.dart
index d31b337..583233d 100644
--- a/packages/flutter_tools/lib/src/commands/packages.dart
+++ b/packages/flutter_tools/lib/src/commands/packages.dart
@@ -147,13 +147,13 @@
     final FlutterProject rootProject = FlutterProject.fromPath(target);
 
     await _runPubGet(target, rootProject);
-    await rootProject.ensureReadyForPlatformSpecificTooling(checkProjects: true);
+    await rootProject.regeneratePlatformSpecificTooling();
 
     // Get/upgrade packages in example app as well
     if (rootProject.hasExampleApp) {
       final FlutterProject exampleProject = rootProject.example;
       await _runPubGet(exampleProject.directory.path, exampleProject);
-      await exampleProject.ensureReadyForPlatformSpecificTooling(checkProjects: true);
+      await exampleProject.regeneratePlatformSpecificTooling();
     }
 
     return FlutterCommandResult.success();
diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart
index 34ae7f0..fb0a27c 100644
--- a/packages/flutter_tools/lib/src/commands/run.dart
+++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -88,6 +88,7 @@
     usesDeviceUserOption();
     usesDeviceTimeoutOption();
     addDdsOptions(verboseHelp: verboseHelp);
+    addAndroidSpecificBuildOptions(hide: !verboseHelp);
   }
 
   bool get traceStartup => boolArg('trace-startup');
diff --git a/packages/flutter_tools/lib/src/compile.dart b/packages/flutter_tools/lib/src/compile.dart
index 4058e67..62a177d 100644
--- a/packages/flutter_tools/lib/src/compile.dart
+++ b/packages/flutter_tools/lib/src/compile.dart
@@ -600,7 +600,7 @@
         message = fileUri.toString();
       } else {
         message = request.packageConfig.toPackageUri(fileUri)?.toString() ??
-          toMultiRootPath(request.mainUri, fileSystemScheme, fileSystemRoots, _platform.isWindows);
+          toMultiRootPath(fileUri, fileSystemScheme, fileSystemRoots, _platform.isWindows);
       }
       _server.stdin.writeln(message);
       _logger.printTrace(message.toString());
diff --git a/packages/flutter_tools/lib/src/devfs.dart b/packages/flutter_tools/lib/src/devfs.dart
index fd7f604..4f8e62d 100644
--- a/packages/flutter_tools/lib/src/devfs.dart
+++ b/packages/flutter_tools/lib/src/devfs.dart
@@ -296,16 +296,11 @@
           _osUtils,
         );
         await request.addStream(contents);
-        // The contents has already been streamed, closing the request should
-        // not take long but we are experiencing hangs with it, see #63869.
-        //
         // Once the bug in Dart is solved we can remove the timeout
-        // (https://github.com/dart-lang/sdk/issues/43525).  The timeout was
-        // chosen to be inflated based on the max observed time when running the
-        // tests in "Google Tests".
+        // (https://github.com/dart-lang/sdk/issues/43525).
         try {
           final HttpClientResponse response = await request.close().timeout(
-            const Duration(milliseconds: 10000));
+            const Duration(seconds: 60));
           response.listen((_) {},
             onError: (dynamic error) {
               _logger.printTrace('error: $error');
diff --git a/packages/flutter_tools/lib/src/intellij/intellij.dart b/packages/flutter_tools/lib/src/intellij/intellij.dart
index cf8b576..f63b132 100644
--- a/packages/flutter_tools/lib/src/intellij/intellij.dart
+++ b/packages/flutter_tools/lib/src/intellij/intellij.dart
@@ -15,6 +15,25 @@
 /// This searches on the provided plugin path for a JAR archive, then
 /// unzips it to parse the META-INF/plugin.xml for version information.
 ///
+/// In particular, the Intellij Flutter plugin has a different structure depending on the version.
+///
+/// For flutter-intellij plugin v49 or lower:
+///   flutter-intellij/lib/flutter-intellij.jar ( includes META-INF/plugin.xml )
+///
+/// For flutter-intellij plugin v50 or higher and for Intellij 2020.2:
+///   flutter-intellij/lib/flutter-intellij-X.Y.Z.jar
+///                        flutter-idea-X.Y.Z.jar ( includes META-INF/plugin.xml )
+///                        flutter-studio-X.Y.Z.jar
+///
+/// For flutter-intellij plugin v50 or higher and for Intellij 2020.3:
+///   flutter-intellij/lib/flutter-intellij-X.Y.Z.jar
+///                        flutter-idea-X.Y.Z.jar ( includes META-INF/plugin.xml )
+///
+/// where X.Y.Z is the version number.
+///
+/// Intellij Flutter plugin's files can be found here:
+///   https://plugins.jetbrains.com/plugin/9212-flutter/versions/stable
+///
 /// See also:
 ///   * [IntellijValidator], the validator base class that uses this to check
 ///     plugin versions.
@@ -69,17 +88,61 @@
     return _fileSystem.isDirectorySync(packagePath);
   }
 
-  String _readPackageVersion(String packageName) {
-    final String jarPath = packageName.endsWith('.jar')
-        ? _fileSystem.path.join(pluginsPath, packageName)
-        : _fileSystem.path.join(pluginsPath, packageName, 'lib', '$packageName.jar');
-    final File file = _fileSystem.file(jarPath);
-    if (!file.existsSync()) {
-      return null;
+  ArchiveFile _findPluginXml(String packageName) {
+    final List<File> mainJarFileList = <File>[];
+    if (packageName.endsWith('.jar')) {
+      // package exists (checked in _hasPackage)
+      mainJarFileList.add(_fileSystem.file(_fileSystem.path.join(pluginsPath, packageName)));
+    } else {
+      final String packageLibPath =
+          _fileSystem.path.join(pluginsPath, packageName, 'lib');
+      if (!_fileSystem.isDirectorySync(packageLibPath)) {
+        return null;
+      }
+      // Collect the files with a file suffix of .jar/.zip that contains the plugin.xml file
+      final List<File> pluginJarFiles = _fileSystem
+          .directory(_fileSystem.path.join(pluginsPath, packageName, 'lib'))
+          .listSync()
+          .whereType<File>()
+          .where((File file) {
+            final String fileExt= _fileSystem.path.extension(file.path);
+            return fileExt == '.jar' || fileExt == '.zip';
+          })
+          .toList();
+
+      if (pluginJarFiles.isEmpty) {
+        return null;
+      }
+      // Prefer file with the same suffix as the package name
+      pluginJarFiles.sort((File a, File b) {
+        final bool aStartWithPackageName =
+            a.basename.toLowerCase().startsWith(packageName.toLowerCase());
+        final bool bStartWithPackageName =
+            b.basename.toLowerCase().startsWith(packageName.toLowerCase());
+        if (bStartWithPackageName != aStartWithPackageName) {
+          return bStartWithPackageName ? 1 : -1;
+        }
+        return a.basename.length - b.basename.length;
+      });
+      mainJarFileList.addAll(pluginJarFiles);
     }
-    try {
+
+    for (final File file in mainJarFileList) {
       final Archive archive = ZipDecoder().decodeBytes(file.readAsBytesSync());
       final ArchiveFile archiveFile = archive.findFile('META-INF/plugin.xml');
+      if (archiveFile != null) {
+        return archiveFile;
+      }
+    }
+    return null;
+  }
+
+  String _readPackageVersion(String packageName) {
+    try {
+      final ArchiveFile archiveFile = _findPluginXml(packageName);
+      if (archiveFile == null) {
+        return null;
+      }
       final String content = utf8.decode(archiveFile.content as List<int>);
       const String versionStartTag = '<version>';
       final int start = content.indexOf(versionStartTag);
diff --git a/packages/flutter_tools/lib/src/isolated/devfs_web.dart b/packages/flutter_tools/lib/src/isolated/devfs_web.dart
index 77d8e87..0dd390f 100644
--- a/packages/flutter_tools/lib/src/isolated/devfs_web.dart
+++ b/packages/flutter_tools/lib/src/isolated/devfs_web.dart
@@ -9,6 +9,7 @@
 import 'package:dwds/dwds.dart';
 import 'package:html/dom.dart';
 import 'package:html/parser.dart';
+import 'package:logging/logging.dart' as logging;
 import 'package:meta/meta.dart';
 import 'package:mime/mime.dart' as mime;
 import 'package:package_config/package_config.dart';
@@ -226,6 +227,10 @@
         };
       };
 
+      logging.Logger.root.onRecord.listen((logging.LogRecord event) {
+        globals.printTrace('${event.loggerName}: ${event.message}');
+      });
+
       // In debug builds, spin up DWDS and the full asset server.
       final Dwds dwds = await dwdsLauncher(
           assetReader: server,
@@ -300,6 +305,11 @@
   // handle requests for JavaScript source, dart sources maps, or asset files.
   @visibleForTesting
   Future<shelf.Response> handleRequest(shelf.Request request) async {
+    if (request.method != 'GET') {
+      // Assets are served via GET only.
+      return shelf.Response.notFound('');
+    }
+
     final String requestPath = _stripBasePath(request.url.path, basePath);
 
     if (requestPath == null) {
@@ -976,6 +986,11 @@
       ];
 
   Future<shelf.Response> handle(shelf.Request request) async {
+    if (request.method != 'GET') {
+      // Assets are served via GET only.
+      return shelf.Response.notFound('');
+    }
+
     Uri fileUri;
     final String requestPath = _stripBasePath(request.url.path, basePath);
 
diff --git a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart
index f76d70e..2205adc 100644
--- a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart
+++ b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart
@@ -657,7 +657,7 @@
 
       final bool hasWebPlugins = (await findPlugins(flutterProject))
         .any((Plugin p) => p.platforms.containsKey(WebPlugin.kConfigKey));
-      await injectPlugins(flutterProject, checkProjects: true);
+      await injectPlugins(flutterProject, webPlatform: true);
 
       final Uri generatedUri = globals.fs.currentDirectory
         .childDirectory('lib')
diff --git a/packages/flutter_tools/lib/src/macos/cocoapod_utils.dart b/packages/flutter_tools/lib/src/macos/cocoapod_utils.dart
index c84e503..c329280 100644
--- a/packages/flutter_tools/lib/src/macos/cocoapod_utils.dart
+++ b/packages/flutter_tools/lib/src/macos/cocoapod_utils.dart
@@ -18,7 +18,7 @@
 ) async {
   final FlutterProject project = xcodeProject.parent;
   // Ensure that the plugin list is up to date, since hasPlugins relies on it.
-  await refreshPluginsList(project);
+  await refreshPluginsList(project, macOSPlatform: project.macos.existsSync());
   if (!(hasPlugins(project) || (project.isModule && xcodeProject.podfile.existsSync()))) {
     return;
   }
diff --git a/packages/flutter_tools/lib/src/plugins.dart b/packages/flutter_tools/lib/src/plugins.dart
index be7df41..3d5292b 100644
--- a/packages/flutter_tools/lib/src/plugins.dart
+++ b/packages/flutter_tools/lib/src/plugins.dart
@@ -1145,11 +1145,12 @@
 /// Rewrites the `.flutter-plugins` file of [project] based on the plugin
 /// dependencies declared in `pubspec.yaml`.
 ///
-/// If `checkProjects` is true, then plugins are only injected into directories
-/// which already exist.
-///
 /// Assumes `pub get` has been executed since last change to `pubspec.yaml`.
-Future<void> refreshPluginsList(FlutterProject project, {bool checkProjects = false}) async {
+Future<void> refreshPluginsList(
+  FlutterProject project, {
+  bool iosPlatform = false,
+  bool macOSPlatform = false,
+}) async {
   final List<Plugin> plugins = await findPlugins(project);
 
   // TODO(franciscojma): Remove once migration is complete.
@@ -1159,12 +1160,10 @@
   final bool changed = _writeFlutterPluginsList(project, plugins);
   if (changed || legacyChanged) {
     createPluginSymlinks(project, force: true);
-    if (!checkProjects || project.ios.existsSync()) {
+    if (iosPlatform) {
       globals.cocoaPods.invalidatePodInstallOutput(project.ios);
     }
-    // TODO(stuartmorgan): Potentially add checkProjects once a decision has
-    // made about how to handle macOS in existing projects.
-    if (project.macos.existsSync()) {
+    if (macOSPlatform) {
       globals.cocoaPods.invalidatePodInstallOutput(project.macos);
     }
   }
@@ -1172,34 +1171,40 @@
 
 /// Injects plugins found in `pubspec.yaml` into the platform-specific projects.
 ///
-/// If `checkProjects` is true, then plugins are only injected into directories
-/// which already exist.
-///
 /// Assumes [refreshPluginsList] has been called since last change to `pubspec.yaml`.
-Future<void> injectPlugins(FlutterProject project, {bool checkProjects = false}) async {
+Future<void> injectPlugins(
+  FlutterProject project, {
+  bool androidPlatform = false,
+  bool iosPlatform = false,
+  bool linuxPlatform = false,
+  bool macOSPlatform = false,
+  bool windowsPlatform = false,
+  bool webPlatform = false,
+}) async {
   final List<Plugin> plugins = await findPlugins(project);
   // Sort the plugins by name to keep ordering stable in generated files.
   plugins.sort((Plugin left, Plugin right) => left.name.compareTo(right.name));
-  if ((checkProjects && project.android.existsSync()) || !checkProjects) {
+  if (androidPlatform) {
     await _writeAndroidPluginRegistrant(project, plugins);
   }
-  if ((checkProjects && project.ios.existsSync()) || !checkProjects) {
+  if (iosPlatform) {
     await _writeIOSPluginRegistrant(project, plugins);
   }
-  // TODO(stuartmorgan): Revisit the conditions here once the plans for handling
-  // desktop in existing projects are in place. For now, ignore checkProjects
-  // on desktop and always treat it as true.
-  if (featureFlags.isLinuxEnabled && project.linux.existsSync()) {
+  if (linuxPlatform) {
     await _writeLinuxPluginFiles(project, plugins);
   }
-  if (featureFlags.isMacOSEnabled && project.macos.existsSync()) {
+  if (macOSPlatform) {
     await _writeMacOSPluginRegistrant(project, plugins);
   }
-  if (featureFlags.isWindowsEnabled && project.windows.existsSync()) {
+  if (windowsPlatform) {
     await _writeWindowsPluginFiles(project, plugins);
   }
-  for (final XcodeBasedProject subproject in <XcodeBasedProject>[project.ios, project.macos]) {
-    if (!project.isModule && (!checkProjects || subproject.existsSync())) {
+  if (!project.isModule) {
+    final List<XcodeBasedProject> darwinProjects = <XcodeBasedProject>[
+      if (iosPlatform) project.ios,
+      if (macOSPlatform) project.macos,
+    ];
+    for (final XcodeBasedProject subproject in darwinProjects) {
       if (plugins.isNotEmpty) {
         await globals.cocoaPods.setupPodfile(subproject);
       }
@@ -1210,7 +1215,7 @@
       }
     }
   }
-  if (featureFlags.isWebEnabled && project.web.existsSync()) {
+  if (webPlatform) {
     await _writeWebPluginRegistrant(project, plugins);
   }
 }
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart
index 4607337..f2dd9ac 100644
--- a/packages/flutter_tools/lib/src/project.dart
+++ b/packages/flutter_tools/lib/src/project.dart
@@ -229,38 +229,64 @@
     return manifest;
   }
 
-  /// Generates project files necessary to make Gradle builds work on Android
-  /// and CocoaPods+Xcode work on iOS, for app and module projects only.
-  // TODO(cyanglaz): The param `checkProjects` is confusing. We should give it a better name
-  // or add some documentation explaining what it does, or both.
-  // https://github.com/flutter/flutter/issues/60023
-  Future<void> ensureReadyForPlatformSpecificTooling({bool checkProjects = false}) async {
+  /// Reapplies template files and regenerates project files and plugin
+  /// registrants for app and module projects only.
+  ///
+  /// Will not create project platform directories if they do not already exist.
+  Future<void> regeneratePlatformSpecificTooling() async {
+    return ensureReadyForPlatformSpecificTooling(
+      androidPlatform: android.existsSync(),
+      iosPlatform: ios.existsSync(),
+      // TODO(stuartmorgan): Revisit the conditions here once the plans for handling
+      // desktop in existing projects are in place.
+      linuxPlatform: featureFlags.isLinuxEnabled && linux.existsSync(),
+      macOSPlatform: featureFlags.isMacOSEnabled && macos.existsSync(),
+      windowsPlatform: featureFlags.isWindowsEnabled && windows.existsSync(),
+      webPlatform: featureFlags.isWebEnabled && web.existsSync(),
+    );
+  }
+
+  /// Applies template files and generates project files and plugin
+  /// registrants for app and module projects only for the specified platforms.
+  Future<void> ensureReadyForPlatformSpecificTooling({
+    bool androidPlatform = false,
+    bool iosPlatform = false,
+    bool linuxPlatform = false,
+    bool macOSPlatform = false,
+    bool windowsPlatform = false,
+    bool webPlatform = false,
+  }) async {
     if (!directory.existsSync() || hasExampleApp) {
       return;
     }
-    await refreshPluginsList(this);
-    if ((android.existsSync() && checkProjects) || !checkProjects) {
+    await refreshPluginsList(this, iosPlatform: iosPlatform, macOSPlatform: macOSPlatform);
+    if (androidPlatform) {
       await android.ensureReadyForPlatformSpecificTooling();
     }
-    if ((ios.existsSync() && checkProjects) || !checkProjects) {
+    if (iosPlatform) {
       await ios.ensureReadyForPlatformSpecificTooling();
     }
-    // TODO(stuartmorgan): Revisit conditions once there is a plan for handling
-    // non-default platform projects. For now, always treat checkProjects as
-    // true for desktop.
-    if (featureFlags.isLinuxEnabled && linux.existsSync()) {
+    if (linuxPlatform) {
       await linux.ensureReadyForPlatformSpecificTooling();
     }
-    if (featureFlags.isMacOSEnabled && macos.existsSync()) {
+    if (macOSPlatform) {
       await macos.ensureReadyForPlatformSpecificTooling();
     }
-    if (featureFlags.isWindowsEnabled && windows.existsSync()) {
+    if (windowsPlatform) {
       await windows.ensureReadyForPlatformSpecificTooling();
     }
-    if (featureFlags.isWebEnabled && web.existsSync()) {
+    if (webPlatform) {
       await web.ensureReadyForPlatformSpecificTooling();
     }
-    await injectPlugins(this, checkProjects: checkProjects);
+    await injectPlugins(
+      this,
+      androidPlatform: androidPlatform,
+      iosPlatform: iosPlatform,
+      linuxPlatform: linuxPlatform,
+      macOSPlatform: macOSPlatform,
+      windowsPlatform: windowsPlatform,
+      webPlatform: webPlatform,
+    );
   }
 
   /// Returns a json encoded string containing the [appName], [version], and [buildNumber] that is used to generate version.json
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index 7cfbf06..e654bf4 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -114,6 +114,7 @@
   static const String kDeviceTimeout = 'device-timeout';
   static const String kAnalyzeSize = 'analyze-size';
   static const String kNullAssertions = 'null-assertions';
+  static const String kAndroidGradleDaemon = 'android-gradle-daemon';
 }
 
 abstract class FlutterCommand extends Command<void> {
@@ -611,6 +612,18 @@
     );
   }
 
+  void addAndroidSpecificBuildOptions({ bool hide = false }) {
+    argParser.addFlag(
+      FlutterOptions.kAndroidGradleDaemon,
+      help: 'Whether to enable the Gradle daemon when performing an Android build. '
+        'Starting the daemon is the default behavior of the gradle wrapper script created '
+        ' in a Flutter project. Setting this flag to false corresponds to passing '
+        "'--no-daemon' to the gradle wrapper script. This flag will cause the daemon "
+        'process to terminate after the build is completed',
+      defaultsTo: true,
+    );
+  }
+
   /// Adds build options common to all of the desktop build commands.
   void addCommonDesktopBuildOptions({ bool verboseHelp = false }) {
     addBuildModeFlags(verboseHelp: verboseHelp);
@@ -764,6 +777,9 @@
       ? stringArg(FlutterOptions.kSplitDebugInfoOption)
       : null;
 
+    final bool androidGradleDaemon = !argParser.options.containsKey(FlutterOptions.kAndroidGradleDaemon)
+      || boolArg(FlutterOptions.kAndroidGradleDaemon);
+
     if (dartObfuscation && (splitDebugInfoPath == null || splitDebugInfoPath.isEmpty)) {
       throwToolExit(
         '"--${FlutterOptions.kDartObfuscationOption}" can only be used in '
@@ -827,6 +843,7 @@
       packagesPath: globalResults['packages'] as String ?? '.packages',
       nullSafetyMode: nullSafetyMode,
       codeSizeDirectory: codeSizeDirectory,
+      androidGradleDaemon: androidGradleDaemon,
     );
   }
 
@@ -996,7 +1013,7 @@
       );
       // All done updating dependencies. Release the cache lock.
       Cache.releaseLock();
-      await project.ensureReadyForPlatformSpecificTooling(checkProjects: true);
+      await project.regeneratePlatformSpecificTooling();
     } else {
       Cache.releaseLock();
     }
diff --git a/packages/flutter_tools/lib/src/web/compile.dart b/packages/flutter_tools/lib/src/web/compile.dart
index e9d2175..d494c57 100644
--- a/packages/flutter_tools/lib/src/web/compile.dart
+++ b/packages/flutter_tools/lib/src/web/compile.dart
@@ -41,7 +41,7 @@
     outputDirectory.deleteSync(recursive: true);
     outputDirectory.createSync(recursive: true);
   }
-  await injectPlugins(flutterProject, checkProjects: true);
+  await injectPlugins(flutterProject, webPlatform: true);
   final Status status = globals.logger.startProgress('Compiling $target for the Web...');
   final Stopwatch sw = Stopwatch()..start();
   try {
diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml
index c2b90fc..d5baa02 100644
--- a/packages/flutter_tools/pubspec.yaml
+++ b/packages/flutter_tools/pubspec.yaml
@@ -113,4 +113,10 @@
   # Exclude this package from the hosted API docs.
   nodoc: true
 
-# PUBSPEC CHECKSUM: 138b
+# PUBSPEC CHECKSUM: 9d86
+
+dependency_overrides:
+  dwds:
+    git:
+      url: git@github.com:dart-lang/webdev.git
+      path: dwds
diff --git a/packages/flutter_tools/templates/app/linux.tmpl/main.cc b/packages/flutter_tools/templates/app/linux.tmpl/main.cc
index 058e617..e7c5c54 100644
--- a/packages/flutter_tools/templates/app/linux.tmpl/main.cc
+++ b/packages/flutter_tools/templates/app/linux.tmpl/main.cc
@@ -1,10 +1,6 @@
 #include "my_application.h"
 
 int main(int argc, char** argv) {
-  // Only X11 is currently supported.
-  // Wayland support is being developed: https://github.com/flutter/flutter/issues/57932.
-  gdk_set_allowed_backends("x11");
-
   g_autoptr(MyApplication) app = my_application_new();
   return g_application_run(G_APPLICATION(app), argc, argv);
 }
diff --git a/packages/flutter_tools/templates/app/linux.tmpl/my_application.cc.tmpl b/packages/flutter_tools/templates/app/linux.tmpl/my_application.cc.tmpl
index 5097955..fe188bb 100644
--- a/packages/flutter_tools/templates/app/linux.tmpl/my_application.cc.tmpl
+++ b/packages/flutter_tools/templates/app/linux.tmpl/my_application.cc.tmpl
@@ -1,6 +1,9 @@
 #include "my_application.h"
 
 #include <flutter_linux/flutter_linux.h>
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#endif
 
 #include "flutter/generated_plugin_registrant.h"
 
@@ -14,11 +17,35 @@
 static void my_application_activate(GApplication* application) {
   GtkWindow* window =
       GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
-  GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
-  gtk_widget_show(GTK_WIDGET(header_bar));
-  gtk_header_bar_set_title(header_bar, "{{projectName}}");
-  gtk_header_bar_set_show_close_button(header_bar, TRUE);
-  gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
+
+  // Use a header bar when running in GNOME as this is the common style used
+  // by applications and is the setup most users will be using (e.g. Ubuntu
+  // desktop).
+  // If running on X and not using GNOME then just use a traditional title bar
+  // in case the window manager does more exotic layout, e.g. tiling.
+  // If running on Wayland assume the header bar will work (may need changing
+  // if future cases occur).
+  gboolean use_header_bar = TRUE;
+#ifdef GDK_WINDOWING_X11
+  GdkScreen *screen = gtk_window_get_screen(window);
+  if (GDK_IS_X11_SCREEN(screen)) {
+     const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
+     if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
+       use_header_bar = FALSE;
+     }
+  }
+#endif
+  if (use_header_bar) {
+    GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
+    gtk_widget_show(GTK_WIDGET(header_bar));
+    gtk_header_bar_set_title(header_bar, "{{projectName}}");
+    gtk_header_bar_set_show_close_button(header_bar, TRUE);
+    gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
+  }
+  else {
+    gtk_window_set_title(window, "{{projectName}}");
+  }
+
   gtk_window_set_default_size(window, 1280, 720);
   gtk_widget_show(GTK_WIDGET(window));
 
diff --git a/packages/flutter_tools/templates/package/pubspec.yaml.tmpl b/packages/flutter_tools/templates/package/pubspec.yaml.tmpl
index 69a6beb..4943d54 100644
--- a/packages/flutter_tools/templates/package/pubspec.yaml.tmpl
+++ b/packages/flutter_tools/templates/package/pubspec.yaml.tmpl
@@ -6,7 +6,7 @@
 
 environment:
   sdk: ">=2.7.0 <3.0.0"
-  flutter: ">=1.17.0 <2.0.0"
+  flutter: ">=1.17.0"
 
 dependencies:
   flutter:
diff --git a/packages/flutter_tools/templates/plugin/pubspec.yaml.tmpl b/packages/flutter_tools/templates/plugin/pubspec.yaml.tmpl
index c2f83ec..4ce3f0d 100644
--- a/packages/flutter_tools/templates/plugin/pubspec.yaml.tmpl
+++ b/packages/flutter_tools/templates/plugin/pubspec.yaml.tmpl
@@ -6,7 +6,7 @@
 
 environment:
   sdk: ">=2.7.0 <3.0.0"
-  flutter: ">=1.20.0 <2.0.0"
+  flutter: ">=1.20.0"
 
 dependencies:
   flutter:
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart
index 0ed3aa0..9538ada 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart
@@ -99,7 +99,7 @@
 
     testUsingContext('returns 1 when app fails to run', () async {
       testDeviceManager.addDevice(MockDevice());
-      appStarter = expectAsync4((DriveCommand command, Uri webUri, ApplicationPackage package, bool prebuiltApplication) async => null);
+      appStarter = expectAsync4((DriveCommand command, Uri webUri, ApplicationPackage package, bool prebuiltApplication) async => null, count: 3);
 
       final String testApp = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e.dart');
       final String testFile = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart');
@@ -228,6 +228,7 @@
         '10',
       ];
       await createTestCommandRunner(command).run(args);
+      verify(mockDevice.dispose());
       expect(testLogger.errorText, isEmpty);
     }, overrides: <Type, Generator>{
       FileSystem: () => fs,
@@ -529,6 +530,7 @@
                 prebuiltApplication: false,
                 userIdentifier: anyNamed('userIdentifier'),
         ));
+        verify(mockDevice.dispose());
       }, overrides: <Type, Generator>{
         FileSystem: () => fs,
         ProcessManager: () => FakeProcessManager.any(),
@@ -558,6 +560,7 @@
                 prebuiltApplication: false,
                 userIdentifier: anyNamed('userIdentifier'),
         ));
+        verify(mockDevice.dispose());
       }, overrides: <Type, Generator>{
         FileSystem: () => fs,
         ProcessManager: () => FakeProcessManager.any(),
diff --git a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart
index 97b2ff0..e046d40 100755
--- a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart
+++ b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart
@@ -7,6 +7,7 @@
 import 'dart:typed_data';
 
 import 'package:args/command_runner.dart';
+import 'package:file_testing/file_testing.dart';
 import 'package:flutter_tools/src/artifacts.dart';
 import 'package:flutter_tools/src/base/file_system.dart';
 import 'package:flutter_tools/src/base/io.dart';
@@ -662,6 +663,52 @@
     expect(testLogger.statusText, isNot(contains('https://flutter.dev/go/android-project-migration')));
   });
 
+  testUsingContext('app does not include desktop or web by default', () async {
+    Cache.flutterRoot = '../..';
+    when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
+    when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
+
+    final CreateCommand command = CreateCommand();
+    final CommandRunner<void> runner = createTestCommandRunner(command);
+
+    await runner.run(<String>['create', '--no-pub', projectDir.path]);
+
+    expect(projectDir.childDirectory('linux'), isNot(exists));
+    expect(projectDir.childDirectory('macos'), isNot(exists));
+    expect(projectDir.childDirectory('windows'), isNot(exists));
+    expect(projectDir.childDirectory('web'), isNot(exists));
+  }, overrides: <Type, Generator>{
+    FeatureFlags: () => TestFeatureFlags(),
+  });
+
+  testUsingContext('plugin does not include desktop or web by default',
+      () async {
+    Cache.flutterRoot = '../..';
+    when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
+    when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
+
+    final CreateCommand command = CreateCommand();
+    final CommandRunner<void> runner = createTestCommandRunner(command);
+
+    await runner.run(
+        <String>['create', '--no-pub', '--template=plugin', projectDir.path]);
+
+    expect(projectDir.childDirectory('linux'), isNot(exists));
+    expect(projectDir.childDirectory('macos'), isNot(exists));
+    expect(projectDir.childDirectory('windows'), isNot(exists));
+    expect(projectDir.childDirectory('web'), isNot(exists));
+    expect(projectDir.childDirectory('example').childDirectory('linux'),
+        isNot(exists));
+    expect(projectDir.childDirectory('example').childDirectory('macos'),
+        isNot(exists));
+    expect(projectDir.childDirectory('example').childDirectory('windows'),
+        isNot(exists));
+    expect(projectDir.childDirectory('example').childDirectory('web'),
+        isNot(exists));
+  }, overrides: <Type, Generator>{
+    FeatureFlags: () => TestFeatureFlags(),
+  });
+
   testUsingContext('app supports Linux if requested', () async {
     Cache.flutterRoot = '../..';
     when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
@@ -670,28 +717,24 @@
     final CreateCommand command = CreateCommand();
     final CommandRunner<void> runner = createTestCommandRunner(command);
 
-    await runner.run(<String>['create', '--no-pub', projectDir.path]);
+    await runner.run(<String>[
+      'create',
+      '--no-pub',
+      '--platforms=linux',
+      projectDir.path,
+    ]);
 
-    expect(projectDir.childDirectory('linux').childFile('CMakeLists.txt').existsSync(), true);
+    expect(
+        projectDir.childDirectory('linux').childFile('CMakeLists.txt'), exists);
+    expect(projectDir.childDirectory('android'), isNot(exists));
+    expect(projectDir.childDirectory('ios'), isNot(exists));
+    expect(projectDir.childDirectory('windows'), isNot(exists));
+    expect(projectDir.childDirectory('macos'), isNot(exists));
+    expect(projectDir.childDirectory('web'), isNot(exists));
   }, overrides: <Type, Generator>{
     FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
   });
 
-  testUsingContext('app does not include Linux by default', () async {
-    Cache.flutterRoot = '../..';
-    when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
-    when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
-
-    final CreateCommand command = CreateCommand();
-    final CommandRunner<void> runner = createTestCommandRunner(command);
-
-    await runner.run(<String>['create', '--no-pub', projectDir.path]);
-
-    expect(projectDir.childDirectory('linux').childFile('CMakeLists.txt').existsSync(), false);
-  }, overrides: <Type, Generator>{
-    FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: false),
-  });
-
   testUsingContext('plugin supports Linux if requested', () async {
     Cache.flutterRoot = '../..';
     when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
@@ -702,17 +745,32 @@
 
     await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=linux', projectDir.path]);
 
-    expect(projectDir.childDirectory('linux').childFile('CMakeLists.txt').existsSync(), true);
-    expect(projectDir.childDirectory('example').childDirectory('linux').existsSync(), true);
-        validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[
-      'linux',
-    ], pluginClass: 'FlutterProjectPlugin',
+    expect(
+        projectDir.childDirectory('linux').childFile('CMakeLists.txt'), exists);
+    expect(
+        projectDir.childDirectory('example').childDirectory('linux'), exists);
+    expect(projectDir.childDirectory('example').childDirectory('android'),
+        isNot(exists));
+    expect(projectDir.childDirectory('example').childDirectory('ios'),
+        isNot(exists));
+    expect(projectDir.childDirectory('example').childDirectory('windows'),
+        isNot(exists));
+    expect(projectDir.childDirectory('example').childDirectory('macos'),
+        isNot(exists));
+    expect(projectDir.childDirectory('example').childDirectory('web'),
+        isNot(exists));
+    validatePubspecForPlugin(
+        projectDir: projectDir.absolute.path,
+        expectedPlatforms: const <String>[
+          'linux',
+        ],
+        pluginClass: 'FlutterProjectPlugin',
     unexpectedPlatforms: <String>['some_platform']);
   }, overrides: <Type, Generator>{
     FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
   });
 
-  testUsingContext('plugin does not include Linux by default', () async {
+  testUsingContext('app supports macOS if requested', () async {
     Cache.flutterRoot = '../..';
     when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
     when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
@@ -720,27 +778,23 @@
     final CreateCommand command = CreateCommand();
     final CommandRunner<void> runner = createTestCommandRunner(command);
 
-    await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
+    await runner.run(<String>[
+      'create',
+      '--no-pub',
+      '--platforms=macos',
+      projectDir.path,
+    ]);
 
-    expect(projectDir.childDirectory('linux').childFile('CMakeLists.txt').existsSync(), false);
-    expect(projectDir.childDirectory('example').childDirectory('linux').existsSync(), false);
+    expect(
+        projectDir.childDirectory('macos').childDirectory('Runner.xcworkspace'),
+        exists);
+    expect(projectDir.childDirectory('android'), isNot(exists));
+    expect(projectDir.childDirectory('ios'), isNot(exists));
+    expect(projectDir.childDirectory('linux'), isNot(exists));
+    expect(projectDir.childDirectory('windows'), isNot(exists));
+    expect(projectDir.childDirectory('web'), isNot(exists));
   }, overrides: <Type, Generator>{
-    FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: false),
-  });
-
-  testUsingContext('app does not include macOS by default', () async {
-    Cache.flutterRoot = '../..';
-    when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
-    when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
-
-    final CreateCommand command = CreateCommand();
-    final CommandRunner<void> runner = createTestCommandRunner(command);
-
-    await runner.run(<String>['create', '--no-pub', projectDir.path]);
-
-    expect(projectDir.childDirectory('macos').childDirectory('Runner.xcworkspace').existsSync(), false);
-  }, overrides: <Type, Generator>{
-    FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: false),
+    FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
   });
 
   testUsingContext('plugin supports macOS if requested', () async {
@@ -753,8 +807,20 @@
 
     await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=macos', projectDir.path]);
 
-    expect(projectDir.childDirectory('macos').childFile('flutter_project.podspec').existsSync(), true);
-    expect(projectDir.childDirectory('example').childDirectory('macos').existsSync(), true);
+    expect(projectDir.childDirectory('macos').childFile('flutter_project.podspec'),
+        exists);
+    expect(
+        projectDir.childDirectory('example').childDirectory('macos'), exists);
+    expect(projectDir.childDirectory('example').childDirectory('linux'),
+        isNot(exists));
+    expect(projectDir.childDirectory('example').childDirectory('android'),
+        isNot(exists));
+    expect(projectDir.childDirectory('example').childDirectory('ios'),
+        isNot(exists));
+    expect(projectDir.childDirectory('example').childDirectory('windows'),
+        isNot(exists));
+    expect(projectDir.childDirectory('example').childDirectory('web'),
+        isNot(exists));
     validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[
       'macos',
     ], pluginClass: 'FlutterProjectPlugin',
@@ -763,22 +829,6 @@
     FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
   });
 
-  testUsingContext('plugin does not include macOS by default', () async {
-    Cache.flutterRoot = '../..';
-    when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
-    when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
-
-    final CreateCommand command = CreateCommand();
-    final CommandRunner<void> runner = createTestCommandRunner(command);
-
-    await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
-
-    expect(projectDir.childDirectory('macos').childFile('flutter_project.podspec').existsSync(), false);
-    expect(projectDir.childDirectory('example').childDirectory('macos').existsSync(), false);
-  }, overrides: <Type, Generator>{
-    FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: false),
-  });
-
   testUsingContext('app supports Windows if requested', () async {
     Cache.flutterRoot = '../..';
     when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
@@ -787,9 +837,20 @@
     final CreateCommand command = CreateCommand();
     final CommandRunner<void> runner = createTestCommandRunner(command);
 
-    await runner.run(<String>['create', '--no-pub', projectDir.path]);
+    await runner.run(<String>[
+      'create',
+      '--no-pub',
+      '--platforms=windows',
+      projectDir.path,
+    ]);
 
-    expect(projectDir.childDirectory('windows').childFile('CMakeLists.txt').existsSync(), true);
+    expect(projectDir.childDirectory('windows').childFile('CMakeLists.txt'),
+        exists);
+    expect(projectDir.childDirectory('android'), isNot(exists));
+    expect(projectDir.childDirectory('ios'), isNot(exists));
+    expect(projectDir.childDirectory('linux'), isNot(exists));
+    expect(projectDir.childDirectory('macos'), isNot(exists));
+    expect(projectDir.childDirectory('web'), isNot(exists));
   }, overrides: <Type, Generator>{
     FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
   });
@@ -805,7 +866,7 @@
     await runner.run(<String>['create', '--no-pub', '--org', 'com.foo.bar', projectDir.path]);
 
     final File resourceFile = projectDir.childDirectory('windows').childDirectory('runner').childFile('Runner.rc');
-    expect(resourceFile.existsSync(), true);
+    expect(resourceFile, exists);
     final String contents = resourceFile.readAsStringSync();
     expect(contents, contains('"CompanyName", "com.foo.bar"'));
     expect(contents, contains('"ProductName", "flutter_project"'));
@@ -813,21 +874,6 @@
     FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
   });
 
-  testUsingContext('app does not include Windows by default', () async {
-    Cache.flutterRoot = '../..';
-    when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
-    when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
-
-    final CreateCommand command = CreateCommand();
-    final CommandRunner<void> runner = createTestCommandRunner(command);
-
-    await runner.run(<String>['create', '--no-pub', projectDir.path]);
-
-    expect(projectDir.childDirectory('windows').childFile('CMakeLists.txt').existsSync(), false);
-  }, overrides: <Type, Generator>{
-    FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: false),
-  });
-
   testUsingContext('plugin supports Windows if requested', () async {
     Cache.flutterRoot = '../..';
     when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
@@ -838,8 +884,31 @@
 
     await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=windows', projectDir.path]);
 
-    expect(projectDir.childDirectory('windows').childFile('CMakeLists.txt').existsSync(), true);
-    expect(projectDir.childDirectory('example').childDirectory('windows').existsSync(), true);
+    expect(projectDir.childDirectory('windows').childFile('CMakeLists.txt'),
+        exists);
+    expect(
+        projectDir.childDirectory('example').childDirectory('windows'), exists);
+    expect(
+        projectDir
+            .childDirectory('example')
+            .childDirectory('android'),
+        isNot(exists));
+    expect(
+        projectDir.childDirectory('example').childDirectory('ios'),
+        isNot(exists));
+    expect(
+        projectDir
+            .childDirectory('example')
+            .childDirectory('linux'),
+        isNot(exists));
+    expect(
+        projectDir
+            .childDirectory('example')
+            .childDirectory('macos'),
+        isNot(exists));
+    expect(
+        projectDir.childDirectory('example').childDirectory('web'),
+        isNot(exists));
     validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[
       'windows'
     ], pluginClass: 'FlutterProjectPlugin',
@@ -848,7 +917,7 @@
     FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
   });
 
-  testUsingContext('plugin does not include Windows by default', () async {
+  testUsingContext('app supports web if requested', () async {
     Cache.flutterRoot = '../..';
     when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
     when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
@@ -856,12 +925,23 @@
     final CreateCommand command = CreateCommand();
     final CommandRunner<void> runner = createTestCommandRunner(command);
 
-    await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
+    await runner.run(<String>[
+      'create',
+      '--no-pub',
+      '--platforms=web',
+      projectDir.path,
+    ]);
 
-    expect(projectDir.childDirectory('windows').childFile('CMakeLists.txt').existsSync(), false);
-    expect(projectDir.childDirectory('example').childDirectory('windows').existsSync(), false);
+    expect(
+        projectDir.childDirectory('web').childFile('index.html'),
+        exists);
+    expect(projectDir.childDirectory('android'), isNot(exists));
+    expect(projectDir.childDirectory('ios'), isNot(exists));
+    expect(projectDir.childDirectory('linux'), isNot(exists));
+    expect(projectDir.childDirectory('macos'), isNot(exists));
+    expect(projectDir.childDirectory('windows'), isNot(exists));
   }, overrides: <Type, Generator>{
-    FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: false),
+    FeatureFlags: () => TestFeatureFlags(isWebEnabled: true),
   });
 
   testUsingContext('plugin uses new platform schema', () async {
@@ -1516,7 +1596,7 @@
 
     await runner.run(args);
     final File expectedFile = globals.fs.file(outputFile);
-    expect(expectedFile.existsSync(), isTrue);
+    expect(expectedFile, exists);
     expect(expectedFile.readAsStringSync(), equals(samplesIndexJson));
   }, overrides: <Type, Generator>{
     HttpClientFactory: () =>
@@ -1555,7 +1635,7 @@
     ];
 
     await expectLater(runner.run(args), throwsToolExit(exitCode: 2, message: 'Failed to write samples'));
-    expect(globals.fs.file(outputFile).existsSync(), isFalse);
+    expect(globals.fs.file(outputFile), isNot(exists));
   }, overrides: <Type, Generator>{
     HttpClientFactory: () =>
         () => MockHttpClient(404, result: 'not found'),
@@ -1573,21 +1653,27 @@
 
     // TODO(cyanglaz): no-op iOS folder should be removed after 1.20.0 release
     // https://github.com/flutter/flutter/issues/59787
-    expect(projectDir.childDirectory('ios').existsSync(), false);
-    expect(projectDir.childDirectory('android').existsSync(), false);
-    expect(projectDir.childDirectory('web').existsSync(), false);
-    expect(projectDir.childDirectory('linux').existsSync(), false);
-    expect(projectDir.childDirectory('windows').existsSync(), false);
-    expect(projectDir.childDirectory('macos').existsSync(), false);
+    expect(projectDir.childDirectory('ios'), isNot(exists));
+    expect(projectDir.childDirectory('android'), isNot(exists));
+    expect(projectDir.childDirectory('web'), isNot(exists));
+    expect(projectDir.childDirectory('linux'), isNot(exists));
+    expect(projectDir.childDirectory('windows'), isNot(exists));
+    expect(projectDir.childDirectory('macos'), isNot(exists));
 
     // TODO(cyanglaz): no-op iOS folder should be removed after 1.20.0 release
     // https://github.com/flutter/flutter/issues/59787
-    expect(projectDir.childDirectory('example').childDirectory('ios').existsSync(), false);
-    expect(projectDir.childDirectory('example').childDirectory('android').existsSync(), false);
-    expect(projectDir.childDirectory('example').childDirectory('web').existsSync(), false);
-    expect(projectDir.childDirectory('example').childDirectory('linux').existsSync(), false);
-    expect(projectDir.childDirectory('example').childDirectory('windows').existsSync(), false);
-    expect(projectDir.childDirectory('example').childDirectory('macos').existsSync(), false);
+    expect(projectDir.childDirectory('example').childDirectory('ios'),
+        isNot(exists));
+    expect(projectDir.childDirectory('example').childDirectory('android'),
+        isNot(exists));
+    expect(projectDir.childDirectory('example').childDirectory('web'),
+        isNot(exists));
+    expect(projectDir.childDirectory('example').childDirectory('linux'),
+        isNot(exists));
+    expect(projectDir.childDirectory('example').childDirectory('windows'),
+        isNot(exists));
+    expect(projectDir.childDirectory('example').childDirectory('macos'),
+        isNot(exists));
     validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: <String>[
       'some_platform'
     ], pluginClass: 'somePluginClass',
@@ -1607,8 +1693,8 @@
 
     await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=ios', projectDir.path]);
 
-    expect(projectDir.childDirectory('ios').existsSync(), true);
-    expect(projectDir.childDirectory('example').childDirectory('ios').existsSync(), true);
+    expect(projectDir.childDirectory('ios'), exists);
+    expect(projectDir.childDirectory('example').childDirectory('ios'), exists);
     validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: <String>[
       'ios',
     ], pluginClass: 'FlutterProjectPlugin',
@@ -1627,8 +1713,9 @@
 
     await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=android', projectDir.path]);
 
-    expect(projectDir.childDirectory('android').existsSync(), true);
-    expect(projectDir.childDirectory('example').childDirectory('android').existsSync(), true);
+    expect(projectDir.childDirectory('android'), exists);
+    expect(
+        projectDir.childDirectory('example').childDirectory('android'), exists);
     validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[
       'android'
     ], pluginClass: 'FlutterProjectPlugin',
@@ -1647,7 +1734,9 @@
     final CommandRunner<void> runner = createTestCommandRunner(command);
 
     await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=web', projectDir.path]);
-    expect(projectDir.childDirectory('lib').childFile('flutter_project_web.dart').existsSync(), true);
+    expect(
+        projectDir.childDirectory('lib').childFile('flutter_project_web.dart'),
+        exists);
     validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[
       'web'
     ], pluginClass: 'FlutterProjectWeb',
@@ -1667,7 +1756,9 @@
     final CommandRunner<void> runner = createTestCommandRunner(command);
 
     await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=web', projectDir.path]);
-    expect(projectDir.childDirectory('lib').childFile('flutter_project_web.dart').existsSync(), false);
+    expect(
+        projectDir.childDirectory('lib').childFile('flutter_project_web.dart'),
+        isNot(exists));
     validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[
       'some_platform'
     ], pluginClass: 'somePluginClass',
@@ -1686,8 +1777,8 @@
     await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
     await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=ios', projectDir.path]);
 
-    expect(projectDir.childDirectory('ios').existsSync(), true);
-    expect(projectDir.childDirectory('example').childDirectory('ios').existsSync(), true);
+    expect(projectDir.childDirectory('ios'), exists);
+    expect(projectDir.childDirectory('example').childDirectory('ios'), exists);
   }, overrides: <Type, Generator>{
     FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: false),
   });
@@ -1702,8 +1793,9 @@
     await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
     await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=android', projectDir.path]);
 
-    expect(projectDir.childDirectory('android').existsSync(), true);
-    expect(projectDir.childDirectory('example').childDirectory('android').existsSync(), true);
+    expect(projectDir.childDirectory('android'), exists);
+    expect(
+        projectDir.childDirectory('example').childDirectory('android'), exists);
   }, overrides: <Type, Generator>{
     FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: false),
   });
@@ -1718,8 +1810,9 @@
     await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
     await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=linux', projectDir.path]);
 
-    expect(projectDir.childDirectory('linux').existsSync(), true);
-    expect(projectDir.childDirectory('example').childDirectory('linux').existsSync(), true);
+    expect(projectDir.childDirectory('linux'), exists);
+    expect(
+        projectDir.childDirectory('example').childDirectory('linux'), exists);
   }, overrides: <Type, Generator>{
     FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
   });
@@ -1734,8 +1827,9 @@
     await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
     await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=macos', projectDir.path]);
 
-    expect(projectDir.childDirectory('macos').existsSync(), true);
-    expect(projectDir.childDirectory('example').childDirectory('macos').existsSync(), true);
+    expect(projectDir.childDirectory('macos'), exists);
+    expect(
+        projectDir.childDirectory('example').childDirectory('macos'), exists);
   }, overrides: <Type, Generator>{
     FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
   });
@@ -1750,8 +1844,9 @@
     await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
     await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=windows', projectDir.path]);
 
-    expect(projectDir.childDirectory('windows').existsSync(), true);
-    expect(projectDir.childDirectory('example').childDirectory('windows').existsSync(), true);
+    expect(projectDir.childDirectory('windows'), exists);
+    expect(
+        projectDir.childDirectory('example').childDirectory('windows'), exists);
   }, overrides: <Type, Generator>{
     FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
   });
@@ -1766,7 +1861,9 @@
     await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
     await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=web', projectDir.path]);
 
-    expect(projectDir.childDirectory('lib').childFile('flutter_project_web.dart').existsSync(), true);
+    expect(
+        projectDir.childDirectory('lib').childFile('flutter_project_web.dart'),
+        exists);
   }, overrides: <Type, Generator>{
     FeatureFlags: () => TestFeatureFlags(isWebEnabled: true),
   });
@@ -1779,18 +1876,19 @@
     final CreateCommand command = CreateCommand();
     final CommandRunner<void> runner = createTestCommandRunner(command);
     await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=ios', projectDir.path]);
-    expect(projectDir.childDirectory('ios').existsSync(), true);
-    expect(projectDir.childDirectory('example').childDirectory('ios').existsSync(), true);
+    expect(projectDir.childDirectory('ios'), exists);
+    expect(projectDir.childDirectory('example').childDirectory('ios'), exists);
     validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[
       'ios',
     ], pluginClass: 'FlutterProjectPlugin',
     unexpectedPlatforms: <String>['some_platform']);
 
     await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=macos', projectDir.path]);
-    expect(projectDir.childDirectory('macos').existsSync(), true);
-    expect(projectDir.childDirectory('example').childDirectory('macos').existsSync(), true);
-    expect(projectDir.childDirectory('ios').existsSync(), true);
-    expect(projectDir.childDirectory('example').childDirectory('ios').existsSync(), true);
+    expect(projectDir.childDirectory('macos'), exists);
+    expect(
+        projectDir.childDirectory('example').childDirectory('macos'), exists);
+    expect(projectDir.childDirectory('ios'), exists);