Support Hybrid Composition on Android (#4017)

diff --git a/packages/google_maps_flutter/google_maps_flutter/test/google_map_test.dart b/packages/google_maps_flutter/google_maps_flutter/test/google_map_test.dart
index 2b754af..d1ec87a 100644
--- a/packages/google_maps_flutter/google_maps_flutter/test/google_map_test.dart
+++ b/packages/google_maps_flutter/google_maps_flutter/test/google_map_test.dart
@@ -587,4 +587,34 @@
 
     expect(platformGoogleMap.buildingsEnabled, true);
   });
+
+  testWidgets(
+    'Default Android widget is AndroidView',
+    (WidgetTester tester) async {
+      await tester.pumpWidget(
+        const Directionality(
+          textDirection: TextDirection.ltr,
+          child: GoogleMap(
+            initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)),
+          ),
+        ),
+      );
+
+      expect(find.byType(AndroidView), findsOneWidget);
+    },
+  );
+
+  // TODO(bparrishMines): Uncomment once https://github.com/flutter/plugins/pull/4017 has landed.
+  // testWidgets('Use AndroidViewSurface on Android', (WidgetTester tester) async {
+  //   await tester.pumpWidget(
+  //     const Directionality(
+  //       textDirection: TextDirection.ltr,
+  //       child: GoogleMap(
+  //         initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)),
+  //       ),
+  //     ),
+  //   );
+  //
+  //   expect(find.byType(AndroidViewSurface), findsOneWidget);
+  // });
 }
diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md
index b6603d6..2dc533f 100644
--- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md
+++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 2.1.0
+
+* Add support for Hybrid Composition when building the Google Maps widget on Android. Set
+  `MethodChannelGoogleMapsFlutter.useAndroidViewSurface` to `true` to build with Hybrid Composition.
+
 ## 2.0.4
 
 * Preserve the `TileProvider` when copying `TileOverlay`, fixing a
diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/google_maps_flutter_platform_interface.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/google_maps_flutter_platform_interface.dart
index 650a839..3007000 100644
--- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/google_maps_flutter_platform_interface.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/google_maps_flutter_platform_interface.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.
 
+export 'src/method_channel/method_channel_google_maps_flutter.dart'
+    show MethodChannelGoogleMapsFlutter;
 export 'src/platform_interface/google_maps_flutter_platform.dart';
 export 'src/types/types.dart';
 export 'src/events/map_event.dart';
diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart
index 49029cc..41aedc7 100644
--- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart
@@ -8,6 +8,7 @@
 import 'package:flutter/foundation.dart';
 import 'package:flutter/gestures.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
 import 'package:flutter/services.dart';
 import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
 import 'package:stream_transform/stream_transform.dart';
@@ -441,6 +442,98 @@
     return channel(mapId).invokeMethod<Uint8List>('map#takeSnapshot');
   }
 
+  /// Set [GoogleMapsFlutterPlatform] to use [AndroidViewSurface] to build the Google Maps widget.
+  ///
+  /// This implementation uses hybrid composition to render the Google Maps
+  /// Widget on Android. This comes at the cost of some performance on Android
+  /// versions below 10. See
+  /// https://flutter.dev/docs/development/platform-integration/platform-views#performance for more
+  /// information.
+  ///
+  /// If set to true, the google map widget should be built with
+  /// [buildViewWithTextDirection] instead of [buildView].
+  ///
+  /// Defaults to false.
+  bool useAndroidViewSurface = false;
+
+  /// Returns a widget displaying the map view.
+  ///
+  /// This method includes a parameter for platforms that require a text
+  /// direction. For example, this should be used when using hybrid composition
+  /// on Android.
+  Widget buildViewWithTextDirection(
+    int creationId,
+    PlatformViewCreatedCallback onPlatformViewCreated, {
+    required CameraPosition initialCameraPosition,
+    required TextDirection textDirection,
+    Set<Marker> markers = const <Marker>{},
+    Set<Polygon> polygons = const <Polygon>{},
+    Set<Polyline> polylines = const <Polyline>{},
+    Set<Circle> circles = const <Circle>{},
+    Set<TileOverlay> tileOverlays = const <TileOverlay>{},
+    Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers,
+    Map<String, dynamic> mapOptions = const <String, dynamic>{},
+  }) {
+    if (defaultTargetPlatform == TargetPlatform.android &&
+        useAndroidViewSurface) {
+      final Map<String, dynamic> creationParams = <String, dynamic>{
+        'initialCameraPosition': initialCameraPosition.toMap(),
+        'options': mapOptions,
+        'markersToAdd': serializeMarkerSet(markers),
+        'polygonsToAdd': serializePolygonSet(polygons),
+        'polylinesToAdd': serializePolylineSet(polylines),
+        'circlesToAdd': serializeCircleSet(circles),
+        'tileOverlaysToAdd': serializeTileOverlaySet(tileOverlays),
+      };
+      return PlatformViewLink(
+        viewType: 'plugins.flutter.io/google_maps',
+        surfaceFactory: (
+          BuildContext context,
+          PlatformViewController controller,
+        ) {
+          return AndroidViewSurface(
+            controller: controller as AndroidViewController,
+            gestureRecognizers: gestureRecognizers ??
+                const <Factory<OneSequenceGestureRecognizer>>{},
+            hitTestBehavior: PlatformViewHitTestBehavior.opaque,
+          );
+        },
+        onCreatePlatformView: (PlatformViewCreationParams params) {
+          final SurfaceAndroidViewController controller =
+              PlatformViewsService.initSurfaceAndroidView(
+            id: params.id,
+            viewType: 'plugins.flutter.io/google_maps',
+            layoutDirection: textDirection,
+            creationParams: creationParams,
+            creationParamsCodec: const StandardMessageCodec(),
+            onFocus: () => params.onFocusChanged(true),
+          );
+          controller.addOnPlatformViewCreatedListener(
+            params.onPlatformViewCreated,
+          );
+          controller.addOnPlatformViewCreatedListener(
+            onPlatformViewCreated,
+          );
+
+          controller.create();
+          return controller;
+        },
+      );
+    }
+    return buildView(
+      creationId,
+      onPlatformViewCreated,
+      initialCameraPosition: initialCameraPosition,
+      markers: markers,
+      polygons: polygons,
+      polylines: polylines,
+      circles: circles,
+      tileOverlays: tileOverlays,
+      gestureRecognizers: gestureRecognizers,
+      mapOptions: mapOptions,
+    );
+  }
+
   @override
   Widget buildView(
     int creationId,
diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml
index 5b278a8..1ea425e 100644
--- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml
+++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml
@@ -4,7 +4,7 @@
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22
 # NOTE: We strongly prefer non-breaking changes, even at the expense of a
 # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes
-version: 2.0.4
+version: 2.1.0
 
 environment:
   sdk: '>=2.12.0 <3.0.0'