Basic support for resizing overlay surfaces in hybrid composition (#19402)
diff --git a/shell/platform/android/external_view_embedder/external_view_embedder.cc b/shell/platform/android/external_view_embedder/external_view_embedder.cc index 8bb4eaa..259deec7 100644 --- a/shell/platform/android/external_view_embedder/external_view_embedder.cc +++ b/shell/platform/android/external_view_embedder/external_view_embedder.cc
@@ -238,6 +238,9 @@ double device_pixel_ratio, fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) { Reset(); + if (frame_size_ != frame_size) { + surface_pool_->DestroyLayers(jni_facade_); + } frame_size_ = frame_size; device_pixel_ratio_ = device_pixel_ratio; // JNI method must be called on the platform thread.
diff --git a/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc b/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc index 8bac045..b7d329c 100644 --- a/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc +++ b/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc
@@ -402,5 +402,20 @@ embedder->EndFrame(/*should_resubmit_frame=*/false, raster_thread_merger); } +TEST(AndroidExternalViewEmbedder, DestroyOverlayLayersOnSizeChange) { + auto jni_mock = std::make_shared<JNIMock>(); + auto embedder = + std::make_unique<AndroidExternalViewEmbedder>(nullptr, jni_mock, nullptr); + + auto raster_thread_merger = GetThreadMergerFromPlatformThread(); + ASSERT_FALSE(raster_thread_merger->IsMerged()); + + embedder->BeginFrame(SkISize::Make(10, 20), nullptr, 1.0, + raster_thread_merger); + EXPECT_CALL(*jni_mock, FlutterViewDestroyOverlaySurfaces()); + embedder->BeginFrame(SkISize::Make(30, 40), nullptr, 1.0, + raster_thread_merger); +} + } // namespace testing } // namespace flutter
diff --git a/shell/platform/android/external_view_embedder/surface_pool.cc b/shell/platform/android/external_view_embedder/surface_pool.cc index 8258234..934b153 100644 --- a/shell/platform/android/external_view_embedder/surface_pool.cc +++ b/shell/platform/android/external_view_embedder/surface_pool.cc
@@ -70,6 +70,13 @@ available_layer_index_ = 0; } +void SurfacePool::DestroyLayers( + std::shared_ptr<PlatformViewAndroidJNI> jni_facade) { + layers_.clear(); + available_layer_index_ = 0; + jni_facade->FlutterViewDestroyOverlaySurfaces(); +} + std::vector<std::shared_ptr<OverlayLayer>> SurfacePool::GetUnusedLayers() { std::vector<std::shared_ptr<OverlayLayer>> results; for (size_t i = available_layer_index_; i < layers_.size(); i++) {
diff --git a/shell/platform/android/external_view_embedder/surface_pool.h b/shell/platform/android/external_view_embedder/surface_pool.h index e4eee52..f4412a8 100644 --- a/shell/platform/android/external_view_embedder/surface_pool.h +++ b/shell/platform/android/external_view_embedder/surface_pool.h
@@ -64,6 +64,9 @@ // Marks the layers in the pool as available for reuse. void RecycleLayers(); + // Destroys all the layers in the pool. + void DestroyLayers(std::shared_ptr<PlatformViewAndroidJNI> jni_facade); + private: // The index of the entry in the layers_ vector that determines the beginning // of the unused layers. For example, consider the following vector:
diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java b/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java index f4f1677..dfc5fae 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java
@@ -10,13 +10,16 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.ColorSpace; +import android.graphics.PixelFormat; import android.hardware.HardwareBuffer; import android.media.Image; import android.media.Image.Plane; import android.media.ImageReader; +import android.view.Surface; import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import io.flutter.embedding.engine.renderer.FlutterRenderer; import io.flutter.embedding.engine.renderer.RenderSurface; @@ -35,7 +38,7 @@ @SuppressLint("ViewConstructor") @TargetApi(19) public class FlutterImageView extends View implements RenderSurface { - private final ImageReader imageReader; + @NonNull private ImageReader imageReader; @Nullable private Image nextImage; @Nullable private Image currentImage; @Nullable private Bitmap currentBitmap; @@ -63,13 +66,39 @@ * Constructs a {@code FlutterImageView} with an {@link android.media.ImageReader} that provides * the Flutter UI. */ - public FlutterImageView( - @NonNull Context context, @NonNull ImageReader imageReader, SurfaceKind kind) { + public FlutterImageView(@NonNull Context context, int width, int height, SurfaceKind kind) { + super(context, null); + this.imageReader = createImageReader(width, height); + this.kind = kind; + } + + @VisibleForTesting + FlutterImageView(@NonNull Context context, @NonNull ImageReader imageReader, SurfaceKind kind) { super(context, null); this.imageReader = imageReader; this.kind = kind; } + @TargetApi(19) + @NonNull + private static ImageReader createImageReader(int width, int height) { + if (android.os.Build.VERSION.SDK_INT >= 29) { + return ImageReader.newInstance( + width, + height, + PixelFormat.RGBA_8888, + 3, + HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT); + } else { + return ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 3); + } + } + + @NonNull + public Surface getSurface() { + return imageReader.getSurface(); + } + @Nullable @Override public FlutterRenderer getAttachedRenderer() { @@ -179,4 +208,15 @@ currentBitmap.copyPixelsFromBuffer(imagePlane.getBuffer()); } } + + @Override + protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) { + if (width == imageReader.getWidth() && height == imageReader.getHeight()) { + return; + } + if (kind == SurfaceKind.background && flutterRenderer != null) { + imageReader = createImageReader(width, height); + flutterRenderer.swapSurface(imageReader.getSurface()); + } + } }
diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 8e4636d..465f718 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java
@@ -10,7 +10,6 @@ import android.content.res.Configuration; import android.graphics.Insets; import android.graphics.Rect; -import android.media.ImageReader; import android.os.Build; import android.text.format.DateFormat; import android.util.AttributeSet; @@ -945,9 +944,9 @@ public void convertToImageView() { renderSurface.pause(); - ImageReader imageReader = PlatformViewsController.createImageReader(getWidth(), getHeight()); flutterImageView = - new FlutterImageView(getContext(), imageReader, FlutterImageView.SurfaceKind.background); + new FlutterImageView( + getContext(), getWidth(), getHeight(), FlutterImageView.SurfaceKind.background); renderSurface = flutterImageView; if (flutterEngine != null) { renderSurface.attachToRenderer(flutterEngine.getRenderer());
diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index 9c24f06..f344df6 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java
@@ -855,6 +855,17 @@ } return platformViewsController.createOverlaySurface(); } + + @SuppressWarnings("unused") + @UiThread + public void destroyOverlaySurfaces() { + ensureRunningOnMainThread(); + if (platformViewsController == null) { + throw new RuntimeException( + "platformViewsController must be set before attempting to destroy an overlay surface"); + } + platformViewsController.destroyOverlaySurfaces(); + } // ----- End Engine Lifecycle Support ---- // ----- Start Localization Support ----
diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index f776357..40c56be 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java
@@ -9,9 +9,6 @@ import android.annotation.TargetApi; import android.content.Context; -import android.graphics.PixelFormat; -import android.hardware.HardwareBuffer; -import android.media.ImageReader; import android.os.Build; import android.util.DisplayMetrics; import android.util.Log; @@ -694,28 +691,20 @@ } @TargetApi(19) - public static ImageReader createImageReader(int width, int height) { - if (android.os.Build.VERSION.SDK_INT >= 29) { - return ImageReader.newInstance( - width, - height, - PixelFormat.RGBA_8888, - 3, - HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT); - } else { - return ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 3); - } - } - - @TargetApi(19) public FlutterOverlaySurface createOverlaySurface() { - ImageReader imageReader = createImageReader(flutterView.getWidth(), flutterView.getHeight()); FlutterImageView imageView = new FlutterImageView( - flutterView.getContext(), imageReader, FlutterImageView.SurfaceKind.overlay); + flutterView.getContext(), + flutterView.getWidth(), + flutterView.getHeight(), + FlutterImageView.SurfaceKind.overlay); int id = nextOverlayLayerId++; overlayLayerViews.put(id, imageView); - return new FlutterOverlaySurface(id, imageReader.getSurface()); + return new FlutterOverlaySurface(id, imageView.getSurface()); + } + + public void destroyOverlaySurfaces() { + overlayLayerViews.clear(); } }
diff --git a/shell/platform/android/jni/jni_mock.h b/shell/platform/android/jni/jni_mock.h index 245c8e7..280e421 100644 --- a/shell/platform/android/jni/jni_mock.h +++ b/shell/platform/android/jni/jni_mock.h
@@ -80,6 +80,8 @@ (), (override)); + MOCK_METHOD(void, FlutterViewDestroyOverlaySurfaces, (), (override)); + MOCK_METHOD(std::unique_ptr<std::vector<std::string>>, FlutterViewComputePlatformResolvedLocale, (std::vector<std::string> supported_locales_data),
diff --git a/shell/platform/android/jni/platform_view_android_jni.h b/shell/platform/android/jni/platform_view_android_jni.h index 311209d..9d6e9b1 100644 --- a/shell/platform/android/jni/platform_view_android_jni.h +++ b/shell/platform/android/jni/platform_view_android_jni.h
@@ -176,6 +176,13 @@ FlutterViewCreateOverlaySurface() = 0; //---------------------------------------------------------------------------- + /// @brief Destroys the overlay surfaces. + /// + /// @note Must be called from the platform thread. + /// + virtual void FlutterViewDestroyOverlaySurfaces() = 0; + + //---------------------------------------------------------------------------- /// @brief Computes the locale Android would select. /// virtual std::unique_ptr<std::vector<std::string>>
diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index 2675282..e95b47d 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc
@@ -83,6 +83,8 @@ static jmethodID g_create_overlay_surface_method = nullptr; +static jmethodID g_destroy_overlay_surfaces_method = nullptr; + static jmethodID g_on_begin_frame_method = nullptr; static jmethodID g_on_end_frame_method = nullptr; @@ -721,6 +723,14 @@ return false; } + g_destroy_overlay_surfaces_method = env->GetMethodID( + g_flutter_jni_class->obj(), "destroyOverlaySurfaces", "()V"); + + if (g_destroy_overlay_surfaces_method == nullptr) { + FML_LOG(ERROR) << "Could not locate destroyOverlaySurfaces method"; + return false; + } + fml::jni::ScopedJavaLocalRef<jclass> overlay_surface_class( env, env->FindClass("io/flutter/embedding/engine/FlutterOverlaySurface")); if (overlay_surface_class.is_null()) { @@ -1179,6 +1189,19 @@ overlay_id, std::move(overlay_window)); } +void PlatformViewAndroidJNIImpl::FlutterViewDestroyOverlaySurfaces() { + JNIEnv* env = fml::jni::AttachCurrentThread(); + + auto java_object = java_object_.get(env); + if (java_object.is_null()) { + return; + } + + env->CallVoidMethod(java_object.obj(), g_destroy_overlay_surfaces_method); + + FML_CHECK(CheckException(env)); +} + std::unique_ptr<std::vector<std::string>> PlatformViewAndroidJNIImpl::FlutterViewComputePlatformResolvedLocale( std::vector<std::string> supported_locales_data) {
diff --git a/shell/platform/android/platform_view_android_jni_impl.h b/shell/platform/android/platform_view_android_jni_impl.h index b496f2d..cf743bb 100644 --- a/shell/platform/android/platform_view_android_jni_impl.h +++ b/shell/platform/android/platform_view_android_jni_impl.h
@@ -69,6 +69,8 @@ std::unique_ptr<PlatformViewAndroidJNI::OverlayMetadata> FlutterViewCreateOverlaySurface() override; + void FlutterViewDestroyOverlaySurfaces() override; + std::unique_ptr<std::vector<std::string>> FlutterViewComputePlatformResolvedLocale( std::vector<std::string> supported_locales_data) override;