Add a virtual-display (VD) platform view test, and refactor tests a bit. (#161349)

Towards https://github.com/flutter/flutter/issues/161261.

Still need to add a HC (Hybrid Composition) variant, but figured I'd do
this incrementally to make it easier to review.
diff --git a/dev/integration_tests/android_engine_test/android/app/src/main/kotlin/com/example/native_driver_test/MainActivity.kt b/dev/integration_tests/android_engine_test/android/app/src/main/kotlin/com/example/native_driver_test/MainActivity.kt
index 6b74332..66cc6cb 100644
--- a/dev/integration_tests/android_engine_test/android/app/src/main/kotlin/com/example/native_driver_test/MainActivity.kt
+++ b/dev/integration_tests/android_engine_test/android/app/src/main/kotlin/com/example/native_driver_test/MainActivity.kt
@@ -12,6 +12,7 @@
 import androidx.core.view.WindowInsetsControllerCompat
 import com.example.android_engine_test.extensions.NativeDriverSupportPlugin
 import com.example.android_engine_test.fixtures.BlueOrangeGradientPlatformViewFactory
+import com.example.android_engine_test.fixtures.BlueOrangeGradientSurfaceViewPlatformViewFactory
 import com.example.android_engine_test.fixtures.ChangingColorButtonPlatformViewFactory
 import com.example.android_engine_test.fixtures.OtherFaceTexturePlugin
 import com.example.android_engine_test.fixtures.SmileyFaceTexturePlugin
@@ -35,6 +36,7 @@
             .registry
             .apply {
                 registerViewFactory("blue_orange_gradient_platform_view", BlueOrangeGradientPlatformViewFactory())
+                registerViewFactory("blue_orange_gradient_surface_view_platform_view", BlueOrangeGradientSurfaceViewPlatformViewFactory())
                 registerViewFactory("changing_color_button_platform_view", ChangingColorButtonPlatformViewFactory())
             }
     }
diff --git a/dev/integration_tests/android_engine_test/android/app/src/main/kotlin/com/example/native_driver_test/extensions/NativeDriverSupportPlugin.kt b/dev/integration_tests/android_engine_test/android/app/src/main/kotlin/com/example/native_driver_test/extensions/NativeDriverSupportPlugin.kt
index 13d77b6..f8d64a4 100644
--- a/dev/integration_tests/android_engine_test/android/app/src/main/kotlin/com/example/native_driver_test/extensions/NativeDriverSupportPlugin.kt
+++ b/dev/integration_tests/android_engine_test/android/app/src/main/kotlin/com/example/native_driver_test/extensions/NativeDriverSupportPlugin.kt
@@ -7,6 +7,7 @@
 package com.example.android_engine_test.extensions
 
 import android.app.Activity
+import android.os.Build
 import android.os.SystemClock
 import android.view.MotionEvent
 import io.flutter.Log
@@ -44,6 +45,10 @@
             return
         }
         when (call.method) {
+            "sdk_version" -> {
+                val versionMap = mapOf("version" to Build.VERSION.SDK_INT)
+                result.success(versionMap)
+            }
             "ping" -> {
                 result.success(null)
             }
diff --git a/dev/integration_tests/android_engine_test/android/app/src/main/kotlin/com/example/native_driver_test/fixtures/BlueOrangeGradientSurfaceViewPlatformViewFactory.kt b/dev/integration_tests/android_engine_test/android/app/src/main/kotlin/com/example/native_driver_test/fixtures/BlueOrangeGradientSurfaceViewPlatformViewFactory.kt
new file mode 100644
index 0000000..9736b39
--- /dev/null
+++ b/dev/integration_tests/android_engine_test/android/app/src/main/kotlin/com/example/native_driver_test/fixtures/BlueOrangeGradientSurfaceViewPlatformViewFactory.kt
@@ -0,0 +1,98 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+@file:Suppress("PackageName")
+
+package com.example.android_engine_test.fixtures
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.LinearGradient
+import android.graphics.Paint
+import android.graphics.Shader
+import android.view.SurfaceHolder
+import android.view.SurfaceView
+import android.view.View
+import android.view.ViewGroup
+import io.flutter.plugin.platform.PlatformView
+import io.flutter.plugin.platform.PlatformViewFactory
+
+class BlueOrangeGradientSurfaceViewPlatformViewFactory : PlatformViewFactory(null) {
+    override fun create(
+        context: Context,
+        viewId: Int,
+        args: Any?
+    ): PlatformView = GradientSurfaceViewPlatformView(context)
+}
+
+private class GradientSurfaceViewPlatformView(
+    context: Context
+) : SurfaceView(context),
+    PlatformView,
+    SurfaceHolder.Callback {
+    val paint = Paint()
+
+    init {
+        layoutParams =
+            ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT
+            )
+        holder.addCallback(this)
+    }
+
+    override fun getView(): View = this
+
+    override fun dispose() {}
+
+    override fun surfaceCreated(holder: SurfaceHolder) {
+        val canvas = holder.lockCanvas()
+        if (canvas != null) {
+            drawGradient(canvas)
+            holder.unlockCanvasAndPost(canvas)
+        }
+    }
+
+    override fun surfaceChanged(
+        holder: SurfaceHolder,
+        format: Int,
+        width: Int,
+        height: Int
+    ) {
+        val canvas = holder.lockCanvas()
+        if (canvas != null) {
+            drawGradient(canvas)
+            holder.unlockCanvasAndPost(canvas)
+        }
+    }
+
+    override fun surfaceDestroyed(holder: SurfaceHolder) {}
+
+    private fun drawGradient(canvas: Canvas) {
+        canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
+    }
+
+    override fun onSizeChanged(
+        w: Int,
+        h: Int,
+        oldw: Int,
+        oldh: Int
+    ) {
+        paint.shader =
+            LinearGradient(
+                0f,
+                0f,
+                w.toFloat(),
+                h.toFloat(),
+                intArrayOf(
+                    Color.rgb(0x41, 0x69, 0xE1),
+                    Color.rgb(0xFF, 0xA5, 0x00)
+                ),
+                null,
+                Shader.TileMode.CLAMP
+            )
+        super.onSizeChanged(w, h, oldw, oldh)
+    }
+}
diff --git a/dev/integration_tests/android_engine_test/lib/external_texture_smiley_face_main.dart b/dev/integration_tests/android_engine_test/lib/external_texture_smiley_face_main.dart
index 032aed5..b1b5bc9 100644
--- a/dev/integration_tests/android_engine_test/lib/external_texture_smiley_face_main.dart
+++ b/dev/integration_tests/android_engine_test/lib/external_texture_smiley_face_main.dart
@@ -21,7 +21,7 @@
 }
 
 void main() async {
-  ensureAndroidOrIosDevice();
+  ensureAndroidDevice();
   enableFlutterDriverExtension(commands: <CommandExtension>[nativeDriverCommands]);
 
   // Run on full screen.
diff --git a/dev/integration_tests/android_engine_test/lib/flutter_rendered_blue_rectangle_main.dart b/dev/integration_tests/android_engine_test/lib/flutter_rendered_blue_rectangle_main.dart
index a6b452b..ded54bc 100644
--- a/dev/integration_tests/android_engine_test/lib/flutter_rendered_blue_rectangle_main.dart
+++ b/dev/integration_tests/android_engine_test/lib/flutter_rendered_blue_rectangle_main.dart
@@ -10,7 +10,7 @@
 import 'src/allow_list_devices.dart';
 
 void main() {
-  ensureAndroidOrIosDevice();
+  ensureAndroidDevice();
   enableFlutterDriverExtension(commands: <CommandExtension>[nativeDriverCommands]);
 
   // Run on full screen.
diff --git a/dev/integration_tests/android_engine_test/lib/platform_view_blue_orange_gradient_main.dart b/dev/integration_tests/android_engine_test/lib/platform_view/texture_layer_hybrid_composition_platform_view_main.dart
similarity index 72%
rename from dev/integration_tests/android_engine_test/lib/platform_view_blue_orange_gradient_main.dart
rename to dev/integration_tests/android_engine_test/lib/platform_view/texture_layer_hybrid_composition_platform_view_main.dart
index 788abc8..10c1dec 100644
--- a/dev/integration_tests/android_engine_test/lib/platform_view_blue_orange_gradient_main.dart
+++ b/dev/integration_tests/android_engine_test/lib/platform_view/texture_layer_hybrid_composition_platform_view_main.dart
@@ -7,10 +7,10 @@
 import 'package:flutter/services.dart';
 import 'package:flutter_driver/driver_extension.dart';
 
-import 'src/allow_list_devices.dart';
+import '../src/allow_list_devices.dart';
 
 void main() async {
-  ensureAndroidOrIosDevice();
+  ensureAndroidDevice();
   enableFlutterDriverExtension(commands: <CommandExtension>[nativeDriverCommands]);
 
   // Run on full screen.
@@ -25,6 +25,11 @@
   Widget build(BuildContext context) {
     return const MaterialApp(
       debugShowCheckedModeBanner: false,
+      // It is assumed:
+      // - The Android SDK version is >= 23 (the test driver checks)
+      // - This view does NOT use a SurfaceView
+      //
+      // See https://github.com/flutter/flutter/blob/main/docs/platforms/android/Android-Platform-Views.md.
       home: AndroidView(viewType: 'blue_orange_gradient_platform_view'),
     );
   }
diff --git a/dev/integration_tests/android_engine_test/lib/platform_view_blue_orange_gradient_main.dart b/dev/integration_tests/android_engine_test/lib/platform_view/virtual_display_platform_view_main.dart
similarity index 66%
copy from dev/integration_tests/android_engine_test/lib/platform_view_blue_orange_gradient_main.dart
copy to dev/integration_tests/android_engine_test/lib/platform_view/virtual_display_platform_view_main.dart
index 788abc8..4c51b60 100644
--- a/dev/integration_tests/android_engine_test/lib/platform_view_blue_orange_gradient_main.dart
+++ b/dev/integration_tests/android_engine_test/lib/platform_view/virtual_display_platform_view_main.dart
@@ -7,10 +7,10 @@
 import 'package:flutter/services.dart';
 import 'package:flutter_driver/driver_extension.dart';
 
-import 'src/allow_list_devices.dart';
+import '../src/allow_list_devices.dart';
 
 void main() async {
-  ensureAndroidOrIosDevice();
+  ensureAndroidDevice();
   enableFlutterDriverExtension(commands: <CommandExtension>[nativeDriverCommands]);
 
   // Run on full screen.
@@ -25,7 +25,12 @@
   Widget build(BuildContext context) {
     return const MaterialApp(
       debugShowCheckedModeBanner: false,
-      home: AndroidView(viewType: 'blue_orange_gradient_platform_view'),
+      // It is assumed:
+      // - The Android SDK version is >= 23 (the test driver checks)
+      // - This view DOES use a SurfaceView
+      //
+      // See https://github.com/flutter/flutter/blob/main/docs/platforms/android/Android-Platform-Views.md.
+      home: AndroidView(viewType: 'blue_orange_gradient_surface_view_platform_view'),
     );
   }
 }
diff --git a/dev/integration_tests/android_engine_test/lib/platform_view_tap_color_change_main.dart b/dev/integration_tests/android_engine_test/lib/platform_view_tap_color_change_main.dart
index 9bf0fc2..b2612cb 100644
--- a/dev/integration_tests/android_engine_test/lib/platform_view_tap_color_change_main.dart
+++ b/dev/integration_tests/android_engine_test/lib/platform_view_tap_color_change_main.dart
@@ -10,7 +10,7 @@
 import 'src/allow_list_devices.dart';
 
 void main() async {
-  ensureAndroidOrIosDevice();
+  ensureAndroidDevice();
   enableFlutterDriverExtension(commands: <CommandExtension>[nativeDriverCommands]);
 
   // Run on full screen.
diff --git a/dev/integration_tests/android_engine_test/lib/src/allow_list_devices.dart b/dev/integration_tests/android_engine_test/lib/src/allow_list_devices.dart
index b1f1135..f9a9a6f 100644
--- a/dev/integration_tests/android_engine_test/lib/src/allow_list_devices.dart
+++ b/dev/integration_tests/android_engine_test/lib/src/allow_list_devices.dart
@@ -6,11 +6,11 @@
 
 import 'package:flutter/foundation.dart';
 
-/// Throws an [UnsupportedError] if the current platform is not Android or iOS.
-void ensureAndroidOrIosDevice() {
-  if (kIsWeb || (!io.Platform.isAndroid && !io.Platform.isIOS)) {
+/// Throws an [UnsupportedError] if the current platform is not Android.
+void ensureAndroidDevice() {
+  if (kIsWeb || !io.Platform.isAndroid) {
     throw UnsupportedError(
-      'This app should only run on Android or iOS devices. It uses native '
+      'This app should only run on Android devices. It uses native Android '
       'plugins that are not developed for other platforms, and would need to '
       'be adapted to run on other platforms. See the README.md for details.',
     );
diff --git a/dev/integration_tests/android_engine_test/test_driver/platform_view_blue_orange_gradient_main_test.dart b/dev/integration_tests/android_engine_test/test_driver/platform_view/texture_layer_hybrid_composition_platform_view_main_test.dart
similarity index 70%
rename from dev/integration_tests/android_engine_test/test_driver/platform_view_blue_orange_gradient_main_test.dart
rename to dev/integration_tests/android_engine_test/test_driver/platform_view/texture_layer_hybrid_composition_platform_view_main_test.dart
index 69d7ad2..feb47ac 100644
--- a/dev/integration_tests/android_engine_test/test_driver/platform_view_blue_orange_gradient_main_test.dart
+++ b/dev/integration_tests/android_engine_test/test_driver/platform_view/texture_layer_hybrid_composition_platform_view_main_test.dart
@@ -7,12 +7,14 @@
 import 'package:flutter_driver/flutter_driver.dart';
 import 'package:test/test.dart';
 
-import '_luci_skia_gold_prelude.dart';
+import '../_luci_skia_gold_prelude.dart';
 
 void main() async {
   // To test the golden file generation locally, comment out the following line.
   // autoUpdateGoldenFiles = true;
 
+  const String goldenPrefix = 'texture_layer_hybrid_composition_platform_view';
+
   late final FlutterDriver flutterDriver;
   late final NativeDriver nativeDriver;
 
@@ -24,6 +26,12 @@
     nativeDriver = await AndroidNativeDriver.connect(flutterDriver);
     await nativeDriver.configureForScreenshotTesting();
     await flutterDriver.waitUntilFirstFrameRasterized();
+
+    // Double check that we are really probably testing using TLHC.
+    // See https://github.com/flutter/flutter/blob/main/docs/platforms/android/Android-Platform-Views.md.
+    if (await nativeDriver.sdkVersion case final int version when version < 23) {
+      fail('Requires SDK >= 23, got $version');
+    }
   });
 
   tearDownAll(() async {
@@ -35,7 +43,7 @@
     await flutterDriver.waitFor(find.byType('AndroidView'));
     await expectLater(
       nativeDriver.screenshot(),
-      matchesGoldenFile('platform_view_blue_orange_gradient_portrait.android.png'),
+      matchesGoldenFile('$goldenPrefix.blue_orange_gradient_portrait.android.png'),
     );
   }, timeout: Timeout.none);
 
@@ -44,13 +52,13 @@
     await nativeDriver.rotateToLandscape();
     await expectLater(
       nativeDriver.screenshot(),
-      matchesGoldenFile('platform_view_blue_orange_gradient_landscape.android.png'),
+      matchesGoldenFile('$goldenPrefix.blue_orange_gradient_landscape_rotated.android.png'),
     );
 
     await nativeDriver.rotateResetDefault();
     await expectLater(
       nativeDriver.screenshot(),
-      matchesGoldenFile('platform_view_blue_orange_gradient_portrait_post_rotation.android.png'),
+      matchesGoldenFile('$goldenPrefix.blue_orange_gradient_portait_rotated_back.android.png'),
     );
   }, timeout: Timeout.none);
 }
diff --git a/dev/integration_tests/android_engine_test/test_driver/platform_view_blue_orange_gradient_main_test.dart b/dev/integration_tests/android_engine_test/test_driver/platform_view/virtual_display_platform_view_main_test.dart
similarity index 70%
copy from dev/integration_tests/android_engine_test/test_driver/platform_view_blue_orange_gradient_main_test.dart
copy to dev/integration_tests/android_engine_test/test_driver/platform_view/virtual_display_platform_view_main_test.dart
index 69d7ad2..f6b6a26 100644
--- a/dev/integration_tests/android_engine_test/test_driver/platform_view_blue_orange_gradient_main_test.dart
+++ b/dev/integration_tests/android_engine_test/test_driver/platform_view/virtual_display_platform_view_main_test.dart
@@ -7,12 +7,14 @@
 import 'package:flutter_driver/flutter_driver.dart';
 import 'package:test/test.dart';
 
-import '_luci_skia_gold_prelude.dart';
+import '../_luci_skia_gold_prelude.dart';
 
 void main() async {
   // To test the golden file generation locally, comment out the following line.
   // autoUpdateGoldenFiles = true;
 
+  const String goldenPrefix = 'virtual_display_platform_view';
+
   late final FlutterDriver flutterDriver;
   late final NativeDriver nativeDriver;
 
@@ -24,6 +26,12 @@
     nativeDriver = await AndroidNativeDriver.connect(flutterDriver);
     await nativeDriver.configureForScreenshotTesting();
     await flutterDriver.waitUntilFirstFrameRasterized();
+
+    // Double check that we are really probably testing using Virtual Display.
+    // See https://github.com/flutter/flutter/blob/main/docs/platforms/android/Android-Platform-Views.md.
+    if (await nativeDriver.sdkVersion case final int version when version < 23) {
+      fail('Requires SDK >= 23, got $version');
+    }
   });
 
   tearDownAll(() async {
@@ -35,7 +43,7 @@
     await flutterDriver.waitFor(find.byType('AndroidView'));
     await expectLater(
       nativeDriver.screenshot(),
-      matchesGoldenFile('platform_view_blue_orange_gradient_portrait.android.png'),
+      matchesGoldenFile('$goldenPrefix.blue_orange_gradient_portrait.android.png'),
     );
   }, timeout: Timeout.none);
 
@@ -44,13 +52,13 @@
     await nativeDriver.rotateToLandscape();
     await expectLater(
       nativeDriver.screenshot(),
-      matchesGoldenFile('platform_view_blue_orange_gradient_landscape.android.png'),
+      matchesGoldenFile('$goldenPrefix.blue_orange_gradient_landscape_rotated.android.png'),
     );
 
     await nativeDriver.rotateResetDefault();
     await expectLater(
       nativeDriver.screenshot(),
-      matchesGoldenFile('platform_view_blue_orange_gradient_portrait_post_rotation.android.png'),
+      matchesGoldenFile('$goldenPrefix.blue_orange_gradient_portait_rotated_back.android.png'),
     );
   }, timeout: Timeout.none);
 }
diff --git a/dev/tools/android_driver_extensions/lib/extension.dart b/dev/tools/android_driver_extensions/lib/extension.dart
index 32740c6..92f9139 100644
--- a/dev/tools/android_driver_extensions/lib/extension.dart
+++ b/dev/tools/android_driver_extensions/lib/extension.dart
@@ -49,10 +49,14 @@
     if (result == null) {
       return const _MethodChannelResult(<String, Object?>{});
     }
-    if (result is! Map<String, Object?>) {
-      throw ArgumentError.value(result, 'result', 'Expected a Map<String, Object?>');
+    if (result is! Map<Object?, Object?>) {
+      throw ArgumentError.value(
+        result,
+        'result',
+        'Expected a Map<String, Object?>, got ${result.runtimeType}',
+      );
     }
-    return _MethodChannelResult(result);
+    return _MethodChannelResult(result.cast());
   }
 
   // While these could have been implemented in native code, they are already
diff --git a/dev/tools/android_driver_extensions/lib/src/backend/android/driver.dart b/dev/tools/android_driver_extensions/lib/src/backend/android/driver.dart
index e8f1deb..7b8fec8 100644
--- a/dev/tools/android_driver_extensions/lib/src/backend/android/driver.dart
+++ b/dev/tools/android_driver_extensions/lib/src/backend/android/driver.dart
@@ -71,6 +71,12 @@
     await _driver.sendCommand(NativeCommand.tap(finder));
   }
 
+  @override
+  Future<int> get sdkVersion async {
+    final Map<String, Object?> result = await _driver.sendCommand(NativeCommand.getSdkVersion);
+    return result['version']! as int;
+  }
+
   /// Waits for 2 seconds before completing.
   ///
   /// There is no perfect way, outside of polling, to know when the device is
diff --git a/dev/tools/android_driver_extensions/lib/src/common.dart b/dev/tools/android_driver_extensions/lib/src/common.dart
index 34c90b1..8d8e0f2 100644
--- a/dev/tools/android_driver_extensions/lib/src/common.dart
+++ b/dev/tools/android_driver_extensions/lib/src/common.dart
@@ -27,6 +27,9 @@
   /// Pings the device to ensure it is responsive.
   static const NativeCommand ping = NativeCommand('ping');
 
+  /// Gets the SDK version code.
+  static const NativeCommand getSdkVersion = NativeCommand('sdk_version');
+
   /// The method to call on the plugin.
   final String method;
 
diff --git a/dev/tools/android_driver_extensions/lib/src/driver.dart b/dev/tools/android_driver_extensions/lib/src/driver.dart
index 1bf3ff1..f6af26e 100644
--- a/dev/tools/android_driver_extensions/lib/src/driver.dart
+++ b/dev/tools/android_driver_extensions/lib/src/driver.dart
@@ -45,6 +45,9 @@
   /// ```
   Future<Duration> ping();
 
+  /// Returns the SDK version.
+  Future<int> get sdkVersion;
+
   /// Take a screenshot using a platform-specific mechanism.
   ///
   /// The image is returned as an opaque handle that can be used to retrieve