[webview_flutter] Default Android to hybrid composition (#4576)

Switches the default implemention on Android from virtual display to
hybrid composition. This is a breaking change.

Part of https://github.com/flutter/flutter/issues/93335
diff --git a/packages/webview_flutter/webview_flutter/CHANGELOG.md b/packages/webview_flutter/webview_flutter/CHANGELOG.md
index 4906003..aa117c9 100644
--- a/packages/webview_flutter/webview_flutter/CHANGELOG.md
+++ b/packages/webview_flutter/webview_flutter/CHANGELOG.md
@@ -1,3 +1,9 @@
+## 3.0.0
+
+* **BREAKING CHANGE**: On Android, hybrid composition (SurfaceAndroidWebView)
+  is now the default. The previous default, virtual display, can be specified
+  with `WebView.platform = AndroidWebView()`
+
 ## 2.8.0
 
 * Adds support for the `loadFlutterAsset` method.
diff --git a/packages/webview_flutter/webview_flutter/README.md b/packages/webview_flutter/webview_flutter/README.md
index c3982ea..8387eb6 100644
--- a/packages/webview_flutter/webview_flutter/README.md
+++ b/packages/webview_flutter/webview_flutter/README.md
@@ -15,68 +15,63 @@
 widget's Dartdoc for more details on how to use the widget.
 
 ## Android Platform Views
-The WebView is relying on
+This plugin uses
 [Platform Views](https://flutter.dev/docs/development/platform-integration/platform-views) to embed
-the Android’s webview within the Flutter app. It supports two modes: *Virtual displays* (the current default) and *Hybrid composition*.
+the Android’s webview within the Flutter app. It supports two modes:
+*hybrid composition* (the current default) and *virtual display*.
 
 Here are some points to consider when choosing between the two:
 
-* *Hybrid composition* mode has a built-in keyboard support while *Virtual displays* mode has multiple
-[keyboard issues](https://github.com/flutter/flutter/issues?q=is%3Aopen+label%3Avd-only+label%3A%22p%3A+webview-keyboard%22)
-* *Hybrid composition* mode requires Android SDK 19+ while *Virtual displays* mode requires Android SDK 20+
-* *Hybrid composition* mode has [performance limitations](https://flutter.dev/docs/development/platform-integration/platform-views#performance) when working on Android versions prior to Android 10 while *Virtual displays* is performant on all supported Android versions 
-
-|                             | Hybrid composition  | Virtual displays |
-| --------------------------- | ------------------- | ---------------- |
-| **Full keyboard supoport**  | yes                 | no               |
-| **Android SDK support**     | 19+                 | 20+              |
-| **Full performance**        | Android 10+         | always           |
-| **The default**             | no                  | yes              |
-
-### Using Virtual displays
-
-The mode is currently enabled by default. You should however make sure to set the correct `minSdkVersion` in `android/app/build.gradle` (if it was previously lower than 20):
-
-```groovy
-android {
-    defaultConfig {
-        minSdkVersion 20
-    }
-}
-```
+* *Hybrid composition* has built-in keyboard support while *virtual display* has multiple
+[keyboard issues](https://github.com/flutter/flutter/issues?q=is%3Aopen+label%3Avd-only+label%3A%22p%3A+webview-keyboard%22).
+* *Hybrid composition* requires Android SDK 19+ while *virtual display* requires Android SDK 20+.
+* *Hybrid composition* and *virtual display* have different
+  [performance tradeoffs](https://flutter.dev/docs/development/platform-integration/platform-views#performance).
 
 
 ### Using Hybrid Composition
 
-1. Set the correct `minSdkVersion` in `android/app/build.gradle` (if it was previously lower than 19):
+The mode is currently enabled by default. You should however make sure to set the correct `minSdkVersion` in `android/app/build.gradle` if it was previously lower than 19:
+
+```groovy
+android {
+    defaultConfig {
+        minSdkVersion 19
+    }
+}
+```
+
+### Using Virtual displays
+
+1. Set the correct `minSdkVersion` in `android/app/build.gradle` (if it was previously lower than 20):
 
     ```groovy
     android {
         defaultConfig {
-            minSdkVersion 19
+            minSdkVersion 20
         }
     }
     ```
 
-2. Set `WebView.platform = SurfaceAndroidWebView();` in `initState()`.
+2. Set `WebView.platform = AndroidWebView();` in `initState()`.
     For example:
-    
+
     ```dart
     import 'dart:io';
-    
+
     import 'package:webview_flutter/webview_flutter.dart';
 
     class WebViewExample extends StatefulWidget {
       @override
       WebViewExampleState createState() => WebViewExampleState();
     }
-    
+
     class WebViewExampleState extends State<WebViewExample> {
       @override
       void initState() {
         super.initState();
-            // Enable hybrid composition.
-    if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
+        // Enable virtual display.
+        if (Platform.isAndroid) WebView.platform = AndroidWebView();
       }
 
       @override
diff --git a/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart
index cc74afd..4c2d1fb 100644
--- a/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart
+++ b/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart
@@ -860,192 +860,41 @@
     }, skip: Platform.isAndroid && _skipDueToIssue86757);
   });
 
-  group('SurfaceAndroidWebView', () {
+  // Minimial end-to-end testing of the legacy Android implementation.
+  group('AndroidWebView (virtual display)', () {
     setUpAll(() {
-      WebView.platform = SurfaceAndroidWebView();
+      WebView.platform = AndroidWebView();
     });
 
     tearDownAll(() {
       WebView.platform = null;
     });
 
-    // TODO(bparrishMines): skipped due to https://github.com/flutter/flutter/issues/86757.
-    testWidgets('setAndGetScrollPosition', (WidgetTester tester) async {
-      const String scrollTestPage = '''
-        <!DOCTYPE html>
-        <html>
-          <head>
-            <style>
-              body {
-                height: 100%;
-                width: 100%;
-              }
-              #container{
-                width:5000px;
-                height:5000px;
-            }
-            </style>
-          </head>
-          <body>
-            <div id="container"/>
-          </body>
-        </html>
-      ''';
-
-      final String scrollTestPageBase64 =
-          base64Encode(const Utf8Encoder().convert(scrollTestPage));
-
-      final Completer<void> pageLoaded = Completer<void>();
+    testWidgets('initialUrl', (WidgetTester tester) async {
       final Completer<WebViewController> controllerCompleter =
           Completer<WebViewController>();
-
+      final Completer<void> loadCompleter = Completer<void>();
       await tester.pumpWidget(
         Directionality(
           textDirection: TextDirection.ltr,
           child: WebView(
-            initialUrl:
-                'data:text/html;charset=utf-8;base64,$scrollTestPageBase64',
+            key: GlobalKey(),
+            initialUrl: primaryUrl,
             onWebViewCreated: (WebViewController controller) {
               controllerCompleter.complete(controller);
             },
             onPageFinished: (String url) {
-              pageLoaded.complete(null);
+              loadCompleter.complete();
             },
           ),
         ),
       );
-
       final WebViewController controller = await controllerCompleter.future;
-      await pageLoaded.future;
-
-      await tester.pumpAndSettle(const Duration(seconds: 3));
-
-      // Check scrollTo()
-      const int X_SCROLL = 123;
-      const int Y_SCROLL = 321;
-
-      await controller.scrollTo(X_SCROLL, Y_SCROLL);
-      int scrollPosX = await controller.getScrollX();
-      int scrollPosY = await controller.getScrollY();
-      expect(X_SCROLL, scrollPosX);
-      expect(Y_SCROLL, scrollPosY);
-
-      // Check scrollBy() (on top of scrollTo())
-      await controller.scrollBy(X_SCROLL, Y_SCROLL);
-      scrollPosX = await controller.getScrollX();
-      scrollPosY = await controller.getScrollY();
-      expect(X_SCROLL * 2, scrollPosX);
-      expect(Y_SCROLL * 2, scrollPosY);
-    }, skip: !Platform.isAndroid || _skipDueToIssue86757);
-
-    // TODO(bparrishMines): skipped due to https://github.com/flutter/flutter/issues/86757.
-    testWidgets('inputs are scrolled into view when focused',
-        (WidgetTester tester) async {
-      const String scrollTestPage = '''
-        <!DOCTYPE html>
-        <html>
-          <head>
-            <style>
-              input {
-                margin: 10000px 0;
-              }
-              #viewport {
-                position: fixed;
-                top:0;
-                bottom:0;
-                left:0;
-                right:0;
-              }
-            </style>
-          </head>
-          <body>
-            <div id="viewport"></div>
-            <input type="text" id="inputEl">
-          </body>
-        </html>
-      ''';
-
-      final String scrollTestPageBase64 =
-          base64Encode(const Utf8Encoder().convert(scrollTestPage));
-
-      final Completer<void> pageLoaded = Completer<void>();
-      final Completer<WebViewController> controllerCompleter =
-          Completer<WebViewController>();
-
-      await tester.runAsync(() async {
-        await tester.pumpWidget(
-          Directionality(
-            textDirection: TextDirection.ltr,
-            child: SizedBox(
-              width: 200,
-              height: 200,
-              child: WebView(
-                initialUrl:
-                    'data:text/html;charset=utf-8;base64,$scrollTestPageBase64',
-                onWebViewCreated: (WebViewController controller) {
-                  controllerCompleter.complete(controller);
-                },
-                onPageFinished: (String url) {
-                  pageLoaded.complete(null);
-                },
-                javascriptMode: JavascriptMode.unrestricted,
-              ),
-            ),
-          ),
-        );
-        await Future<dynamic>.delayed(const Duration(milliseconds: 20));
-        await tester.pump();
-      });
-
-      final WebViewController controller = await controllerCompleter.future;
-      await pageLoaded.future;
-      final String viewportRectJSON = await _runJavascriptReturningResult(
-          controller, 'JSON.stringify(viewport.getBoundingClientRect())');
-      final Map<String, dynamic> viewportRectRelativeToViewport =
-          jsonDecode(viewportRectJSON) as Map<String, dynamic>;
-
-      // Check that the input is originally outside of the viewport.
-
-      final String initialInputClientRectJSON =
-          await _runJavascriptReturningResult(
-              controller, 'JSON.stringify(inputEl.getBoundingClientRect())');
-      final Map<String, dynamic> initialInputClientRectRelativeToViewport =
-          jsonDecode(initialInputClientRectJSON) as Map<String, dynamic>;
-
-      expect(
-          initialInputClientRectRelativeToViewport['bottom'] <=
-              viewportRectRelativeToViewport['bottom'],
-          isFalse);
-
-      await controller.runJavascript('inputEl.focus()');
-
-      // Check that focusing the input brought it into view.
-
-      final String lastInputClientRectJSON =
-          await _runJavascriptReturningResult(
-              controller, 'JSON.stringify(inputEl.getBoundingClientRect())');
-      final Map<String, dynamic> lastInputClientRectRelativeToViewport =
-          jsonDecode(lastInputClientRectJSON) as Map<String, dynamic>;
-
-      expect(
-          lastInputClientRectRelativeToViewport['top'] >=
-              viewportRectRelativeToViewport['top'],
-          isTrue);
-      expect(
-          lastInputClientRectRelativeToViewport['bottom'] <=
-              viewportRectRelativeToViewport['bottom'],
-          isTrue);
-
-      expect(
-          lastInputClientRectRelativeToViewport['left'] >=
-              viewportRectRelativeToViewport['left'],
-          isTrue);
-      expect(
-          lastInputClientRectRelativeToViewport['right'] <=
-              viewportRectRelativeToViewport['right'],
-          isTrue);
-    }, skip: !Platform.isAndroid || _skipDueToIssue86757);
-  });
+      await loadCompleter.future;
+      final String? currentUrl = await controller.currentUrl();
+      expect(currentUrl, primaryUrl);
+    });
+  }, skip: !Platform.isAndroid || _skipDueToIssue86757);
 
   group('NavigationDelegate', () {
     const String blankPage = '<!DOCTYPE html><head></head><body></body></html>';
diff --git a/packages/webview_flutter/webview_flutter/lib/src/webview.dart b/packages/webview_flutter/webview_flutter/lib/src/webview.dart
index 7b907a5..a177027 100644
--- a/packages/webview_flutter/webview_flutter/lib/src/webview.dart
+++ b/packages/webview_flutter/webview_flutter/lib/src/webview.dart
@@ -9,8 +9,8 @@
 import 'package:flutter/gestures.dart';
 import 'package:flutter/rendering.dart';
 import 'package:flutter/widgets.dart';
-import 'package:webview_flutter_android/webview_android.dart';
 import 'package:webview_flutter_android/webview_android_cookie_manager.dart';
+import 'package:webview_flutter_android/webview_surface_android.dart';
 import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
 import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';
 
@@ -119,12 +119,12 @@
 
   /// The WebView platform that's used by this WebView.
   ///
-  /// The default value is [AndroidWebView] on Android and [CupertinoWebView] on iOS.
+  /// The default value is [SurfaceAndroidWebView] on Android and [CupertinoWebView] on iOS.
   static WebViewPlatform get platform {
     if (_platform == null) {
       switch (defaultTargetPlatform) {
         case TargetPlatform.android:
-          _platform = AndroidWebView();
+          _platform = SurfaceAndroidWebView();
           break;
         case TargetPlatform.iOS:
           _platform = CupertinoWebView();
diff --git a/packages/webview_flutter/webview_flutter/pubspec.yaml b/packages/webview_flutter/webview_flutter/pubspec.yaml
index 4ac5e1d..4af4462 100644
--- a/packages/webview_flutter/webview_flutter/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter/pubspec.yaml
@@ -2,7 +2,7 @@
 description: A Flutter plugin that provides a WebView widget on Android and iOS.
 repository: https://github.com/flutter/plugins/tree/master/packages/webview_flutter/webview_flutter
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
-version: 2.8.0
+version: 3.0.0
 
 environment:
   sdk: ">=2.14.0 <3.0.0"