diff --git a/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java b/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java index 188875d89d5af..aa17536df2db4 100644 --- a/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java +++ b/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java @@ -53,6 +53,7 @@ @Keep @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) class SingleViewPresentation extends Presentation { + private static final String TAG = "PlatformViewsController"; /* * When an embedded view is resized in Flutterverse we move the Android view to a new virtual display @@ -184,10 +185,20 @@ protected void onCreate(Bundle savedInstanceState) { MutableContextWrapper currentContext = (MutableContextWrapper) embeddedView.getContext(); currentContext.setBaseContext(baseContext); } else { - throw new IllegalStateException( - "Unexpected platform view context. " - + "When constructing a platform view in the factory, use the context from PlatformViewFactory#create, view id: " - + viewId); + // In some cases, such as when using LayoutInflator, the original context + // may not be preserved. For backward compatibility with previous + // implementations of Virtual Display, which didn't validate the context, + // continue, but log a warning indicating that some functionality may not + // work as expected. + // See https://github.com/flutter/flutter/issues/110146 for context. + Log.w( + TAG, + "Unexpected platform view context for view ID " + + viewId + + "; some functionality may not work correctly. When constructing a platform view " + + "in the factory, ensure that the view returned from PlatformViewFactory#create " + + "returns the provided context from getContext(). If you are unable to associate " + + "the view with that context, consider using Hybrid Composition instead."); } container.addView(embeddedView); diff --git a/testing/scenario_app/android/BUILD.gn b/testing/scenario_app/android/BUILD.gn index 9e3b401f61882..3be504a6f0fdb 100644 --- a/testing/scenario_app/android/BUILD.gn +++ b/testing/scenario_app/android/BUILD.gn @@ -13,6 +13,7 @@ _android_sources = [ "app/src/androidTest/java/dev/flutter/scenariosui/MemoryLeakTests.java", "app/src/androidTest/java/dev/flutter/scenariosui/PlatformTextureUiTests.java", "app/src/androidTest/java/dev/flutter/scenariosui/PlatformViewUiTests.java", + "app/src/androidTest/java/dev/flutter/scenariosui/PlatformViewWithSurfaceViewBadContextUiTest.java", "app/src/androidTest/java/dev/flutter/scenariosui/PlatformViewWithSurfaceViewUiTest.java", "app/src/androidTest/java/dev/flutter/scenariosui/PlatformViewWithTextureViewUiTest.java", "app/src/androidTest/java/dev/flutter/scenariosui/ScreenshotUtil.java", diff --git a/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/PlatformViewWithSurfaceViewBadContextUiTest.java b/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/PlatformViewWithSurfaceViewBadContextUiTest.java new file mode 100644 index 0000000000000..a036a90a73e02 --- /dev/null +++ b/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/PlatformViewWithSurfaceViewBadContextUiTest.java @@ -0,0 +1,45 @@ +// Copyright 2013 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. + +package dev.flutter.scenariosui; + +import android.content.Intent; +import androidx.annotation.NonNull; +import androidx.test.filters.LargeTest; +import androidx.test.rule.ActivityTestRule; +import androidx.test.runner.AndroidJUnit4; +import dev.flutter.scenarios.PlatformViewsActivity; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class PlatformViewWithSurfaceViewBadContextUiTest { + Intent intent; + + @Rule @NonNull + public ActivityTestRule activityRule = + new ActivityTestRule<>( + PlatformViewsActivity.class, /*initialTouchMode=*/ false, /*launchActivity=*/ false); + + private static String goldName(String suffix) { + return "PlatformViewWithSurfaceViewBadContextUiTest_" + suffix; + } + + @Before + public void setUp() { + intent = new Intent(Intent.ACTION_MAIN); + // Render a texture. + intent.putExtra("use_android_view", false); + intent.putExtra("view_type", PlatformViewsActivity.SURFACE_VIEW_BAD_CONTEXT_PV); + } + + @Test + public void testPlatformView() throws Exception { + intent.putExtra("scenario_name", "platform_view"); + ScreenshotUtil.capture(activityRule.launchActivity(intent), goldName("testPlatformView")); + } +} diff --git a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/PlatformViewsActivity.java b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/PlatformViewsActivity.java index a67be3ab2c76f..80cde94422e10 100644 --- a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/PlatformViewsActivity.java +++ b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/PlatformViewsActivity.java @@ -8,8 +8,12 @@ import io.flutter.embedding.engine.FlutterEngine; public class PlatformViewsActivity extends TestActivity { + // WARNING: These strings must all be exactly the same length to avoid + // breaking the 'create' method's manual encoding in the test. See the + // TODO(stuartmorgan) about encoding alignment in platform_view.dart public static final String TEXT_VIEW_PV = "scenarios/textPlatformView"; public static final String SURFACE_VIEW_PV = "scenarios/surfacePlatformV"; + public static final String SURFACE_VIEW_BAD_CONTEXT_PV = "scenarios/surfaceVBadCntxt"; public static final String TEXTURE_VIEW_PV = "scenarios/texturePlatformV"; @Override @@ -23,7 +27,12 @@ public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { flutterEngine .getPlatformViewsController() .getRegistry() - .registerViewFactory(SURFACE_VIEW_PV, new SurfacePlatformViewFactory()); + .registerViewFactory(SURFACE_VIEW_PV, new SurfacePlatformViewFactory(true)); + + flutterEngine + .getPlatformViewsController() + .getRegistry() + .registerViewFactory(SURFACE_VIEW_BAD_CONTEXT_PV, new SurfacePlatformViewFactory(false)); flutterEngine .getPlatformViewsController() diff --git a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/SurfacePlatformViewFactory.java b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/SurfacePlatformViewFactory.java index bec46167f63c8..399afdf1be6b9 100644 --- a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/SurfacePlatformViewFactory.java +++ b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/SurfacePlatformViewFactory.java @@ -6,6 +6,7 @@ import android.annotation.TargetApi; import android.content.Context; +import android.content.ContextWrapper; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; @@ -24,7 +25,9 @@ @TargetApi(23) public final class SurfacePlatformViewFactory extends PlatformViewFactory { - SurfacePlatformViewFactory() { + private boolean preserveContext; + + SurfacePlatformViewFactory(boolean preserveContext) { super( new MessageCodec() { @Nullable @@ -42,13 +45,19 @@ public Object decodeMessage(@Nullable ByteBuffer byteBuffer) { return StringCodec.INSTANCE.decodeMessage(byteBuffer); } }); + this.preserveContext = preserveContext; } @SuppressWarnings("unchecked") @Override @NonNull public PlatformView create(@NonNull Context context, int id, @Nullable Object args) { - return new SurfacePlatformView(context); + if (preserveContext) { + return new SurfacePlatformView(context); + } else { + final Context differentContext = new ContextWrapper(context); + return new SurfacePlatformView(differentContext); + } } private static class SurfacePlatformView implements PlatformView { diff --git a/testing/scenario_app/lib/src/platform_view.dart b/testing/scenario_app/lib/src/platform_view.dart index 1422029a9a461..95afb82569e40 100644 --- a/testing/scenario_app/lib/src/platform_view.dart +++ b/testing/scenario_app/lib/src/platform_view.dart @@ -1144,6 +1144,11 @@ void addPlatformView( valueString, 'width'.length, ...utf8.encode('width'), + // This is missing the 64-bit boundary alignment, making the entire + // message encoding fragile to changes before this point. Do not add new + // variable-length values such as strings before this point. + // TODO(stuartmorgan): Fix this to use the actual encoding logic, + // including alignment: https://github.com/flutter/flutter/issues/111188 valueFloat64, ..._to64(width), valueString,