diff --git a/dev/integration_tests/android_engine_test/lib/hcpp/platform_view_main.dart b/dev/integration_tests/android_engine_test/lib/hcpp/platform_view_main.dart index 1b3c1d32eee..e3635572b04 100644 --- a/dev/integration_tests/android_engine_test/lib/hcpp/platform_view_main.dart +++ b/dev/integration_tests/android_engine_test/lib/hcpp/platform_view_main.dart @@ -12,6 +12,7 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_driver/driver_extension.dart'; +import '../platform_view/_shared.dart'; import '../src/allow_list_devices.dart'; void main() async { @@ -27,32 +28,11 @@ void main() async { // Run on full screen. await SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); - runApp(const MainApp()); -} - -final class MainApp extends StatelessWidget { - const MainApp({super.key}); - - // This should appear as the yellow line over a blue box. The - // green box should not be visible unless the platform view has not loaded yet. - @override - Widget build(BuildContext context) { - return const MaterialApp( - debugShowCheckedModeBanner: false, - home: Stack( - alignment: AlignmentDirectional.center, - children: [ - SizedBox(width: 190, height: 190, child: ColoredBox(color: Colors.green)), - SizedBox( - width: 200, - height: 200, - child: _HybridCompositionAndroidPlatformView(viewType: 'box_platform_view'), - ), - SizedBox(width: 800, height: 25, child: ColoredBox(color: Colors.yellow)), - ], - ), - ); - } + runApp( + const MainApp( + platformView: _HybridCompositionAndroidPlatformView(viewType: 'box_platform_view'), + ), + ); } final class _HybridCompositionAndroidPlatformView extends StatelessWidget { @@ -60,7 +40,6 @@ final class _HybridCompositionAndroidPlatformView extends StatelessWidget { final String viewType; - // TODO(jonahwilliams): swap this out with new platform view APIs. @override Widget build(BuildContext context) { return PlatformViewLink( diff --git a/dev/integration_tests/android_engine_test/lib/platform_view/_shared.dart b/dev/integration_tests/android_engine_test/lib/platform_view/_shared.dart index 77a44e6d10d..33c011cd963 100644 --- a/dev/integration_tests/android_engine_test/lib/platform_view/_shared.dart +++ b/dev/integration_tests/android_engine_test/lib/platform_view/_shared.dart @@ -4,18 +4,50 @@ import 'package:flutter/material.dart'; -final class MainApp extends StatelessWidget { +// This should appear as the yellow line over a blue box. The +// green box should not be visible unless the platform view has not loaded yet. +final class MainApp extends StatefulWidget { const MainApp({super.key, required this.platformView}); + final Widget platformView; + @override + State createState() => _MainAppState(); +} + +class _MainAppState extends State { + bool showPlatformView = true; + + void _togglePlatformView() { + setState(() { + showPlatformView = !showPlatformView; + }); + } + @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: Stack( + alignment: AlignmentDirectional.center, children: [ - platformView, - Center(child: Container(width: 100, height: 100, color: Colors.red)), + TextButton( + key: const ValueKey('AddOverlay'), + onPressed: _togglePlatformView, + child: const SizedBox(width: 190, height: 190, child: ColoredBox(color: Colors.green)), + ), + if (showPlatformView) ...[ + SizedBox(width: 200, height: 200, child: widget.platformView), + TextButton( + key: const ValueKey('RemoveOverlay'), + onPressed: _togglePlatformView, + child: const SizedBox( + width: 800, + height: 25, + child: ColoredBox(color: Colors.yellow), + ), + ), + ], ], ), ); diff --git a/dev/integration_tests/android_engine_test/test_driver/hcpp/platform_view_main_test.dart b/dev/integration_tests/android_engine_test/test_driver/hcpp/platform_view_main_test.dart index a93856e5933..fa62b7e6020 100644 --- a/dev/integration_tests/android_engine_test/test_driver/hcpp/platform_view_main_test.dart +++ b/dev/integration_tests/android_engine_test/test_driver/hcpp/platform_view_main_test.dart @@ -41,6 +41,8 @@ void main() async { }); tearDownAll(() async { + await flutterDriver.tap(find.byValueKey('AddOverlay')); + await nativeDriver.close(); await flutterDriver.close(); }); @@ -72,4 +74,16 @@ void main() async { matchesGoldenFile('$goldenPrefix.platform_view_portait_rotated_back.png'), ); }, timeout: Timeout.none); + + // Note: this doesn't reset the app so if additional test cases are added + // make sure to press the button again. + test('should remove overlay when platform view is removed', () async { + await flutterDriver.tap(find.byValueKey('RemoveOverlay')); + await Future.delayed(const Duration(seconds: 1)); + + await expectLater( + nativeDriver.screenshot(), + matchesGoldenFile('$goldenPrefix.removed_overlay.png'), + ); + }, timeout: Timeout.none); } diff --git a/dev/integration_tests/android_engine_test/test_driver/platform_view/texture_layer_hybrid_composition_platform_view_main_test.dart b/dev/integration_tests/android_engine_test/test_driver/platform_view/texture_layer_hybrid_composition_platform_view_main_test.dart index 68d8d4cc49c..8a76d4a6b69 100644 --- a/dev/integration_tests/android_engine_test/test_driver/platform_view/texture_layer_hybrid_composition_platform_view_main_test.dart +++ b/dev/integration_tests/android_engine_test/test_driver/platform_view/texture_layer_hybrid_composition_platform_view_main_test.dart @@ -45,6 +45,8 @@ void main() async { }); tearDownAll(() async { + await flutterDriver.tap(find.byValueKey('AddOverlay')); + await nativeDriver.close(); await flutterDriver.close(); }); @@ -69,4 +71,14 @@ void main() async { matchesGoldenFile('$goldenPrefix.blue_orange_gradient_portait_rotated_back.png'), ); }, timeout: Timeout.none); + + test('should hide overlay layer', () async { + await flutterDriver.tap(find.byValueKey('RemoveOverlay')); + await Future.delayed(const Duration(seconds: 1)); + + await expectLater( + nativeDriver.screenshot(), + matchesGoldenFile('$goldenPrefix.hide_overlay.png'), + ); + }, timeout: Timeout.none); } diff --git a/dev/integration_tests/android_engine_test/test_driver/platform_view/virtual_display_platform_view_main_test.dart b/dev/integration_tests/android_engine_test/test_driver/platform_view/virtual_display_platform_view_main_test.dart index 510271c3b59..59c36179db4 100644 --- a/dev/integration_tests/android_engine_test/test_driver/platform_view/virtual_display_platform_view_main_test.dart +++ b/dev/integration_tests/android_engine_test/test_driver/platform_view/virtual_display_platform_view_main_test.dart @@ -56,6 +56,8 @@ void main() async { }); tearDownAll(() async { + await flutterDriver.tap(find.byValueKey('AddOverlay')); + await nativeDriver.close(); await flutterDriver.close(); }); @@ -86,4 +88,14 @@ void main() async { ), ); }, timeout: Timeout.none); + + test('should hide overlay layer', () async { + await flutterDriver.tap(find.byValueKey('RemoveOverlay')); + await Future.delayed(const Duration(seconds: 1)); + + await expectLater( + nativeDriver.screenshot(), + matchesGoldenFile('$goldenPrefix.hide_overlay.png'), + ); + }, timeout: Timeout.none); } diff --git a/engine/src/flutter/shell/platform/android/android_shell_holder_unittests.cc b/engine/src/flutter/shell/platform/android/android_shell_holder_unittests.cc index b6b0b88ce07..cbd3cd27fcb 100644 --- a/engine/src/flutter/shell/platform/android/android_shell_holder_unittests.cc +++ b/engine/src/flutter/shell/platform/android/android_shell_holder_unittests.cc @@ -111,6 +111,8 @@ class MockPlatformViewAndroidJNI : public PlatformViewAndroidJNI { MutatorsStack mutators_stack), (override)); MOCK_METHOD(void, onEndFrame2, (), (override)); + MOCK_METHOD(void, showOverlaySurface2, (), (override)); + MOCK_METHOD(void, hideOverlaySurface2, (), (override)); MOCK_METHOD(std::unique_ptr>, FlutterViewComputePlatformResolvedLocale, (std::vector supported_locales_data), diff --git a/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.cc b/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.cc index 7b39f8c8fab..d233e6d76bb 100644 --- a/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.cc +++ b/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.cc @@ -76,10 +76,15 @@ void AndroidExternalViewEmbedder2::SubmitFlutterView( if (!FrameHasPlatformLayers()) { frame->Submit(); + // If the previous frame had platform views, hide the overlay surface. + if (previous_frame_view_count_ > 0) { + jni_facade_->hideOverlaySurface2(); + } jni_facade_->applyTransaction(); return; } + bool prev_frame_no_platform_views = previous_frame_view_count_ == 0; std::unordered_map view_rects; for (auto platform_id : composition_order_) { view_rects[platform_id] = GetViewRect(platform_id, view_params_); @@ -143,9 +148,13 @@ void AndroidExternalViewEmbedder2::SubmitFlutterView( task_runners_.GetPlatformTaskRunner()->PostTask(fml::MakeCopyable( [&, composition_order = composition_order_, view_params = view_params_, jni_facade = jni_facade_, device_pixel_ratio = device_pixel_ratio_, - slices = std::move(slices_)]() -> void { + slices = std::move(slices_), prev_frame_no_platform_views]() -> void { jni_facade->swapTransaction(); + if (prev_frame_no_platform_views) { + jni_facade_->showOverlaySurface2(); + } + for (int64_t view_id : composition_order) { SkRect view_rect = GetViewRect(view_id, view_params); const EmbeddedViewParams& params = view_params.at(view_id); diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index bc6403ebde3..dac6d25149c 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -1322,6 +1322,28 @@ public class FlutterJNI { return platformViewsController2.createOverlaySurface(); } + @SuppressWarnings("unused") + @SuppressLint("NewApi") + @UiThread + public void showOverlaySurface2() { + if (platformViewsController2 == null) { + throw new RuntimeException( + "platformViewsController must be set before attempting to destroy an overlay surface"); + } + platformViewsController2.showOverlaySurface(); + } + + @SuppressWarnings("unused") + @SuppressLint("NewApi") + @UiThread + public void hideOverlaySurface2() { + if (platformViewsController2 == null) { + throw new RuntimeException( + "platformViewsController must be set before attempting to destroy an overlay surface"); + } + platformViewsController2.hideOverlaySurface(); + } + @SuppressWarnings("unused") @SuppressLint("NewApi") @UiThread diff --git a/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewRegistryImpl.java b/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewRegistryImpl.java index a071d3cca75..724853998ed 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewRegistryImpl.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewRegistryImpl.java @@ -7,7 +7,7 @@ package io.flutter.plugin.platform; import java.util.HashMap; import java.util.Map; -class PlatformViewRegistryImpl implements PlatformViewRegistry { +public class PlatformViewRegistryImpl implements PlatformViewRegistry { PlatformViewRegistryImpl() { viewFactories = new HashMap<>(); diff --git a/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController2.java b/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController2.java index e59211c2fef..0410141191d 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController2.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController2.java @@ -65,6 +65,7 @@ public class PlatformViewsController2 implements PlatformViewsAccessibilityDeleg private final ArrayList pendingTransactions; private final ArrayList activeTransactions; private Surface overlayerSurface = null; + private SurfaceControl overlaySurfaceControl = null; public PlatformViewsController2() { accessibilityEventsDelegate = new AccessibilityEventsDelegate(); @@ -577,6 +578,7 @@ public class PlatformViewsController2 implements PlatformViewsAccessibilityDeleg tx.setLayer(surfaceControl, 1000); tx.apply(); overlayerSurface = new Surface(surfaceControl); + overlaySurfaceControl = surfaceControl; } return new FlutterOverlaySurface(0, overlayerSurface); @@ -586,9 +588,32 @@ public class PlatformViewsController2 implements PlatformViewsAccessibilityDeleg if (overlayerSurface != null) { overlayerSurface.release(); overlayerSurface = null; + overlaySurfaceControl = null; } } + @TargetApi(API_LEVELS.API_34) + @RequiresApi(API_LEVELS.API_34) + public void showOverlaySurface() { + if (overlaySurfaceControl == null) { + return; + } + SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); + tx.setVisibility(overlaySurfaceControl, /*visible=*/ true); + tx.apply(); + } + + @TargetApi(API_LEVELS.API_34) + @RequiresApi(API_LEVELS.API_34) + public void hideOverlaySurface() { + if (overlaySurfaceControl == null) { + return; + } + SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); + tx.setVisibility(overlaySurfaceControl, /*visible=*/ false); + tx.apply(); + } + //// Message Handler /////// private final PlatformViewsChannel2.PlatformViewsHandler channelHandler = diff --git a/engine/src/flutter/shell/platform/android/jni/jni_mock.h b/engine/src/flutter/shell/platform/android/jni/jni_mock.h index 55462360767..8f9158c97de 100644 --- a/engine/src/flutter/shell/platform/android/jni/jni_mock.h +++ b/engine/src/flutter/shell/platform/android/jni/jni_mock.h @@ -140,6 +140,8 @@ class JNIMock final : public PlatformViewAndroidJNI { (override)); MOCK_METHOD(void, onEndFrame2, (), (override)); + MOCK_METHOD(void, hideOverlaySurface2, (), (override)); + MOCK_METHOD(void, showOverlaySurface2, (), (override)); MOCK_METHOD(std::unique_ptr>, FlutterViewComputePlatformResolvedLocale, diff --git a/engine/src/flutter/shell/platform/android/jni/platform_view_android_jni.h b/engine/src/flutter/shell/platform/android/jni/platform_view_android_jni.h index 70fcff60078..4c78c044f39 100644 --- a/engine/src/flutter/shell/platform/android/jni/platform_view_android_jni.h +++ b/engine/src/flutter/shell/platform/android/jni/platform_view_android_jni.h @@ -239,6 +239,10 @@ class PlatformViewAndroidJNI { int32_t viewHeight, MutatorsStack mutators_stack) = 0; + virtual void showOverlaySurface2() = 0; + + virtual void hideOverlaySurface2() = 0; + //---------------------------------------------------------------------------- /// @brief Computes the locale Android would select. /// diff --git a/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc b/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc index 221af375418..8e73491c5ea 100644 --- a/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc +++ b/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc @@ -160,6 +160,10 @@ static jmethodID g_on_display_platform_view2_method = nullptr; static jmethodID g_on_end_frame2_method = nullptr; +static jmethodID g_show_overlay_surface2_method = nullptr; + +static jmethodID g_hide_overlay_surface2_method = nullptr; + // Mutators static fml::jni::ScopedJavaGlobalRef* g_mutators_stack_class = nullptr; static jmethodID g_mutators_stack_init_method = nullptr; @@ -1052,6 +1056,20 @@ bool RegisterApi(JNIEnv* env) { FML_LOG(ERROR) << "Could not locate onEndFrame2 method"; return false; } + + g_show_overlay_surface2_method = env->GetMethodID( + g_flutter_jni_class->obj(), "showOverlaySurface2", "()V"); + if (g_on_end_frame2_method == nullptr) { + FML_LOG(ERROR) << "Could not locate showOverlaySurface2 method"; + return false; + } + + g_hide_overlay_surface2_method = env->GetMethodID( + g_flutter_jni_class->obj(), "hideOverlaySurface2", "()V"); + if (g_on_end_frame2_method == nullptr) { + FML_LOG(ERROR) << "Could not locate hideOverlaySurface2 method"; + return false; + } // fml::jni::ScopedJavaLocalRef overlay_surface_class( @@ -2184,4 +2202,28 @@ void PlatformViewAndroidJNIImpl::onEndFrame2() { FML_CHECK(fml::jni::CheckException(env)); } +void PlatformViewAndroidJNIImpl::showOverlaySurface2() { + JNIEnv* env = fml::jni::AttachCurrentThread(); + + auto java_object = java_object_.get(env); + if (java_object.is_null()) { + return; + } + + env->CallVoidMethod(java_object.obj(), g_show_overlay_surface2_method); + FML_CHECK(fml::jni::CheckException(env)); +} + +void PlatformViewAndroidJNIImpl::hideOverlaySurface2() { + JNIEnv* env = fml::jni::AttachCurrentThread(); + + auto java_object = java_object_.get(env); + if (java_object.is_null()) { + return; + } + + env->CallVoidMethod(java_object.obj(), g_hide_overlay_surface2_method); + FML_CHECK(fml::jni::CheckException(env)); +} + } // namespace flutter diff --git a/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.h b/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.h index 694ac1c0098..06be3d01c1f 100644 --- a/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.h +++ b/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.h @@ -124,6 +124,10 @@ class PlatformViewAndroidJNIImpl final : public PlatformViewAndroidJNI { int32_t viewHeight, MutatorsStack mutators_stack) override; + void showOverlaySurface2() override; + + void hideOverlaySurface2() override; + void onEndFrame2() override; private: