feat(cronet_http): Add support for QUIC hints (#1873)

* feat(cronet_http): Add support for QUIC hints

* Fix comment

* Update version

* Update cronet_configuration_test.dart

* Update cronet_configuration_test.dart

* Update cronet_configuration_test.dart

* Fix tests

* Add wip

* Update pkgs/cronet_http/lib/src/cronet_client.dart

Co-authored-by: Michael Goderbauer <goderbauer@google.com>

---------

Co-authored-by: Michael Goderbauer <goderbauer@google.com>
diff --git a/pkgs/cronet_http/CHANGELOG.md b/pkgs/cronet_http/CHANGELOG.md
index 72a8ad0..aa525e7 100644
--- a/pkgs/cronet_http/CHANGELOG.md
+++ b/pkgs/cronet_http/CHANGELOG.md
@@ -1,8 +1,9 @@
-## 1.7.1-wip
+## 1.8.0-wip
 
 * Made callbacks asynchronous to prevent background errors caused by the
   unavailability of the Dart callback.
 * Change the compile SDK version to 35 for compatibility with `package:jni`.
+* Add support for QUIC hints.
 
 ## 1.7.0
 
diff --git a/pkgs/cronet_http/example/integration_test/cronet_configuration_test.dart b/pkgs/cronet_http/example/integration_test/cronet_configuration_test.dart
index 63ce8eb..7b06862 100644
--- a/pkgs/cronet_http/example/integration_test/cronet_configuration_test.dart
+++ b/pkgs/cronet_http/example/integration_test/cronet_configuration_test.dart
@@ -121,6 +121,49 @@
   });
 }
 
+void testQuicHints() {
+  group('quicHints', () {
+    late HttpServer server;
+
+    setUp(() async {
+      server = (await HttpServer.bind('localhost', 0))
+        ..listen((request) async {
+          await request.drain<void>();
+          request.response.headers.set('Content-Type', 'text/plain');
+          request.response.write('Hello World');
+          await request.response.close();
+        });
+    });
+    tearDown(() {
+      server.close();
+    });
+
+    test('quic hints', () async {
+      final engine = CronetEngine.build(
+          cacheMode: CacheMode.diskNoHttp,
+          enableQuic: true,
+          enableHttp2: false,
+          quicHints: [
+            ('www.google.com', 443, 443),
+          ]);
+      final response = await CronetClient.fromCronetEngine(engine)
+          .send(Request('GET', Uri.parse('https://www.google.com/')));
+      expect(response.negotiatedProtocol, 'h3');
+    }, skip: 'requires internet access');
+
+    test('no quic hints', () async {
+      final engine = CronetEngine.build(
+        cacheMode: CacheMode.diskNoHttp,
+        enableQuic: true,
+        enableHttp2: false,
+      );
+      final response = await CronetClient.fromCronetEngine(engine)
+          .send(Request('GET', Uri.parse('https://www.google.com/')));
+      expect(response.negotiatedProtocol, 'http1.1');
+    }, skip: 'requires internet access');
+  });
+}
+
 void testEngineClose() {
   group('engine close', () {
     test('multiple close', () {
@@ -155,5 +198,6 @@
   testCache();
   testInvalidConfigurations();
   testUserAgent();
+  testQuicHints();
   testEngineClose();
 }
diff --git a/pkgs/cronet_http/lib/src/cronet_client.dart b/pkgs/cronet_http/lib/src/cronet_client.dart
index b3146ae..abf6bbf 100644
--- a/pkgs/cronet_http/lib/src/cronet_client.dart
+++ b/pkgs/cronet_http/lib/src/cronet_client.dart
@@ -119,6 +119,11 @@
   /// should be used per [CronetEngine].
   ///
   /// [userAgent] controls the `User-Agent` header.
+  ///
+  /// [quicHints] adds a list of hosts that support QUIC. Each hint is a tuple
+  /// of (host, port, alternativePort) that indicates that the host supports
+  /// QUIC. Note that [CacheMode.disk] or [CacheMode.diskNoHttp] is needed to
+  /// take advantage of 0-RTT connection establishment between sessions.
   static CronetEngine build(
       {CacheMode? cacheMode,
       int? cacheMaxSize,
@@ -127,7 +132,8 @@
       bool? enablePublicKeyPinningBypassForLocalTrustAnchors,
       bool? enableQuic,
       String? storagePath,
-      String? userAgent}) {
+      String? userAgent,
+      List<(String, int, int)>? quicHints}) {
     try {
       return using((arena) {
         final builder = jb.CronetEngine$Builder(
@@ -173,6 +179,13 @@
               ?.release();
         }
 
+        if (quicHints != null) {
+          for (final (host, port, alternativePort) in quicHints) {
+            builder.addQuicHint(
+                host.toJString()..releasedBy(arena), port, alternativePort);
+          }
+        }
+
         return CronetEngine._(builder.build()!);
       });
     } on JniException catch (e) {
diff --git a/pkgs/cronet_http/pubspec.yaml b/pkgs/cronet_http/pubspec.yaml
index d88b592..074d26b 100644
--- a/pkgs/cronet_http/pubspec.yaml
+++ b/pkgs/cronet_http/pubspec.yaml
@@ -1,5 +1,5 @@
 name: cronet_http
-version: 1.7.1-wip
+version: 1.8.0-wip
 description: >-
   An Android Flutter plugin that provides access to the Cronet HTTP client.
 repository: https://github.com/dart-lang/http/tree/master/pkgs/cronet_http