Cherry pick - Flutter 1.24 candidate.11 (#70424)

* Future can return null (#70324)

* Revert usages of the binding's platformDispatcher to use window instead (#70319)

This reverts usages of the binding's platformDispatcher to use window again temporarily, because there isn't a TestPlatformDispatcher yet, and so some tests were failing because they mocked the TestWindow to return certain things (locales) that were returning the real values instead of the test values.

Once I've created a TestPlatformDispatcher to allow fake data to be passed to it, we can go back to using the platformDispatcher in all of these places

* Revert "mark catalina ios tasks as flaky (#70255)" (#70328)

Co-authored-by: Emmanuel Garcia <egarciad@google.com>
Co-authored-by: Greg Spencer <gspencergoog@users.noreply.github.com>
Co-authored-by: keyonghan <54558023+keyonghan@users.noreply.github.com>
diff --git a/dev/devicelab/manifest.yaml b/dev/devicelab/manifest.yaml
index bda1cdb..14cda25 100644
--- a/dev/devicelab/manifest.yaml
+++ b/dev/devicelab/manifest.yaml
@@ -645,14 +645,12 @@
       A smoke test that runs on macOS Catalina, which is a clone of the Gallery startup latency test.
     stage: devicelab_ios
     required_agent_capabilities: ["mac-catalina/ios"]
-    flaky: true
 
   smoke_catalina_hot_mode_dev_cycle_ios__benchmark:
     description: >
       A some test that runs on macOS Catalina, which is a clone of the Dart VM hot patching performance benchmarking.
     stage: devicelab_ios
     required_agent_capabilities: ["mac-catalina/ios"]
-    flaky: true
 
   smoke_catalina_start_up:
     description: >
diff --git a/dev/integration_tests/flutter_gallery/lib/gallery/updater.dart b/dev/integration_tests/flutter_gallery/lib/gallery/updater.dart
index ce15012..9a81ac7 100644
--- a/dev/integration_tests/flutter_gallery/lib/gallery/updater.dart
+++ b/dev/integration_tests/flutter_gallery/lib/gallery/updater.dart
@@ -6,7 +6,7 @@
 
 import 'package:url_launcher/url_launcher.dart';
 
-typedef UpdateUrlFetcher = Future<String> Function();
+typedef UpdateUrlFetcher = Future<String?> Function();
 
 class Updater extends StatefulWidget {
   const Updater({ required this.updateUrlFetcher, this.child, Key? key })
@@ -35,9 +35,9 @@
     }
     _lastUpdateCheck = DateTime.now();
 
-    final String updateUrl = await widget.updateUrlFetcher();
+    final String? updateUrl = await widget.updateUrlFetcher();
     final bool? wantsUpdate = await showDialog<bool>(context: context, builder: _buildDialog);
-    if (wantsUpdate != null && wantsUpdate)
+    if (wantsUpdate != null && updateUrl != null && wantsUpdate)
       launch(updateUrl);
   }
 
diff --git a/packages/flutter/lib/src/foundation/binding.dart b/packages/flutter/lib/src/foundation/binding.dart
index e6c650e..e140e87 100644
--- a/packages/flutter/lib/src/foundation/binding.dart
+++ b/packages/flutter/lib/src/foundation/binding.dart
@@ -236,12 +236,12 @@
             }
             _postExtensionStateChangedEvent(
               brightnessOverrideExtensionName,
-              (debugBrightnessOverride ?? platformDispatcher.platformBrightness).toString(),
+              (debugBrightnessOverride ?? window.platformBrightness).toString(),
             );
             await reassembleApplication();
           }
           return <String, dynamic>{
-            'value': (debugBrightnessOverride ?? platformDispatcher.platformBrightness).toString(),
+            'value': (debugBrightnessOverride ?? window.platformBrightness).toString(),
           };
         },
       );
diff --git a/packages/flutter/lib/src/gestures/binding.dart b/packages/flutter/lib/src/gestures/binding.dart
index 8b42ad0..7aa5f08 100644
--- a/packages/flutter/lib/src/gestures/binding.dart
+++ b/packages/flutter/lib/src/gestures/binding.dart
@@ -198,7 +198,7 @@
   void initInstances() {
     super.initInstances();
     _instance = this;
-    platformDispatcher.onPointerDataPacket = _handlePointerDataPacket;
+    window.onPointerDataPacket = _handlePointerDataPacket;
   }
 
   @override
diff --git a/packages/flutter/lib/src/rendering/binding.dart b/packages/flutter/lib/src/rendering/binding.dart
index 77d86db..e83bf5d 100644
--- a/packages/flutter/lib/src/rendering/binding.dart
+++ b/packages/flutter/lib/src/rendering/binding.dart
@@ -33,7 +33,7 @@
       onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
       onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
     );
-    platformDispatcher
+    window
       ..onMetricsChanged = handleMetricsChanged
       ..onTextScaleFactorChanged = handleTextScaleFactorChanged
       ..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
@@ -280,7 +280,7 @@
   }
 
   void _handleSemanticsEnabledChanged() {
-    setSemanticsEnabled(platformDispatcher.semanticsEnabled);
+    setSemanticsEnabled(window.semanticsEnabled);
   }
 
   /// Whether the render tree associated with this binding should produce a tree
diff --git a/packages/flutter/lib/src/scheduler/binding.dart b/packages/flutter/lib/src/scheduler/binding.dart
index a57ac34..0fe1eca 100644
--- a/packages/flutter/lib/src/scheduler/binding.dart
+++ b/packages/flutter/lib/src/scheduler/binding.dart
@@ -265,10 +265,10 @@
   void addTimingsCallback(TimingsCallback callback) {
     _timingsCallbacks.add(callback);
     if (_timingsCallbacks.length == 1) {
-      assert(platformDispatcher.onReportTimings == null);
-      platformDispatcher.onReportTimings = _executeTimingsCallbacks;
+      assert(window.onReportTimings == null);
+      window.onReportTimings = _executeTimingsCallbacks;
     }
-    assert(platformDispatcher.onReportTimings == _executeTimingsCallbacks);
+    assert(window.onReportTimings == _executeTimingsCallbacks);
   }
 
   /// Removes a callback that was earlier added by [addTimingsCallback].
@@ -276,7 +276,7 @@
     assert(_timingsCallbacks.contains(callback));
     _timingsCallbacks.remove(callback);
     if (_timingsCallbacks.isEmpty) {
-      platformDispatcher.onReportTimings = null;
+      window.onReportTimings = null;
     }
   }
 
@@ -724,8 +724,8 @@
   /// [PlatformDispatcher.onDrawFrame] are registered.
   @protected
   void ensureFrameCallbacksRegistered() {
-    platformDispatcher.onBeginFrame ??= _handleBeginFrame;
-    platformDispatcher.onDrawFrame ??= _handleDrawFrame;
+    window.onBeginFrame ??= _handleBeginFrame;
+    window.onDrawFrame ??= _handleDrawFrame;
   }
 
   /// Schedules a new frame using [scheduleFrame] if this object is not
@@ -790,7 +790,7 @@
       return true;
     }());
     ensureFrameCallbacksRegistered();
-    platformDispatcher.scheduleFrame();
+    window.scheduleFrame();
     _hasScheduledFrame = true;
   }
 
@@ -827,7 +827,7 @@
         debugPrintStack(label: 'scheduleForcedFrame() called. Current phase is $schedulerPhase.');
       return true;
     }());
-    platformDispatcher.scheduleFrame();
+    window.scheduleFrame();
     _hasScheduledFrame = true;
   }
 
diff --git a/packages/flutter/lib/src/semantics/binding.dart b/packages/flutter/lib/src/semantics/binding.dart
index 62f9b82..131ff78 100644
--- a/packages/flutter/lib/src/semantics/binding.dart
+++ b/packages/flutter/lib/src/semantics/binding.dart
@@ -21,7 +21,7 @@
   void initInstances() {
     super.initInstances();
     _instance = this;
-    _accessibilityFeatures = platformDispatcher.accessibilityFeatures;
+    _accessibilityFeatures = window.accessibilityFeatures;
   }
 
   /// Called when the platform accessibility features change.
@@ -29,7 +29,7 @@
   /// See [dart:ui.PlatformDispatcher.onAccessibilityFeaturesChanged].
   @protected
   void handleAccessibilityFeaturesChanged() {
-    _accessibilityFeatures = platformDispatcher.accessibilityFeatures;
+    _accessibilityFeatures = window.accessibilityFeatures;
   }
 
   /// Creates an empty semantics update builder.
diff --git a/packages/flutter/lib/src/semantics/semantics.dart b/packages/flutter/lib/src/semantics/semantics.dart
index d30d1d7..f5fd3ab 100644
--- a/packages/flutter/lib/src/semantics/semantics.dart
+++ b/packages/flutter/lib/src/semantics/semantics.dart
@@ -2658,7 +2658,7 @@
       final CustomSemanticsAction action = CustomSemanticsAction.getAction(actionId)!;
       builder.updateCustomAction(id: actionId, label: action.label, hint: action.hint, overrideId: action.action?.index ?? -1);
     }
-    SemanticsBinding.instance!.platformDispatcher.updateSemantics(builder.build());
+    SemanticsBinding.instance!.window.updateSemantics(builder.build());
     notifyListeners();
   }
 
diff --git a/packages/flutter/lib/src/services/binding.dart b/packages/flutter/lib/src/services/binding.dart
index b4fa2c8..8992ba6 100644
--- a/packages/flutter/lib/src/services/binding.dart
+++ b/packages/flutter/lib/src/services/binding.dart
@@ -28,7 +28,7 @@
     _instance = this;
     _defaultBinaryMessenger = createBinaryMessenger();
     _restorationManager = createRestorationManager();
-    platformDispatcher.onPlatformMessage = defaultBinaryMessenger.handlePlatformMessage;
+    window.onPlatformMessage = defaultBinaryMessenger.handlePlatformMessage;
     initLicenses();
     SystemChannels.system.setMessageHandler((dynamic message) => handleSystemMessage(message as Object));
     SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
diff --git a/packages/flutter/lib/src/widgets/app.dart b/packages/flutter/lib/src/widgets/app.dart
index 1a14c9c..2b4d801 100644
--- a/packages/flutter/lib/src/widgets/app.dart
+++ b/packages/flutter/lib/src/widgets/app.dart
@@ -1113,15 +1113,15 @@
   // If window.defaultRouteName isn't '/', we should assume it was set
   // intentionally via `setInitialRoute`, and should override whatever is in
   // [widget.initialRoute].
-  String get _initialRouteName => WidgetsBinding.instance!.platformDispatcher.defaultRouteName != Navigator.defaultRouteName
-    ? WidgetsBinding.instance!.platformDispatcher.defaultRouteName
-    : widget.initialRoute ?? WidgetsBinding.instance!.platformDispatcher.defaultRouteName;
+  String get _initialRouteName => WidgetsBinding.instance!.window.defaultRouteName != Navigator.defaultRouteName
+    ? WidgetsBinding.instance!.window.defaultRouteName
+    : widget.initialRoute ?? WidgetsBinding.instance!.window.defaultRouteName;
 
   @override
   void initState() {
     super.initState();
     _updateRouting();
-    _locale = _resolveLocales(WidgetsBinding.instance!.platformDispatcher.locales, widget.supportedLocales);
+    _locale = _resolveLocales(WidgetsBinding.instance!.window.locales, widget.supportedLocales);
     WidgetsBinding.instance!.addObserver(this);
   }
 
diff --git a/packages/flutter/lib/src/widgets/binding.dart b/packages/flutter/lib/src/widgets/binding.dart
index 22c4e08..c8a6cfd 100644
--- a/packages/flutter/lib/src/widgets/binding.dart
+++ b/packages/flutter/lib/src/widgets/binding.dart
@@ -245,7 +245,7 @@
   ///
   /// This method exposes notifications from
   /// [dart:ui.PlatformDispatcher.onLocaleChanged].
-  void didChangeLocales(List<Locale>? locale) { }
+  void didChangeLocales(List<Locale>? locales) { }
 
   /// Called when the system puts the app in the background or returns
   /// the app to the foreground.
@@ -287,8 +287,8 @@
     // properly setup the [defaultBinaryMessenger] instance.
     _buildOwner = BuildOwner();
     buildOwner!.onBuildScheduled = _handleBuildScheduled;
-    platformDispatcher.onLocaleChanged = handleLocaleChanged;
-    platformDispatcher.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
+    window.onLocaleChanged = handleLocaleChanged;
+    window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
     SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
     FlutterErrorDetails.propertiesTransformers.add(transformDebugCreator);
   }
@@ -586,7 +586,7 @@
   @protected
   @mustCallSuper
   void handleLocaleChanged() {
-    dispatchLocalesChanged(platformDispatcher.locales);
+    dispatchLocalesChanged(window.locales);
   }
 
   /// Notify all the observers that the locale has changed (using
@@ -1016,7 +1016,7 @@
   /// method again with the matched locale of the first call omitted from
   /// `supportedLocales`.
   Locale? computePlatformResolvedLocale(List<Locale> supportedLocales) {
-    return platformDispatcher.computePlatformResolvedLocale(supportedLocales);
+    return window.computePlatformResolvedLocale(supportedLocales);
   }
 }
 
diff --git a/packages/flutter/test/widgets/binding_first_frame_rasterized_test.dart b/packages/flutter/test/widgets/binding_first_frame_rasterized_test.dart
index 45eda8f..cb179de 100644
--- a/packages/flutter/test/widgets/binding_first_frame_rasterized_test.dart
+++ b/packages/flutter/test/widgets/binding_first_frame_rasterized_test.dart
@@ -19,14 +19,14 @@
 
       // Simulates the engine completing a frame render to trigger the
       // appropriate callback setting [WidgetBinding.firstFrameRasterized].
-      binding.platformDispatcher.onReportTimings!(<FrameTiming>[]);
+      binding.window.onReportTimings!(<FrameTiming>[]);
       expect(binding.firstFrameRasterized, isFalse);
 
       binding.allowFirstFrame();
       fakeAsync.flushTimers();
 
       // Simulates the engine again.
-      binding.platformDispatcher.onReportTimings!(<FrameTiming>[]);
+      binding.window.onReportTimings!(<FrameTiming>[]);
       expect(binding.firstFrameRasterized, isTrue);
     });
   });
diff --git a/packages/flutter_test/lib/src/binding.dart b/packages/flutter_test/lib/src/binding.dart
index d1714f0..dce1080 100644
--- a/packages/flutter_test/lib/src/binding.dart
+++ b/packages/flutter_test/lib/src/binding.dart
@@ -1039,8 +1039,8 @@
   @override
   void ensureFrameCallbacksRegistered() {
     // Leave PlatformDispatcher alone, do nothing.
-    assert(platformDispatcher.onDrawFrame == null);
-    assert(platformDispatcher.onBeginFrame == null);
+    assert(window.onDrawFrame == null);
+    assert(window.onBeginFrame == null);
   }
 
   @override
diff --git a/packages/flutter_test/test/window_test.dart b/packages/flutter_test/test/window_test.dart
index bfde8d8..0c9d037 100644
--- a/packages/flutter_test/test/window_test.dart
+++ b/packages/flutter_test/test/window_test.dart
@@ -5,7 +5,7 @@
 import 'dart:ui' as ui show window;
 import 'dart:ui' show Size, Locale, WindowPadding, AccessibilityFeatures, Brightness;
 
-import 'package:flutter/widgets.dart' show WidgetsBinding;
+import 'package:flutter/widgets.dart' show WidgetsBinding, WidgetsBindingObserver;
 import 'package:flutter_test/flutter_test.dart';
 
 void main() {
@@ -184,6 +184,14 @@
     expect(WidgetsBinding.instance!.window.devicePixelRatio, originalDevicePixelRatio);
     expect(WidgetsBinding.instance!.window.textScaleFactor, originalTextScaleFactor);
   });
+
+  testWidgets('TestWindow sends fake locales when WidgetsBindingObserver notifiers are called', (WidgetTester tester) async {
+    final TestObserver observer = TestObserver();
+    retrieveTestBinding(tester).addObserver(observer);
+    final List<Locale> expectedValue = <Locale>[const Locale('fake_language_code')];
+    retrieveTestBinding(tester).window.localesTestValue = expectedValue;
+    expect(observer.locales, equals(expectedValue));
+  });
 }
 
 void verifyThatTestWindowCanFakeProperty<WindowPropertyType>({
@@ -273,3 +281,13 @@
     return null;
   }
 }
+
+class TestObserver with WidgetsBindingObserver {
+  List<Locale>? locales;
+  Locale? locale;
+
+  @override
+  void didChangeLocales(List<Locale>? locales) {
+    this.locales = locales;
+  }
+}