Skip to content

Commit f039697

Browse files
author
Jonah Williams
authored
[Android] HC++ wire up dart platform channel code and integration test. (#162751)
Use the PlatformViewController2 to register a platform view, allow the dart side platform view logic to opt into this new platform view. Wires up an integration test with android_engine_test.
1 parent 7569fbf commit f039697

File tree

23 files changed

+477
-18
lines changed

23 files changed

+477
-18
lines changed

dev/bots/suite_runners/run_android_engine_tests.dart

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ Future<void> runAndroidEngineTests({required ImpellerBackend impellerBackend}) a
5757
// TODO(matanlurey): Enable once `flutter drive` retains error logs.
5858
// final RegExp impellerStdoutPattern = RegExp('Using the Imepller rendering backend (.*)');
5959

60-
for (final FileSystemEntity file in mains) {
60+
Future<void> runTest(FileSystemEntity file) async {
6161
final CommandResult result = await runCommand(
6262
'flutter',
6363
<String>[
@@ -77,7 +77,7 @@ Future<void> runAndroidEngineTests({required ImpellerBackend impellerBackend}) a
7777
final String? stdout = result.flattenedStdout;
7878
if (stdout == null) {
7979
foundError(<String>['No stdout produced.']);
80-
continue;
80+
return;
8181
}
8282

8383
// TODO(matanlurey): Enable once `flutter drive` retains error logs.
@@ -86,23 +86,51 @@ Future<void> runAndroidEngineTests({required ImpellerBackend impellerBackend}) a
8686
// final Match? stdoutMatch = impellerStdoutPattern.firstMatch(stdout);
8787
// if (stdoutMatch == null) {
8888
// foundError(<String>['Could not find pattern ${impellerStdoutPattern.pattern}.', stdout]);
89-
// continue;
89+
// return;
9090
// }
9191

9292
// final String reportedBackend = stdoutMatch.group(1)!.toLowerCase();
9393
// if (reportedBackend != impellerBackend.name) {
9494
// foundError(<String>[
9595
// 'Reported Imepller backend was $reportedBackend, expected ${impellerBackend.name}',
9696
// ]);
97-
// continue;
97+
// return;
9898
// }
9999
}
100+
101+
for (final FileSystemEntity file in mains) {
102+
if (file.path.contains('hcpp')) {
103+
continue;
104+
}
105+
await runTest(file);
106+
}
107+
108+
// Test HCPP Platform Views on Vulkan.
109+
if (impellerBackend == ImpellerBackend.vulkan) {
110+
androidManifestXml.writeAsStringSync(
111+
androidManifestXml.readAsStringSync().replaceFirst(
112+
kSurfaceControlMetadataDisabled,
113+
kSurfaceControlMetadataEnabled,
114+
),
115+
);
116+
for (final FileSystemEntity file in mains) {
117+
if (!file.path.contains('hcpp')) {
118+
continue;
119+
}
120+
await runTest(file);
121+
}
122+
}
100123
} finally {
101124
// Restore original contents.
102125
androidManifestXml.writeAsStringSync(androidManifestContents);
103126
}
104127
}
105128

129+
const String kSurfaceControlMetadataDisabled =
130+
'<meta-data android:name="io.flutter.embedding.android.UseSurfaceControl" android:value="false" />';
131+
const String kSurfaceControlMetadataEnabled =
132+
'<meta-data android:name="io.flutter.embedding.android.UseSurfaceControl" android:value="true" />';
133+
106134
String _impellerBackendMetadata({required String value}) {
107135
return '<meta-data android:name="io.flutter.embedding.android.ImpellerBackend" android:value="$value" />';
108136
}

dev/integration_tests/android_engine_test/android/app/src/main/AndroidManifest.xml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,20 @@ found in the LICENSE file. -->
2121
while the Flutter UI initializes. After that, this theme continues
2222
to determine the Window background behind the Flutter UI. -->
2323
<meta-data
24-
android:name="io.flutter.embedding.android.NormalTheme"
25-
android:resource="@style/NormalTheme"
26-
/>
24+
android:name="io.flutter.embedding.android.NormalTheme"
25+
android:resource="@style/NormalTheme"
26+
/>
2727
<intent-filter>
28-
<action android:name="android.intent.action.MAIN"/>
29-
<category android:name="android.intent.category.LAUNCHER"/>
28+
<action android:name="android.intent.action.MAIN" />
29+
<category android:name="android.intent.category.LAUNCHER" />
3030
</intent-filter>
3131
</activity>
3232
<!-- Don't delete the meta-data below.
3333
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
3434
<meta-data android:name="flutterEmbedding" android:value="2" />
3535
<meta-data android:name="io.flutter.embedding.android.EnableImpeller" android:value="true" />
3636
<meta-data android:name="io.flutter.embedding.android.ImpellerBackend" android:value="vulkan" />
37+
<meta-data android:name="io.flutter.embedding.android.EnableSurfaceControl" android:value="false" />
3738
</application>
3839
<!-- Required to query activities that can process text, see:
3940
https://developer.android.com/training/package-visibility and

dev/integration_tests/android_engine_test/android/app/src/main/kotlin/com/example/native_driver_test/MainActivity.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import androidx.core.view.WindowInsetsControllerCompat
1313
import com.example.android_engine_test.extensions.NativeDriverSupportPlugin
1414
import com.example.android_engine_test.fixtures.BlueOrangeGradientPlatformViewFactory
1515
import com.example.android_engine_test.fixtures.BlueOrangeGradientSurfaceViewPlatformViewFactory
16+
import com.example.android_engine_test.fixtures.BoxPlatformViewFactory
1617
import com.example.android_engine_test.fixtures.ChangingColorButtonPlatformViewFactory
1718
import com.example.android_engine_test.fixtures.OtherFaceTexturePlugin
1819
import com.example.android_engine_test.fixtures.SmileyFaceTexturePlugin
@@ -31,13 +32,25 @@ class MainActivity : FlutterActivity() {
3132
add(NativeDriverSupportPlugin())
3233
}
3334

35+
// TODO(jonahwilliams): make platform view controllers share platform view registry.
36+
flutterEngine
37+
.platformViewsController2
38+
.registry
39+
.apply {
40+
registerViewFactory("blue_orange_gradient_platform_view", BlueOrangeGradientPlatformViewFactory())
41+
registerViewFactory("blue_orange_gradient_surface_view_platform_view", BlueOrangeGradientSurfaceViewPlatformViewFactory())
42+
registerViewFactory("changing_color_button_platform_view", ChangingColorButtonPlatformViewFactory())
43+
registerViewFactory("box_platform_view", BoxPlatformViewFactory())
44+
}
45+
3446
flutterEngine
3547
.platformViewsController
3648
.registry
3749
.apply {
3850
registerViewFactory("blue_orange_gradient_platform_view", BlueOrangeGradientPlatformViewFactory())
3951
registerViewFactory("blue_orange_gradient_surface_view_platform_view", BlueOrangeGradientSurfaceViewPlatformViewFactory())
4052
registerViewFactory("changing_color_button_platform_view", ChangingColorButtonPlatformViewFactory())
53+
registerViewFactory("box_platform_view", BoxPlatformViewFactory())
4154
}
4255
}
4356

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
@file:Suppress("PackageName")
6+
7+
package com.example.android_engine_test.fixtures
8+
9+
import android.content.Context
10+
import android.graphics.Canvas
11+
import android.graphics.Color
12+
import android.graphics.Paint
13+
import android.view.View
14+
import android.view.ViewGroup
15+
import io.flutter.plugin.platform.PlatformView
16+
import io.flutter.plugin.platform.PlatformViewFactory
17+
18+
class BoxPlatformViewFactory : PlatformViewFactory(null) {
19+
override fun create(
20+
context: Context,
21+
viewId: Int,
22+
args: Any?
23+
): PlatformView = BoxPlatformView(context)
24+
}
25+
26+
private class BoxPlatformView(
27+
context: Context
28+
) : View(context),
29+
PlatformView {
30+
val paint = Paint()
31+
32+
init {
33+
layoutParams =
34+
ViewGroup.LayoutParams(
35+
ViewGroup.LayoutParams.MATCH_PARENT,
36+
ViewGroup.LayoutParams.MATCH_PARENT
37+
)
38+
}
39+
40+
override fun getView(): View = this
41+
42+
override fun dispose() {}
43+
44+
override fun onDraw(canvas: Canvas) {
45+
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
46+
super.onDraw(canvas)
47+
}
48+
49+
override fun onSizeChanged(
50+
w: Int,
51+
h: Int,
52+
oldw: Int,
53+
oldh: Int
54+
) {
55+
paint.color = Color.rgb(0x41, 0x69, 0xE1)
56+
super.onSizeChanged(w, h, oldw, oldh)
57+
}
58+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:android_driver_extensions/extension.dart';
6+
import 'package:flutter/foundation.dart';
7+
import 'package:flutter/gestures.dart';
8+
import 'package:flutter/material.dart';
9+
import 'package:flutter/rendering.dart';
10+
import 'package:flutter/services.dart';
11+
import 'package:flutter_driver/driver_extension.dart';
12+
13+
import '../src/allow_list_devices.dart';
14+
15+
void main() async {
16+
ensureAndroidDevice();
17+
enableFlutterDriverExtension(commands: <CommandExtension>[nativeDriverCommands]);
18+
19+
// Run on full screen.
20+
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
21+
runApp(const MainApp());
22+
}
23+
24+
final class MainApp extends StatelessWidget {
25+
const MainApp({super.key});
26+
27+
// This should appear as the yellow line over a blue box. The
28+
// red box should not be visible unless the platform view has not loaded yet.
29+
@override
30+
Widget build(BuildContext context) {
31+
return const MaterialApp(
32+
debugShowCheckedModeBanner: false,
33+
home: Stack(
34+
alignment: AlignmentDirectional.center,
35+
children: <Widget>[
36+
SizedBox(width: 190, height: 190, child: ColoredBox(color: Colors.red)),
37+
SizedBox(
38+
width: 200,
39+
height: 200,
40+
child: _HybridCompositionAndroidPlatformView(viewType: 'box_platform_view'),
41+
),
42+
SizedBox(width: 800, height: 25, child: ColoredBox(color: Colors.yellow)),
43+
],
44+
),
45+
);
46+
}
47+
}
48+
49+
final class _HybridCompositionAndroidPlatformView extends StatelessWidget {
50+
const _HybridCompositionAndroidPlatformView({required this.viewType});
51+
52+
final String viewType;
53+
54+
// TODO(jonahwilliams): swap this out with new platform view APIs.
55+
@override
56+
Widget build(BuildContext context) {
57+
return PlatformViewLink(
58+
viewType: viewType,
59+
surfaceFactory: (BuildContext context, PlatformViewController controller) {
60+
return AndroidViewSurface(
61+
controller: controller as AndroidViewController,
62+
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
63+
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
64+
);
65+
},
66+
onCreatePlatformView: (PlatformViewCreationParams params) {
67+
return PlatformViewsService.initHybridAndroidView(
68+
id: params.id,
69+
viewType: viewType,
70+
layoutDirection: TextDirection.ltr,
71+
creationParamsCodec: const StandardMessageCodec(),
72+
)
73+
..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
74+
..create();
75+
},
76+
);
77+
}
78+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:android_driver_extensions/native_driver.dart';
6+
import 'package:android_driver_extensions/skia_gold.dart';
7+
import 'package:flutter_driver/flutter_driver.dart';
8+
import 'package:test/test.dart';
9+
10+
import '../_luci_skia_gold_prelude.dart';
11+
12+
/// For local debugging, a (local) golden-file is required as a baseline:
13+
///
14+
/// ```sh
15+
/// # Checkout HEAD, i.e. *before* changes you want to test.
16+
/// UPDATE_GOLDENS=1 flutter drive lib/platform_view/hcpp/platform_view_main.dart
17+
///
18+
/// # Make your changes.
19+
///
20+
/// # Run the test against baseline.
21+
/// flutter drive lib/platform_view/hcpp/platform_view_main.dart
22+
/// ```
23+
///
24+
/// For a convenient way to deflake a test, see `tool/deflake.dart`.
25+
void main() async {
26+
const String goldenPrefix = 'hybrid_composition_pp_platform_view';
27+
28+
late final FlutterDriver flutterDriver;
29+
late final NativeDriver nativeDriver;
30+
31+
setUpAll(() async {
32+
if (isLuci) {
33+
await enableSkiaGoldComparator(namePrefix: 'android_engine_test$goldenVariant');
34+
}
35+
flutterDriver = await FlutterDriver.connect();
36+
nativeDriver = await AndroidNativeDriver.connect(flutterDriver);
37+
await nativeDriver.configureForScreenshotTesting();
38+
await flutterDriver.waitUntilFirstFrameRasterized();
39+
});
40+
41+
tearDownAll(() async {
42+
await nativeDriver.close();
43+
await flutterDriver.close();
44+
});
45+
46+
test('should screenshot an HCPP platform view', () async {
47+
await expectLater(
48+
nativeDriver.screenshot(),
49+
matchesGoldenFile('$goldenPrefix.platform_view.png'),
50+
);
51+
}, timeout: Timeout.none);
52+
53+
test('should rotate landscape and screenshot the platform view', () async {
54+
await nativeDriver.rotateToLandscape();
55+
await expectLater(
56+
nativeDriver.screenshot(),
57+
matchesGoldenFile('$goldenPrefix.platform_view_landscape_rotated.png'),
58+
);
59+
60+
await nativeDriver.rotateResetDefault();
61+
await expectLater(
62+
nativeDriver.screenshot(),
63+
matchesGoldenFile('$goldenPrefix.platform_view_portait_rotated_back.png'),
64+
);
65+
}, timeout: Timeout.none);
66+
}

engine/src/flutter/shell/platform/android/android_shell_holder_unittests.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ class MockPlatformViewAndroidJNI : public PlatformViewAndroidJNI {
110110
int32_t viewHeight,
111111
MutatorsStack mutators_stack),
112112
(override));
113+
MOCK_METHOD(void, onEndFrame2, (), (override));
113114
MOCK_METHOD(std::unique_ptr<std::vector<std::string>>,
114115
FlutterViewComputePlatformResolvedLocale,
115116
(std::vector<std::string> supported_locales_data),

0 commit comments

Comments
 (0)