From 7bbe571f14447c1f5f58da71f6d62c8dd22bc772 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Wed, 16 Nov 2022 14:06:30 -0500 Subject: [PATCH] Fix resize crash in Android virtual display (flutter/engine#37329) In the Virtual Display codepath for Android platform views, resize completes asynchronously. Currently it is attempting to access the Context in the completion handler, but there is no guarantee that it is still present at that point, leading to possible null pointer crashes. This adds a check for the current state of the Context, and uses a fallback if it's not available. Fixes https://github.com/flutter/flutter/issues/114095 --- .../platform/PlatformViewsController.java | 16 +++++++++--- .../platform/PlatformViewsControllerTest.java | 26 +++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index 87a04f5671..68fc2f1599 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -314,6 +314,7 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega final int viewId = request.viewId; if (usesVirtualDisplay(viewId)) { + final float originalDisplayDensity = getDisplayDensity(); final VirtualDisplayController vdController = vdControllers.get(viewId); // Resizing involved moving the platform view to a new virtual display. Doing so // potentially results in losing an active input connection. To make sure we preserve @@ -325,10 +326,15 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega physicalHeight, () -> { unlockInputConnection(vdController); + // Converting back to logic pixels requires a context, which may no longer be + // available. If that happens, assume the same logic/physical relationship as + // was present when the request arrived. + final float displayDensity = + context == null ? originalDisplayDensity : getDisplayDensity(); onComplete.run( new PlatformViewsChannel.PlatformViewBufferSize( - toLogicalPixels(vdController.getBufferWidth()), - toLogicalPixels(vdController.getBufferHeight()))); + toLogicalPixels(vdController.getBufferWidth(), displayDensity), + toLogicalPixels(vdController.getBufferHeight(), displayDensity))); }); return; } @@ -1002,8 +1008,12 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega return (int) Math.round(logicalPixels * getDisplayDensity()); } + private int toLogicalPixels(double physicalPixels, float displayDensity) { + return (int) Math.round(physicalPixels / displayDensity); + } + private int toLogicalPixels(double physicalPixels) { - return (int) Math.round(physicalPixels / getDisplayDensity()); + return toLogicalPixels(physicalPixels, getDisplayDensity()); } private void diposeAllViews() { diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java index 5dd43edc13..e896ecf0f7 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java @@ -124,6 +124,32 @@ public class PlatformViewsControllerTest { assertEquals(presentation.isShowing(), false); } + @Test + @Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class}) + public void virtualDisplay_handlesResizeResponseWithoutContext() { + final int platformViewId = 0; + FlutterView fakeFlutterView = new FlutterView(ApplicationProvider.getApplicationContext()); + VirtualDisplayController fakeVdController = mock(VirtualDisplayController.class); + PlatformViewsController platformViewsController = new PlatformViewsController(); + platformViewsController.vdControllers.put(platformViewId, fakeVdController); + + platformViewsController.attachToView(fakeFlutterView); + + FlutterJNI jni = new FlutterJNI(); + attach(jni, platformViewsController); + + resize(jni, platformViewsController, platformViewId, 10.0, 20.0); + + ArgumentCaptor resizeCallbackCaptor = ArgumentCaptor.forClass(Runnable.class); + verify(fakeVdController, times(1)).resize(anyInt(), anyInt(), resizeCallbackCaptor.capture()); + + // Simulate a detach call before the resize completes. + platformViewsController.detach(); + + // Trigger the callback to ensure that it doesn't crash. + resizeCallbackCaptor.getValue().run(); + } + @Test public void itUsesActionEventTypeFromFrameworkEventForVirtualDisplays() { MotionEventTracker motionEventTracker = MotionEventTracker.getInstance();