Skip to content

Commit 761c162

Browse files
author
Jonah Williams
authored
[Android] fix hcpp overlay layer intersection. (#163024)
Since we only have a single overlay layer, we need to diff out the platform views that _would_ intersect if we did the correct layering.
1 parent e7e5480 commit 761c162

File tree

3 files changed

+203
-3
lines changed

3 files changed

+203
-3
lines changed
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
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 'dart:convert';
6+
7+
import 'package:android_driver_extensions/extension.dart';
8+
import 'package:flutter/foundation.dart';
9+
import 'package:flutter/gestures.dart';
10+
import 'package:flutter/material.dart';
11+
import 'package:flutter/rendering.dart';
12+
import 'package:flutter/services.dart';
13+
import 'package:flutter_driver/driver_extension.dart';
14+
15+
import '../src/allow_list_devices.dart';
16+
17+
void main() async {
18+
ensureAndroidDevice();
19+
enableFlutterDriverExtension(
20+
handler: (String? command) async {
21+
return json.encode(<String, Object?>{
22+
'supported': await HybridAndroidViewController.checkIfSupported(),
23+
});
24+
},
25+
commands: <CommandExtension>[nativeDriverCommands],
26+
);
27+
28+
// Run on full screen.
29+
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
30+
runApp(const MainApp());
31+
}
32+
33+
// This should appear as the yellow line over a blue box. The
34+
// green box should not be visible unless the platform view has not loaded yet.
35+
final class MainApp extends StatefulWidget {
36+
const MainApp({super.key});
37+
38+
@override
39+
State<MainApp> createState() => _MainAppState();
40+
}
41+
42+
class _MainAppState extends State<MainApp> {
43+
@override
44+
Widget build(BuildContext context) {
45+
return MaterialApp(
46+
debugShowCheckedModeBanner: false,
47+
home: Stack(
48+
children: <Widget>[
49+
Positioned.directional(
50+
top: 100,
51+
textDirection: TextDirection.ltr,
52+
child: const SizedBox(
53+
width: 200,
54+
height: 200,
55+
child: _HybridCompositionAndroidPlatformView(viewType: 'box_platform_view'),
56+
),
57+
),
58+
Positioned.directional(
59+
top: 200,
60+
textDirection: TextDirection.ltr,
61+
child: const SizedBox(width: 800, height: 200, child: ColoredBox(color: Colors.yellow)),
62+
),
63+
Positioned.directional(
64+
top: 300,
65+
textDirection: TextDirection.ltr,
66+
child: const SizedBox(
67+
width: 200,
68+
height: 200,
69+
child: _HybridCompositionAndroidPlatformView(viewType: 'box_platform_view'),
70+
),
71+
),
72+
Positioned.directional(
73+
top: 400,
74+
textDirection: TextDirection.ltr,
75+
child: const SizedBox(width: 800, height: 200, child: ColoredBox(color: Colors.red)),
76+
),
77+
Positioned.directional(
78+
top: 500,
79+
textDirection: TextDirection.ltr,
80+
child: const SizedBox(
81+
width: 200,
82+
height: 200,
83+
child: _HybridCompositionAndroidPlatformView(viewType: 'box_platform_view'),
84+
),
85+
),
86+
Positioned.directional(
87+
top: 600,
88+
textDirection: TextDirection.ltr,
89+
child: const SizedBox(width: 800, height: 200, child: ColoredBox(color: Colors.orange)),
90+
),
91+
],
92+
),
93+
);
94+
}
95+
}
96+
97+
final class _HybridCompositionAndroidPlatformView extends StatelessWidget {
98+
const _HybridCompositionAndroidPlatformView({required this.viewType});
99+
100+
final String viewType;
101+
102+
@override
103+
Widget build(BuildContext context) {
104+
return PlatformViewLink(
105+
viewType: viewType,
106+
surfaceFactory: (BuildContext context, PlatformViewController controller) {
107+
return AndroidViewSurface(
108+
controller: controller as AndroidViewController,
109+
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
110+
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
111+
);
112+
},
113+
onCreatePlatformView: (PlatformViewCreationParams params) {
114+
return PlatformViewsService.initHybridAndroidView(
115+
id: params.id,
116+
viewType: viewType,
117+
layoutDirection: TextDirection.ltr,
118+
creationParamsCodec: const StandardMessageCodec(),
119+
)
120+
..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
121+
..create();
122+
},
123+
);
124+
}
125+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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 'dart:convert';
6+
7+
import 'package:android_driver_extensions/native_driver.dart';
8+
import 'package:android_driver_extensions/skia_gold.dart';
9+
import 'package:flutter_driver/flutter_driver.dart';
10+
import 'package:test/test.dart';
11+
12+
import '../_luci_skia_gold_prelude.dart';
13+
14+
/// For local debugging, a (local) golden-file is required as a baseline:
15+
///
16+
/// ```sh
17+
/// # Checkout HEAD, i.e. *before* changes you want to test.
18+
/// UPDATE_GOLDENS=1 flutter drive lib/platform_view/hcpp/platform_view_overlapping_main.dart
19+
///
20+
/// # Make your changes.
21+
///
22+
/// # Run the test against baseline.
23+
/// flutter drive lib/platform_view/hcpp/platform_view_overlapping_main.dart
24+
/// ```
25+
///
26+
/// For a convenient way to deflake a test, see `tool/deflake.dart`.
27+
void main() async {
28+
const String goldenPrefix = 'hybrid_composition_pp_overlapping_platform_view';
29+
30+
late final FlutterDriver flutterDriver;
31+
late final NativeDriver nativeDriver;
32+
33+
setUpAll(() async {
34+
if (isLuci) {
35+
await enableSkiaGoldComparator(namePrefix: 'android_engine_test$goldenVariant');
36+
}
37+
flutterDriver = await FlutterDriver.connect();
38+
nativeDriver = await AndroidNativeDriver.connect(flutterDriver);
39+
await nativeDriver.configureForScreenshotTesting();
40+
await flutterDriver.waitUntilFirstFrameRasterized();
41+
});
42+
43+
tearDownAll(() async {
44+
await nativeDriver.close();
45+
await flutterDriver.close();
46+
});
47+
48+
test('verify that HCPP is supported and enabled', () async {
49+
final Map<String, Object?> response =
50+
json.decode(await flutterDriver.requestData('')) as Map<String, Object?>;
51+
52+
expect(response['supported'], true);
53+
}, timeout: Timeout.none);
54+
55+
test('should screenshot multiple HCPP platform view with overlays', () async {
56+
await expectLater(
57+
nativeDriver.screenshot(),
58+
matchesGoldenFile('$goldenPrefix.multiple_overlays.png'),
59+
);
60+
}, timeout: Timeout.none);
61+
}

engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.cc

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ void AndroidExternalViewEmbedder2::SubmitFlutterView(
100100
// If there is no overlay Surface, initialize one on the platform thread. This
101101
// will only be done once per application launch, as the singular overlay
102102
// surface is never released.
103-
surface_pool_->ResetLayers();
104103
if (!surface_pool_->HasLayers()) {
105104
std::shared_ptr<fml::CountDownLatch> latch =
106105
std::make_shared<fml::CountDownLatch>(1u);
@@ -112,12 +111,14 @@ void AndroidExternalViewEmbedder2::SubmitFlutterView(
112111
}));
113112
latch->Wait();
114113
}
114+
surface_pool_->ResetLayers();
115115

116116
// Create Overlay frame. If overlay surface creation failed,
117117
// all this work must be skipped.
118118
std::unique_ptr<SurfaceFrame> overlay_frame;
119119
if (surface_pool_->HasLayers()) {
120-
for (int64_t view_id : composition_order_) {
120+
for (size_t i = 0; i < composition_order_.size(); i++) {
121+
int64_t view_id = composition_order_[i];
121122
std::unordered_map<int64_t, SkRect>::const_iterator overlay =
122123
overlay_layers.find(view_id);
123124

@@ -128,13 +129,26 @@ void AndroidExternalViewEmbedder2::SubmitFlutterView(
128129
std::shared_ptr<OverlayLayer> layer = surface_pool_->GetLayer(
129130
context, android_context_, jni_facade_, surface_factory_);
130131
overlay_frame = layer->surface->AcquireFrame(frame_size_);
132+
overlay_frame->Canvas()->Clear(flutter::DlColor::kTransparent());
131133
}
132134

133135
DlCanvas* overlay_canvas = overlay_frame->Canvas();
134136
int restore_count = overlay_canvas->GetSaveCount();
135137
overlay_canvas->Save();
136138
overlay_canvas->ClipRect(overlay->second);
137-
overlay_canvas->Clear(DlColor::kTransparent());
139+
140+
// For all following platform views that would cover this overlay,
141+
// emulate the effect by adding a difference clip. This makes the
142+
// overlays appear as if they are under the platform view, when in
143+
// reality there is only a single layer.
144+
for (size_t j = i + 1; j < composition_order_.size(); j++) {
145+
SkRect view_rect = GetViewRect(composition_order_[j], view_params_);
146+
overlay_canvas->ClipRect(
147+
DlRect::MakeLTRB(view_rect.left(), view_rect.top(),
148+
view_rect.right(), view_rect.bottom()),
149+
DlClipOp::kDifference);
150+
}
151+
138152
slices_[view_id]->render_into(overlay_canvas);
139153
overlay_canvas->RestoreToCount(restore_count);
140154
}

0 commit comments

Comments
 (0)