Support Hybrid Composition through the GoogleMaps Widget (#4082)

diff --git a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md
index 0c95abd..4bc4ce3 100644
--- a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md
+++ b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 2.1.0
+
+* Add iOS unit and UI integration test targets.
+* Provide access to Hybrid Composition on Android through the `GoogleMap` widget.
+
 ## 2.0.11
 
 * Add additional marker drag events.
diff --git a/packages/google_maps_flutter/google_maps_flutter/README.md b/packages/google_maps_flutter/google_maps_flutter/README.md
index 99c04f3..038126f 100644
--- a/packages/google_maps_flutter/google_maps_flutter/README.md
+++ b/packages/google_maps_flutter/google_maps_flutter/README.md
@@ -46,6 +46,18 @@
                android:value="YOUR KEY HERE"/>
 ```
 
+#### Hybrid Composition
+
+To use [Hybrid Composition](https://flutter.dev/docs/development/platform-integration/platform-views)
+to render the `GoogleMap` widget on Android, set `AndroidGoogleMapsFlutter.useAndroidViewSurface` to
+true.
+
+```dart
+if (defaultTargetPlatform == TargetPlatform.android) {
+  AndroidGoogleMapsFlutter.useAndroidViewSurface = true;
+}
+```
+
 ### iOS
 
 This plugin requires iOS 9.0 or higher. To set up, specify your API key in the application delegate `ios/Runner/AppDelegate.m`:
diff --git a/packages/google_maps_flutter/google_maps_flutter/example/lib/main.dart b/packages/google_maps_flutter/google_maps_flutter/example/lib/main.dart
index 15b14db..f67e4ff 100644
--- a/packages/google_maps_flutter/google_maps_flutter/example/lib/main.dart
+++ b/packages/google_maps_flutter/google_maps_flutter/example/lib/main.dart
@@ -4,7 +4,10 @@
 
 // ignore_for_file: public_member_api_docs
 
+import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
+
+import 'package:google_maps_flutter/google_maps_flutter.dart';
 import 'package:google_maps_flutter_example/lite_mode.dart';
 import 'animate_camera.dart';
 import 'map_click.dart';
@@ -66,5 +69,8 @@
 }
 
 void main() {
+  if (defaultTargetPlatform == TargetPlatform.android) {
+    AndroidGoogleMapsFlutter.useAndroidViewSurface = true;
+  }
   runApp(MaterialApp(home: MapsDemo()));
 }
diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart
index b4ffd22..b153832 100644
--- a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart
+++ b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart
@@ -38,6 +38,44 @@
   }
 }
 
+/// Android specific settings for [GoogleMap].
+class AndroidGoogleMapsFlutter {
+  /// Whether to render [GoogleMap] with a [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.
+  ///
+  /// Defaults to false.
+  static bool get useAndroidViewSurface {
+    final GoogleMapsFlutterPlatform platform =
+        GoogleMapsFlutterPlatform.instance;
+    if (platform is MethodChannelGoogleMapsFlutter) {
+      return platform.useAndroidViewSurface;
+    }
+    return false;
+  }
+
+  /// Set whether to render [GoogleMap] with a [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.
+  ///
+  /// Defaults to false.
+  static set useAndroidViewSurface(bool useAndroidViewSurface) {
+    final GoogleMapsFlutterPlatform platform =
+        GoogleMapsFlutterPlatform.instance;
+    if (platform is MethodChannelGoogleMapsFlutter) {
+      platform.useAndroidViewSurface = useAndroidViewSurface;
+    }
+  }
+}
+
 /// A widget which displays a map with data obtained from the Google Maps service.
 class GoogleMap extends StatefulWidget {
   /// Creates a widget displaying data from Google Maps services.
@@ -61,6 +99,7 @@
     this.tiltGesturesEnabled = true,
     this.myLocationEnabled = false,
     this.myLocationButtonEnabled = true,
+    this.layoutDirection,
 
     /// If no padding is specified default padding will be 0.
     this.padding = const EdgeInsets.all(0),
@@ -100,6 +139,12 @@
   /// Type of map tiles to be rendered.
   final MapType mapType;
 
+  /// The layout direction to use for the embedded view.
+  ///
+  /// If this is null, the ambient [Directionality] is used instead. If there is
+  /// no ambient [Directionality], [TextDirection.ltr] is used.
+  final TextDirection? layoutDirection;
+
   /// Preferred bounds for the camera zoom level.
   ///
   /// Actual bounds depend on map data and device.
@@ -250,9 +295,12 @@
 
   @override
   Widget build(BuildContext context) {
-    return GoogleMapsFlutterPlatform.instance.buildView(
+    return GoogleMapsFlutterPlatform.instance.buildViewWithTextDirection(
       _mapId,
       onPlatformViewCreated,
+      textDirection: widget.layoutDirection ??
+          Directionality.maybeOf(context) ??
+          TextDirection.ltr,
       initialCameraPosition: widget.initialCameraPosition,
       markers: widget.markers,
       polygons: widget.polygons,
diff --git a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml
index 61ac88a..dae5a28 100644
--- a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml
+++ b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml
@@ -2,7 +2,7 @@
 description: A Flutter plugin for integrating Google Maps in iOS and Android applications.
 repository: https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter/google_maps_flutter
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22
-version: 2.0.11
+version: 2.1.0
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
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 d1ec87a..003ae06 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
@@ -6,6 +6,7 @@
 import 'package:flutter/widgets.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:google_maps_flutter/google_maps_flutter.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
 
 import 'fake_maps_controllers.dart';
 
@@ -604,17 +605,21 @@
     },
   );
 
-  // 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);
-  // });
+  testWidgets('Use PlatformViewLink on Android', (WidgetTester tester) async {
+    final MethodChannelGoogleMapsFlutter platform =
+        GoogleMapsFlutterPlatform.instance as MethodChannelGoogleMapsFlutter;
+    platform.useAndroidViewSurface = true;
+
+    await tester.pumpWidget(
+      const Directionality(
+        textDirection: TextDirection.ltr,
+        child: GoogleMap(
+          initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)),
+        ),
+      ),
+    );
+
+    expect(find.byType(PlatformViewLink), findsOneWidget);
+    platform.useAndroidViewSurface = false;
+  });
 }