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