Started handling messages from background isolates. (#109005)

Started handling messages from background isolates.
diff --git a/dev/integration_tests/channels/android/app/src/main/AndroidManifest.xml b/dev/integration_tests/channels/android/app/src/main/AndroidManifest.xml
index 6bf32c2..d63de06 100644
--- a/dev/integration_tests/channels/android/app/src/main/AndroidManifest.xml
+++ b/dev/integration_tests/channels/android/app/src/main/AndroidManifest.xml
@@ -18,6 +18,7 @@
          Application and put your custom class here. -->
     <application android:name="${applicationName}" android:label="channels" android:icon="@mipmap/ic_launcher">
         <activity android:name=".MainActivity"
+                  android:exported="true"
                   android:launchMode="singleTop"
                   android:theme="@android:style/Theme.Black.NoTitleBar"
                   android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
diff --git a/dev/integration_tests/channels/ios/Runner/AppDelegate.m b/dev/integration_tests/channels/ios/Runner/AppDelegate.m
index 719abd3..8fc53c3 100644
--- a/dev/integration_tests/channels/ios/Runner/AppDelegate.m
+++ b/dev/integration_tests/channels/ios/Runner/AppDelegate.m
@@ -114,7 +114,18 @@
     [FlutterMethodChannel methodChannelWithName:@"std-method"
                                 binaryMessenger:flutterController
                                           codec:[FlutterStandardMethodCodec codecWithReaderWriter:extendedReaderWriter]]];
-  return [super application:application didFinishLaunchingWithOptions:launchOptions];
+
+  [[FlutterBasicMessageChannel
+      messageChannelWithName:@"std-echo"
+             binaryMessenger:flutterController
+                       codec:[FlutterStandardMessageCodec
+                                 codecWithReaderWriter:extendedReaderWriter]]
+      setMessageHandler:^(id message, FlutterReply reply) {
+        reply(message);
+      }];
+
+  return [super application:application
+      didFinishLaunchingWithOptions:launchOptions];
 }
 
 - (void)setupMessagingHandshakeOnChannel:(FlutterBasicMessageChannel*)channel {
diff --git a/dev/integration_tests/channels/lib/main.dart b/dev/integration_tests/channels/lib/main.dart
index 2a1489d..87e99ad 100644
--- a/dev/integration_tests/channels/lib/main.dart
+++ b/dev/integration_tests/channels/lib/main.dart
@@ -173,6 +173,8 @@
     () => basicStringMessageToUnknownChannel(),
     () => basicJsonMessageToUnknownChannel(),
     () => basicStandardMessageToUnknownChannel(),
+    if (Platform.isIOS)
+      () => basicBackgroundStandardEcho(123),
   ];
   Future<TestStepResult>? _result;
   int _step = 0;
diff --git a/dev/integration_tests/channels/lib/src/basic_messaging.dart b/dev/integration_tests/channels/lib/src/basic_messaging.dart
index 08392fc..c7a4133 100644
--- a/dev/integration_tests/channels/lib/src/basic_messaging.dart
+++ b/dev/integration_tests/channels/lib/src/basic_messaging.dart
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import 'dart:async';
+import 'dart:isolate';
 
 import 'package:flutter/services.dart';
 
@@ -78,6 +79,41 @@
       'Standard >${toString(message)}<', channel, message);
 }
 
+Future<void> _basicBackgroundStandardEchoMain(List<Object> args) async {
+  final SendPort sendPort = args[2] as SendPort;
+  final Object message = args[1];
+  final String name = 'Background Echo >${toString(message)}<';
+  const String description =
+      'Uses a platform channel from a background isolate.';
+  try {
+    BackgroundIsolateBinaryMessenger.ensureInitialized(
+        args[0] as RootIsolateToken);
+    const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
+      'std-echo',
+      ExtendedStandardMessageCodec(),
+    );
+    final Object response = await channel.send(message) as Object;
+
+    final TestStatus testStatus = TestStepResult.deepEquals(message, response)
+        ? TestStatus.ok
+        : TestStatus.failed;
+    sendPort.send(TestStepResult(name, description, testStatus));
+  } catch (ex) {
+    sendPort.send(TestStepResult(name, description, TestStatus.failed,
+        error: ex.toString()));
+  }
+}
+
+Future<TestStepResult> basicBackgroundStandardEcho(Object message) async {
+  final ReceivePort receivePort = ReceivePort();
+  Isolate.spawn(_basicBackgroundStandardEchoMain, <Object>[
+    ServicesBinding.instance.rootIsolateToken!,
+    message,
+    receivePort.sendPort,
+  ]);
+  return await receivePort.first as TestStepResult;
+}
+
 Future<TestStepResult> basicBinaryMessageToUnknownChannel() async {
   const BasicMessageChannel<ByteData?> channel =
       BasicMessageChannel<ByteData?>(
diff --git a/dev/integration_tests/channels/lib/src/test_step.dart b/dev/integration_tests/channels/lib/src/test_step.dart
index 6702560..b449099 100644
--- a/dev/integration_tests/channels/lib/src/test_step.dart
+++ b/dev/integration_tests/channels/lib/src/test_step.dart
@@ -90,6 +90,8 @@
       ],
     );
   }
+
+  static bool deepEquals(dynamic a, dynamic b) => _deepEquals(a, b);
 }
 
 Future<TestStepResult> resultOfHandshake(
diff --git a/packages/flutter/lib/src/services/binding.dart b/packages/flutter/lib/src/services/binding.dart
index 5fd8436..964238f 100644
--- a/packages/flutter/lib/src/services/binding.dart
+++ b/packages/flutter/lib/src/services/binding.dart
@@ -82,6 +82,21 @@
   BinaryMessenger get defaultBinaryMessenger => _defaultBinaryMessenger;
   late final BinaryMessenger _defaultBinaryMessenger;
 
+  /// A token that represents the root isolate, used for coordinating with background
+  /// isolates.
+  ///
+  /// This property is primarily intended for use with
+  /// [BackgroundIsolateBinaryMessenger.ensureInitialized], which takes a
+  /// [RootIsolateToken] as its argument. The value `null` is returned when
+  /// executed from background isolates.
+  ui.RootIsolateToken? get rootIsolateToken => ui.RootIsolateToken.instance;
+
+  /// Returns `true` if executed on a background (non-root) isolate.
+  ///
+  /// The value `false` will always be returned on web since there is no notion
+  /// of root/background isolates on the web.
+  bool get useBackgroundIsolateBinaryMessenger => !kIsWeb && rootIsolateToken == null;
+
   /// The low level buffering and dispatch mechanism for messages sent by
   /// plugins on the engine side to their corresponding plugin code on
   /// the framework side.
diff --git a/packages/flutter/lib/src/services/platform_channel.dart b/packages/flutter/lib/src/services/platform_channel.dart
index 66f1e7e..f7811c7 100644
--- a/packages/flutter/lib/src/services/platform_channel.dart
+++ b/packages/flutter/lib/src/services/platform_channel.dart
@@ -4,6 +4,8 @@
 
 import 'dart:async';
 import 'dart:developer';
+import 'dart:isolate' show ReceivePort;
+import 'dart:ui' as ui;
 
 import 'package:flutter/foundation.dart';
 
@@ -13,6 +15,7 @@
 import 'message_codec.dart';
 import 'message_codecs.dart';
 
+export 'dart:ui' show RootIsolateToken;
 export 'binary_messenger.dart' show BinaryMessenger;
 export 'message_codec.dart' show MessageCodec, MethodCall, MethodCodec;
 
@@ -123,6 +126,93 @@
   _debugLaunchProfilePlatformChannels();
 }
 
+/// A [BinaryMessenger] for use on background (non-root) isolates.
+class BackgroundIsolateBinaryMessenger extends BinaryMessenger {
+  BackgroundIsolateBinaryMessenger._();
+
+  final ReceivePort _receivePort = ReceivePort();
+  final Map<int, Completer<ByteData?>> _completers = <int, Completer<ByteData?>>{};
+  int _messageCount = 0;
+
+  /// The existing instance of this class, if any.
+  ///
+  /// Throws if [ensureInitialized] has not been called at least once.
+  static BinaryMessenger get instance {
+    if (_instance == null) {
+      throw StateError(
+          'The BackgroundIsolateBinaryMessenger.instance value is invalid '
+          'until BackgroundIsolateBinaryMessenger.ensureInitialized is '
+          'executed.');
+    }
+    return _instance!;
+  }
+
+  static BinaryMessenger? _instance;
+
+  /// Ensures that [BackgroundIsolateBinaryMessenger.instance] has been initialized.
+  ///
+  /// The argument should be the value obtained from [ServicesBinding.rootIsolateToken]
+  /// on the root isolate.
+  ///
+  /// This function is idempotent (calling it multiple times is harmless but has no effect).
+  static void ensureInitialized(ui.RootIsolateToken token) {
+    if (_instance == null) {
+      ui.PlatformDispatcher.instance.registerBackgroundIsolate(token);
+      final BackgroundIsolateBinaryMessenger portBinaryMessenger = BackgroundIsolateBinaryMessenger._();
+      _instance = portBinaryMessenger;
+      portBinaryMessenger._receivePort.listen((dynamic message) {
+        try {
+          final List<dynamic> args = message as List<dynamic>;
+          final int identifier = args[0] as int;
+          final Uint8List bytes = args[1] as Uint8List;
+          final ByteData byteData = ByteData.sublistView(bytes);
+          portBinaryMessenger._completers.remove(identifier)!.complete(byteData);
+        } catch (exception, stack) {
+          FlutterError.reportError(FlutterErrorDetails(
+            exception: exception,
+            stack: stack,
+            library: 'services library',
+            context:
+                ErrorDescription('during a platform message response callback'),
+          ));
+        }
+      });
+    }
+  }
+
+  @override
+  Future<void> handlePlatformMessage(String channel, ByteData? data, ui.PlatformMessageResponseCallback? callback) {
+    throw UnimplementedError('handlePlatformMessage is deprecated.');
+  }
+
+  @override
+  Future<ByteData?>? send(String channel, ByteData? message) {
+    final Completer<ByteData?> completer = Completer<ByteData?>();
+    _messageCount += 1;
+    final int messageIdentifier = _messageCount;
+    _completers[messageIdentifier] = completer;
+    ui.PlatformDispatcher.instance.sendPortPlatformMessage(
+      channel,
+      message,
+      messageIdentifier,
+      _receivePort.sendPort,
+    );
+    return completer.future;
+  }
+
+  @override
+  void setMessageHandler(String channel, MessageHandler? handler) {
+    throw UnsupportedError(
+        'Background isolates do not support setMessageHandler(). Messages from the host platform always go to the root isolate.');
+  }
+}
+
+BinaryMessenger _findBinaryMessenger() {
+  return ServicesBinding.instance.useBackgroundIsolateBinaryMessenger
+      ? BackgroundIsolateBinaryMessenger.instance
+      : ServicesBinding.instance.defaultBinaryMessenger;
+}
+
 /// A named channel for communicating with platform plugins using asynchronous
 /// message passing.
 ///
@@ -160,10 +250,14 @@
   /// The message codec used by this channel, not null.
   final MessageCodec<T> codec;
 
-  /// The messenger which sends the bytes for this channel, not null.
+  /// The messenger which sends the bytes for this channel.
+  ///
+  /// On the root isolate or web, this defaults to the
+  /// [ServicesBinding.defaultBinaryMessenger]. In other contexts the default
+  /// value is a [BackgroundIsolateBinaryMessenger] from
+  /// [BackgroundIsolateBinaryMessenger.ensureInitialized].
   BinaryMessenger get binaryMessenger {
-    final BinaryMessenger result =
-        _binaryMessenger ?? ServicesBinding.instance.defaultBinaryMessenger;
+    final BinaryMessenger result = _binaryMessenger ?? _findBinaryMessenger();
     return !kReleaseMode && debugProfilePlatformChannels
         ? _debugBinaryMessengers[this] ??= _ProfiledBinaryMessenger(
             // ignore: no_runtimetype_tostring
@@ -246,12 +340,14 @@
   /// The message codec used by this channel, not null.
   final MethodCodec codec;
 
-  /// The messenger used by this channel to send platform messages.
+  /// The messenger which sends the bytes for this channel.
   ///
-  /// The messenger may not be null.
+  /// On the root isolate or web, this defaults to the
+  /// [ServicesBinding.defaultBinaryMessenger]. In other contexts the default
+  /// value is a [BackgroundIsolateBinaryMessenger] from
+  /// [BackgroundIsolateBinaryMessenger.ensureInitialized].
   BinaryMessenger get binaryMessenger {
-    final BinaryMessenger result =
-        _binaryMessenger ?? ServicesBinding.instance.defaultBinaryMessenger;
+    final BinaryMessenger result = _binaryMessenger ?? _findBinaryMessenger();
     return !kReleaseMode && debugProfilePlatformChannels
         ? _debugBinaryMessengers[this] ??= _ProfiledBinaryMessenger(
             // ignore: no_runtimetype_tostring
@@ -600,8 +696,14 @@
   /// The message codec used by this channel, not null.
   final MethodCodec codec;
 
-  /// The messenger used by this channel to send platform messages, not null.
-  BinaryMessenger get binaryMessenger => _binaryMessenger ?? ServicesBinding.instance.defaultBinaryMessenger;
+  /// The messenger which sends the bytes for this channel.
+  ///
+  /// On the root isolate or web, this defaults to the
+  /// [ServicesBinding.defaultBinaryMessenger]. In other contexts the default
+  /// value is a [BackgroundIsolateBinaryMessenger] from
+  /// [BackgroundIsolateBinaryMessenger.ensureInitialized].
+  BinaryMessenger get binaryMessenger =>
+      _binaryMessenger ?? _findBinaryMessenger();
   final BinaryMessenger? _binaryMessenger;
 
   /// Sets up a broadcast stream for receiving events on this channel.
diff --git a/packages/flutter_test/lib/src/binding.dart b/packages/flutter_test/lib/src/binding.dart
index 758b32c..cc4bb37 100644
--- a/packages/flutter_test/lib/src/binding.dart
+++ b/packages/flutter_test/lib/src/binding.dart
@@ -104,6 +104,9 @@
   TestDefaultBinaryMessenger createBinaryMessenger() {
     return TestDefaultBinaryMessenger(super.createBinaryMessenger());
   }
+
+  @override
+  bool get useBackgroundIsolateBinaryMessenger => false;
 }
 
 /// Base class for bindings used by widgets library tests.