From 37b953439a54f9ef4481dbfb4422b3852384bc89 Mon Sep 17 00:00:00 2001 From: Yaakov Schectman Date: Fri, 18 Oct 2024 15:19:01 -0400 Subject: [PATCH 01/21] Initial commit --- .../embedding/android/FlutterView.java | 41 +++++++------ .../embedding/android/FlutterViewTest.java | 58 +++++++++++++++++++ 2 files changed, 80 insertions(+), 19 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 0d5ae3a8efc5f..0a44f345344bc 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -144,6 +144,8 @@ public class FlutterView extends FrameLayout // Directly implemented View behavior that communicates with Flutter. private final FlutterRenderer.ViewportMetrics viewportMetrics = new FlutterRenderer.ViewportMetrics(); + private final List displayFeatures = new ArrayList<>(); + private final List displayCutouts = new ArrayList<>(); private final AccessibilityBridge.OnAccessibilityChangeListener onAccessibilityChangeListener = new AccessibilityBridge.OnAccessibilityChangeListener() { @@ -538,7 +540,7 @@ protected void onDetachedFromWindow() { @TargetApi(API_LEVELS.API_28) protected void setWindowInfoListenerDisplayFeatures(WindowLayoutInfo layoutInfo) { List displayFeatures = layoutInfo.getDisplayFeatures(); - List result = new ArrayList<>(); + this.displayFeatures.clear(); // Data from WindowInfoTracker display features. Fold and hinge areas are // populated here. @@ -565,31 +567,15 @@ protected void setWindowInfoListenerDisplayFeatures(WindowLayoutInfo layoutInfo) } else { state = DisplayFeatureState.UNKNOWN; } - result.add(new FlutterRenderer.DisplayFeature(displayFeature.getBounds(), type, state)); + this.displayFeatures.add(new FlutterRenderer.DisplayFeature(displayFeature.getBounds(), type, state)); } else { - result.add( + this.displayFeatures.add( new FlutterRenderer.DisplayFeature( displayFeature.getBounds(), DisplayFeatureType.UNKNOWN, DisplayFeatureState.UNKNOWN)); } } - - // Data from the DisplayCutout bounds. Cutouts for cameras and other sensors are - // populated here. DisplayCutout was introduced in API 28. - if (Build.VERSION.SDK_INT >= API_LEVELS.API_28) { - WindowInsets insets = getRootWindowInsets(); - if (insets != null) { - DisplayCutout cutout = insets.getDisplayCutout(); - if (cutout != null) { - for (Rect bounds : cutout.getBoundingRects()) { - Log.v(TAG, "DisplayCutout area reported with bounds = " + bounds.toString()); - result.add(new FlutterRenderer.DisplayFeature(bounds, DisplayFeatureType.CUTOUT)); - } - } - } - } - viewportMetrics.displayFeatures = result; sendViewportMetricsToFlutter(); } @@ -782,6 +768,19 @@ navigationBarVisible && guessBottomKeyboardInset(insets) == 0 viewportMetrics.viewInsetLeft = 0; } + // Data from the DisplayCutout bounds. Cutouts for cameras and other sensors are + // populated here. DisplayCutout was introduced in API 28. + displayCutouts.clear(); + if (Build.VERSION.SDK_INT >= API_LEVELS.API_28) { + DisplayCutout cutout = insets.getDisplayCutout(); + if (cutout != null) { + for (Rect bounds : cutout.getBoundingRects()) { + Log.v(TAG, "DisplayCutout area reported with bounds = " + bounds.toString()); + displayCutouts.add(new FlutterRenderer.DisplayFeature(bounds, DisplayFeatureType.CUTOUT)); + } + } + } + // The caption bar inset is a new addition, and the APIs called to query it utilize a list of // bounding Rects instead of an Insets object, which is a newer API method, as compared to the // existing Insets-based method calls above. @@ -1485,6 +1484,10 @@ private void sendViewportMetricsToFlutter() { viewportMetrics.devicePixelRatio = getResources().getDisplayMetrics().density; viewportMetrics.physicalTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + viewportMetrics.displayFeatures.clear(); + viewportMetrics.displayFeatures.addAll(displayFeatures); + viewportMetrics.displayFeatures.addAll(displayCutouts); + flutterEngine.getRenderer().setViewportMetrics(viewportMetrics); } diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java index dc62638fb2e32..53ad2dc2a2ee6 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -55,6 +55,7 @@ import io.flutter.plugin.platform.PlatformViewsController; import java.lang.reflect.Method; import java.util.Arrays; +import java.util.List; import java.util.concurrent.atomic.AtomicReference; import org.junit.Before; import org.junit.Test; @@ -649,6 +650,63 @@ public void systemInsetDisplayCutoutSimple() { assertEquals(100, viewportMetricsCaptor.getValue().viewInsetTop); } + @SuppressWarnings("deprecation") + @Test + @Config(sdk = 28) + public void onApplyWindowInsetsSetsDisplayCutouts() { + FlutterView flutterView = spy(new FlutterView(ctx)); + assertEquals(0, flutterView.getSystemUiVisibility()); + when(flutterView.getWindowSystemUiVisibility()).thenReturn(0); + when(flutterView.getContext()).thenReturn(ctx); + + FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); + FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); + when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); + + // When we attach a new FlutterView to the engine without any system insets, + // the viewport metrics default to 0. + flutterView.attachToFlutterEngine(flutterEngine); + ArgumentCaptor viewportMetricsCaptor = + ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class); + verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); + assertEquals(0, viewportMetricsCaptor.getValue().viewPaddingTop); + + //Insets insets = Insets.of(100, 100, 100, 100); + //Insets systemGestureInsets = Insets.of(110, 110, 110, 110); + // Then we simulate the system applying a window inset. + WindowInsets windowInsets = mock(WindowInsets.class); + DisplayCutout displayCutout = mock(DisplayCutout.class); + //mockSystemWindowInsets(windowInsets, -1, -1, -1, -1); + //when(windowInsets.getInsets(anyInt())).thenReturn(insets); + //when(windowInsets.getSystemGestureInsets()).thenReturn(systemGestureInsets); + when(windowInsets.getDisplayCutout()).thenReturn(displayCutout); + + //Insets waterfallInsets = Insets.of(200, 0, 200, 0); + //when(displayCutout.getWaterfallInsets()).thenReturn(waterfallInsets); + //when(displayCutout.getSafeInsetTop()).thenReturn(150); + //when(displayCutout.getSafeInsetBottom()).thenReturn(150); + //when(displayCutout.getSafeInsetLeft()).thenReturn(150); + //when(displayCutout.getSafeInsetRight()).thenReturn(150); + List boundingRects = Arrays.asList( + new Rect(0, 200, 300, 400), + new Rect(150, 0, 300, 150) + ); + when(displayCutout.getBoundingRects()).thenReturn(boundingRects); + + flutterView.onApplyWindowInsets(windowInsets); + + verify(flutterRenderer, times(2)).setViewportMetrics(viewportMetricsCaptor.capture()); + //validateViewportMetricPadding(viewportMetricsCaptor, 200, 150, 200, 150); + + //assertEquals(100, viewportMetricsCaptor.getValue().viewInsetTop); + List features = viewportMetricsCaptor.getValue().displayFeatures; + assertEquals(2, features.size()); + for (int i = 0; i < 2; i++) { + assertEquals(FlutterRenderer.DisplayFeatureType.CUTOUT, features.get(i).type); + assertEquals(boundingRects.get(i), features.get(i).bounds); + } + } + @SuppressWarnings("deprecation") // Robolectric.setupActivity // TODO(reidbaker): https://github.com/flutter/flutter/issues/133151 From e91ca733e7d397fd2f246f637bc6a5edbdfb1a71 Mon Sep 17 00:00:00 2001 From: Yaakov Schectman Date: Mon, 21 Oct 2024 13:29:24 -0400 Subject: [PATCH 02/21] Rework method reference --- .../embedding/android/FlutterSurfaceView.java | 1 + .../flutter/embedding/android/FlutterView.java | 16 +++++++--------- .../embedding/android/FlutterViewTest.java | 9 +++++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterSurfaceView.java b/shell/platform/android/io/flutter/embedding/android/FlutterSurfaceView.java index c5c6493494043..a7102a4492318 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterSurfaceView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterSurfaceView.java @@ -224,6 +224,7 @@ public void detachFromRenderer() { setAlpha(0.0f); flutterRenderer.removeIsDisplayingFlutterUiListener(flutterUiDisplayListener); flutterRenderer = null; + throw new RuntimeException("Detaching for some reason in FlutterSurfaceView"); } else { Log.w(TAG, "detachFromRenderer() invoked when no FlutterRenderer was attached."); diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 0a44f345344bc..ab87084ca13ad 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -144,7 +144,7 @@ public class FlutterView extends FrameLayout // Directly implemented View behavior that communicates with Flutter. private final FlutterRenderer.ViewportMetrics viewportMetrics = new FlutterRenderer.ViewportMetrics(); - private final List displayFeatures = new ArrayList<>(); + private List displayFeatures = new ArrayList<>(); private final List displayCutouts = new ArrayList<>(); private final AccessibilityBridge.OnAccessibilityChangeListener onAccessibilityChangeListener = @@ -196,13 +196,7 @@ public void onFlutterUiNoLongerDisplayed() { } }; - private final Consumer windowInfoListener = - new Consumer() { - @Override - public void accept(WindowLayoutInfo layoutInfo) { - setWindowInfoListenerDisplayFeatures(layoutInfo); - } - }; + private Consumer windowInfoListener; /** * Constructs a {@code FlutterView} programmatically, without any XML attributes. @@ -514,6 +508,9 @@ protected void onAttachedToWindow() { this.windowInfoRepo = createWindowInfoRepo(); Activity activity = ViewUtils.getActivity(getContext()); if (windowInfoRepo != null && activity != null) { + // Creating windowInfoListener on-demand instead of at initialization is necessary in order to + // prevent it from capturing the wrong instance of `this` when spying for testing. + windowInfoListener = this::setWindowInfoListenerDisplayFeatures; windowInfoRepo.addWindowLayoutInfoListener( activity, ContextCompat.getMainExecutor(getContext()), windowInfoListener); } @@ -526,9 +523,10 @@ protected void onAttachedToWindow() { */ @Override protected void onDetachedFromWindow() { - if (windowInfoRepo != null) { + if (windowInfoRepo != null && windowInfoListener != null) { windowInfoRepo.removeWindowLayoutInfoListener(windowInfoListener); } + windowInfoListener = null; this.windowInfoRepo = null; super.onDetachedFromWindow(); } diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java index 53ad2dc2a2ee6..4b9e6f0e3ac43 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -55,6 +55,7 @@ import io.flutter.plugin.platform.PlatformViewsController; import java.lang.reflect.Method; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import org.junit.Before; @@ -757,23 +758,23 @@ public void itSendsHingeDisplayFeatureToFlutter() { when(displayFeature.getOcclusionType()).thenReturn(FoldingFeature.OcclusionType.FULL); when(displayFeature.getState()).thenReturn(FoldingFeature.State.FLAT); - WindowLayoutInfo testWindowLayout = new WindowLayoutInfo(Arrays.asList(displayFeature)); + WindowLayoutInfo testWindowLayout = new WindowLayoutInfo(Collections.singletonList(displayFeature)); // When FlutterView is attached to the engine and window, and a hinge display feature exists flutterView.attachToFlutterEngine(flutterEngine); ArgumentCaptor viewportMetricsCaptor = ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class); verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); - assertEquals(Arrays.asList(), viewportMetricsCaptor.getValue().displayFeatures); + assertEquals(Collections.emptyList(), viewportMetricsCaptor.getValue().displayFeatures); flutterView.onAttachedToWindow(); ArgumentCaptor> wmConsumerCaptor = - ArgumentCaptor.forClass((Class) Consumer.class); + ArgumentCaptor.forClass(Consumer.class); verify(windowInfoRepo).addWindowLayoutInfoListener(any(), any(), wmConsumerCaptor.capture()); Consumer wmConsumer = wmConsumerCaptor.getValue(); wmConsumer.accept(testWindowLayout); // Then the Renderer receives the display feature - verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); + verify(flutterRenderer, times(2)).setViewportMetrics(viewportMetricsCaptor.capture()); assertEquals( FlutterRenderer.DisplayFeatureType.HINGE, viewportMetricsCaptor.getValue().displayFeatures.get(0).type); From 7fe4077abcb351ee93313092d2372acd15f0e1de Mon Sep 17 00:00:00 2001 From: Yaakov Schectman Date: Mon, 21 Oct 2024 14:01:44 -0400 Subject: [PATCH 03/21] Dead lines --- .../embedding/android/FlutterSurfaceView.java | 1 - .../flutter/embedding/android/FlutterViewTest.java | 13 ------------- 2 files changed, 14 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterSurfaceView.java b/shell/platform/android/io/flutter/embedding/android/FlutterSurfaceView.java index a7102a4492318..c5c6493494043 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterSurfaceView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterSurfaceView.java @@ -224,7 +224,6 @@ public void detachFromRenderer() { setAlpha(0.0f); flutterRenderer.removeIsDisplayingFlutterUiListener(flutterUiDisplayListener); flutterRenderer = null; - throw new RuntimeException("Detaching for some reason in FlutterSurfaceView"); } else { Log.w(TAG, "detachFromRenderer() invoked when no FlutterRenderer was attached."); diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java index 4b9e6f0e3ac43..c116c8528a098 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -672,22 +672,11 @@ public void onApplyWindowInsetsSetsDisplayCutouts() { verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); assertEquals(0, viewportMetricsCaptor.getValue().viewPaddingTop); - //Insets insets = Insets.of(100, 100, 100, 100); - //Insets systemGestureInsets = Insets.of(110, 110, 110, 110); // Then we simulate the system applying a window inset. WindowInsets windowInsets = mock(WindowInsets.class); DisplayCutout displayCutout = mock(DisplayCutout.class); - //mockSystemWindowInsets(windowInsets, -1, -1, -1, -1); - //when(windowInsets.getInsets(anyInt())).thenReturn(insets); - //when(windowInsets.getSystemGestureInsets()).thenReturn(systemGestureInsets); when(windowInsets.getDisplayCutout()).thenReturn(displayCutout); - //Insets waterfallInsets = Insets.of(200, 0, 200, 0); - //when(displayCutout.getWaterfallInsets()).thenReturn(waterfallInsets); - //when(displayCutout.getSafeInsetTop()).thenReturn(150); - //when(displayCutout.getSafeInsetBottom()).thenReturn(150); - //when(displayCutout.getSafeInsetLeft()).thenReturn(150); - //when(displayCutout.getSafeInsetRight()).thenReturn(150); List boundingRects = Arrays.asList( new Rect(0, 200, 300, 400), new Rect(150, 0, 300, 150) @@ -697,9 +686,7 @@ public void onApplyWindowInsetsSetsDisplayCutouts() { flutterView.onApplyWindowInsets(windowInsets); verify(flutterRenderer, times(2)).setViewportMetrics(viewportMetricsCaptor.capture()); - //validateViewportMetricPadding(viewportMetricsCaptor, 200, 150, 200, 150); - //assertEquals(100, viewportMetricsCaptor.getValue().viewInsetTop); List features = viewportMetricsCaptor.getValue().displayFeatures; assertEquals(2, features.size()); for (int i = 0; i < 2; i++) { From 9f8e7cc962e47836d883f3f14dff9c6564ef3dd8 Mon Sep 17 00:00:00 2001 From: Yaakov Schectman Date: Mon, 21 Oct 2024 14:05:10 -0400 Subject: [PATCH 04/21] Link to Mockito issue --- .../android/io/flutter/embedding/android/FlutterView.java | 1 + 1 file changed, 1 insertion(+) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index ab87084ca13ad..1da11556b0408 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -510,6 +510,7 @@ protected void onAttachedToWindow() { if (windowInfoRepo != null && activity != null) { // Creating windowInfoListener on-demand instead of at initialization is necessary in order to // prevent it from capturing the wrong instance of `this` when spying for testing. + // See https://github.com/mockito/mockito/issues/3479 windowInfoListener = this::setWindowInfoListenerDisplayFeatures; windowInfoRepo.addWindowLayoutInfoListener( activity, ContextCompat.getMainExecutor(getContext()), windowInfoListener); From c90fc86d9a06d18c4454ead3650f590c1a8aa31e Mon Sep 17 00:00:00 2001 From: Yaakov Schectman Date: Mon, 21 Oct 2024 14:10:07 -0400 Subject: [PATCH 05/21] Format --- .../io/flutter/embedding/android/FlutterView.java | 15 ++++++++------- .../embedding/android/FlutterViewTest.java | 12 ++++++------ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 1da11556b0408..e7fa2a7535bb8 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -566,7 +566,8 @@ protected void setWindowInfoListenerDisplayFeatures(WindowLayoutInfo layoutInfo) } else { state = DisplayFeatureState.UNKNOWN; } - this.displayFeatures.add(new FlutterRenderer.DisplayFeature(displayFeature.getBounds(), type, state)); + this.displayFeatures.add( + new FlutterRenderer.DisplayFeature(displayFeature.getBounds(), type, state)); } else { this.displayFeatures.add( new FlutterRenderer.DisplayFeature( @@ -771,13 +772,13 @@ navigationBarVisible && guessBottomKeyboardInset(insets) == 0 // populated here. DisplayCutout was introduced in API 28. displayCutouts.clear(); if (Build.VERSION.SDK_INT >= API_LEVELS.API_28) { - DisplayCutout cutout = insets.getDisplayCutout(); - if (cutout != null) { - for (Rect bounds : cutout.getBoundingRects()) { - Log.v(TAG, "DisplayCutout area reported with bounds = " + bounds.toString()); - displayCutouts.add(new FlutterRenderer.DisplayFeature(bounds, DisplayFeatureType.CUTOUT)); - } + DisplayCutout cutout = insets.getDisplayCutout(); + if (cutout != null) { + for (Rect bounds : cutout.getBoundingRects()) { + Log.v(TAG, "DisplayCutout area reported with bounds = " + bounds.toString()); + displayCutouts.add(new FlutterRenderer.DisplayFeature(bounds, DisplayFeatureType.CUTOUT)); } + } } // The caption bar inset is a new addition, and the APIs called to query it utilize a list of diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java index c116c8528a098..a54431761497d 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -677,17 +677,16 @@ public void onApplyWindowInsetsSetsDisplayCutouts() { DisplayCutout displayCutout = mock(DisplayCutout.class); when(windowInsets.getDisplayCutout()).thenReturn(displayCutout); - List boundingRects = Arrays.asList( - new Rect(0, 200, 300, 400), - new Rect(150, 0, 300, 150) - ); + List boundingRects = + Arrays.asList(new Rect(0, 200, 300, 400), new Rect(150, 0, 300, 150)); when(displayCutout.getBoundingRects()).thenReturn(boundingRects); flutterView.onApplyWindowInsets(windowInsets); verify(flutterRenderer, times(2)).setViewportMetrics(viewportMetricsCaptor.capture()); - List features = viewportMetricsCaptor.getValue().displayFeatures; + List features = + viewportMetricsCaptor.getValue().displayFeatures; assertEquals(2, features.size()); for (int i = 0; i < 2; i++) { assertEquals(FlutterRenderer.DisplayFeatureType.CUTOUT, features.get(i).type); @@ -745,7 +744,8 @@ public void itSendsHingeDisplayFeatureToFlutter() { when(displayFeature.getOcclusionType()).thenReturn(FoldingFeature.OcclusionType.FULL); when(displayFeature.getState()).thenReturn(FoldingFeature.State.FLAT); - WindowLayoutInfo testWindowLayout = new WindowLayoutInfo(Collections.singletonList(displayFeature)); + WindowLayoutInfo testWindowLayout = + new WindowLayoutInfo(Collections.singletonList(displayFeature)); // When FlutterView is attached to the engine and window, and a hinge display feature exists flutterView.attachToFlutterEngine(flutterEngine); From 6ed658c35a617bef945472e256e39b0aca472303 Mon Sep 17 00:00:00 2001 From: Yaakov Schectman Date: Tue, 22 Oct 2024 13:04:13 -0400 Subject: [PATCH 06/21] Use minSdk --- .../embedding/android/FlutterViewTest.java | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java index a54431761497d..5a92bd5d62f90 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -653,7 +653,7 @@ public void systemInsetDisplayCutoutSimple() { @SuppressWarnings("deprecation") @Test - @Config(sdk = 28) + @Config(minSdk = 28) public void onApplyWindowInsetsSetsDisplayCutouts() { FlutterView flutterView = spy(new FlutterView(ctx)); assertEquals(0, flutterView.getSystemUiVisibility()); @@ -681,6 +681,26 @@ public void onApplyWindowInsetsSetsDisplayCutouts() { Arrays.asList(new Rect(0, 200, 300, 400), new Rect(150, 0, 300, 150)); when(displayCutout.getBoundingRects()).thenReturn(boundingRects); + // The following mocked methods are necessary to avoid a NullPointerException when calling + // onApplyWindowInsets, but are irrelevant to the behavior this test concerns. + Insets unusedInsets = Insets.of(100, 100, 100, 100); + // WindowInsets::getSystemGestureInsets was added in API 29, deprecated in API 30. + if (Build.VERSION.SDK_INT == 29) { + when(windowInsets.getSystemGestureInsets()).thenReturn(unusedInsets); + } + // WindowInsets::getInsets was added in API 30. + if (Build.VERSION.SDK_INT >= 30) { + when(windowInsets.getInsets(anyInt())).thenReturn(unusedInsets); + } + // DisplayCutout::getWaterfallInsets was added in API 30. + if (Build.VERSION.SDK_INT >= 30) { + when(displayCutout.getWaterfallInsets()).thenReturn(unusedInsets); + } + when(displayCutout.getSafeInsetTop()).thenReturn(100); + when(displayCutout.getSafeInsetLeft()).thenReturn(100); + when(displayCutout.getSafeInsetBottom()).thenReturn(100); + when(displayCutout.getSafeInsetRight()).thenReturn(100); + flutterView.onApplyWindowInsets(windowInsets); verify(flutterRenderer, times(2)).setViewportMetrics(viewportMetricsCaptor.capture()); From 4b821ba7f66f12060ab116c092415da44404e098 Mon Sep 17 00:00:00 2001 From: Yaakov Schectman Date: Tue, 22 Oct 2024 15:23:52 -0400 Subject: [PATCH 07/21] Clear invocations --- .../test/io/flutter/embedding/android/FlutterViewTest.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java index 5a92bd5d62f90..db14636115f1a 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -701,9 +701,9 @@ public void onApplyWindowInsetsSetsDisplayCutouts() { when(displayCutout.getSafeInsetBottom()).thenReturn(100); when(displayCutout.getSafeInsetRight()).thenReturn(100); + clearInvocations(flutterRenderer); flutterView.onApplyWindowInsets(windowInsets); - - verify(flutterRenderer, times(2)).setViewportMetrics(viewportMetricsCaptor.capture()); + verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); List features = viewportMetricsCaptor.getValue().displayFeatures; @@ -778,10 +778,11 @@ public void itSendsHingeDisplayFeatureToFlutter() { ArgumentCaptor.forClass(Consumer.class); verify(windowInfoRepo).addWindowLayoutInfoListener(any(), any(), wmConsumerCaptor.capture()); Consumer wmConsumer = wmConsumerCaptor.getValue(); + clearInvocations(flutterRenderer); wmConsumer.accept(testWindowLayout); // Then the Renderer receives the display feature - verify(flutterRenderer, times(2)).setViewportMetrics(viewportMetricsCaptor.capture()); + verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); assertEquals( FlutterRenderer.DisplayFeatureType.HINGE, viewportMetricsCaptor.getValue().displayFeatures.get(0).type); From 0feba589add0d155fa0095fd4470b9d03a900b27 Mon Sep 17 00:00:00 2001 From: Yaakov Schectman Date: Tue, 22 Oct 2024 15:24:30 -0400 Subject: [PATCH 08/21] Rename local variable --- .../io/flutter/embedding/android/FlutterView.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index e7fa2a7535bb8..9776cf6d5d895 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -538,12 +538,12 @@ protected void onDetachedFromWindow() { */ @TargetApi(API_LEVELS.API_28) protected void setWindowInfoListenerDisplayFeatures(WindowLayoutInfo layoutInfo) { - List displayFeatures = layoutInfo.getDisplayFeatures(); - this.displayFeatures.clear(); + List newDisplayFeatures = layoutInfo.getDisplayFeatures(); + displayFeatures.clear(); // Data from WindowInfoTracker display features. Fold and hinge areas are // populated here. - for (DisplayFeature displayFeature : displayFeatures) { + for (DisplayFeature displayFeature : newDisplayFeatures) { Log.v( TAG, "WindowInfoTracker Display Feature reported with bounds = " @@ -566,10 +566,10 @@ protected void setWindowInfoListenerDisplayFeatures(WindowLayoutInfo layoutInfo) } else { state = DisplayFeatureState.UNKNOWN; } - this.displayFeatures.add( + displayFeatures.add( new FlutterRenderer.DisplayFeature(displayFeature.getBounds(), type, state)); } else { - this.displayFeatures.add( + displayFeatures.add( new FlutterRenderer.DisplayFeature( displayFeature.getBounds(), DisplayFeatureType.UNKNOWN, From 199d6da2461ede2371b74cdce9d0afd544db04da Mon Sep 17 00:00:00 2001 From: Yaakov Schectman Date: Tue, 22 Oct 2024 15:29:42 -0400 Subject: [PATCH 09/21] Enrich cutout unit test --- .../flutter/embedding/android/FlutterViewTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java index db14636115f1a..643c8a847653b 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -636,17 +636,17 @@ public void systemInsetDisplayCutoutSimple() { when(windowInsets.getSystemGestureInsets()).thenReturn(systemGestureInsets); when(windowInsets.getDisplayCutout()).thenReturn(displayCutout); - Insets waterfallInsets = Insets.of(200, 0, 200, 0); + Insets waterfallInsets = Insets.of(200, 0, 250, 0); when(displayCutout.getWaterfallInsets()).thenReturn(waterfallInsets); - when(displayCutout.getSafeInsetTop()).thenReturn(150); - when(displayCutout.getSafeInsetBottom()).thenReturn(150); - when(displayCutout.getSafeInsetLeft()).thenReturn(150); - when(displayCutout.getSafeInsetRight()).thenReturn(150); + when(displayCutout.getSafeInsetLeft()).thenReturn(110); + when(displayCutout.getSafeInsetTop()).thenReturn(120); + when(displayCutout.getSafeInsetRight()).thenReturn(130); + when(displayCutout.getSafeInsetBottom()).thenReturn(140); flutterView.onApplyWindowInsets(windowInsets); verify(flutterRenderer, times(2)).setViewportMetrics(viewportMetricsCaptor.capture()); - validateViewportMetricPadding(viewportMetricsCaptor, 200, 150, 200, 150); + validateViewportMetricPadding(viewportMetricsCaptor, 200, 120, 250, 140); assertEquals(100, viewportMetricsCaptor.getValue().viewInsetTop); } From 764b169d25a359fea879da1d74c3318c1578a564 Mon Sep 17 00:00:00 2001 From: Yaakov Schectman Date: Tue, 22 Oct 2024 16:05:04 -0400 Subject: [PATCH 10/21] Test features then cutouts --- .../embedding/android/FlutterViewTest.java | 48 ++++++++++++++++--- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java index 643c8a847653b..610744a242494 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -655,10 +655,12 @@ public void systemInsetDisplayCutoutSimple() { @Test @Config(minSdk = 28) public void onApplyWindowInsetsSetsDisplayCutouts() { - FlutterView flutterView = spy(new FlutterView(ctx)); + // Use an Activity context so that FlutterView.onAttachedToWindow completes. + Context context = Robolectric.setupActivity(Activity.class); + FlutterView flutterView = spy(new FlutterView(context)); assertEquals(0, flutterView.getSystemUiVisibility()); when(flutterView.getWindowSystemUiVisibility()).thenReturn(0); - when(flutterView.getContext()).thenReturn(ctx); + when(flutterView.getContext()).thenReturn(context); FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); @@ -672,6 +674,33 @@ public void onApplyWindowInsetsSetsDisplayCutouts() { verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); assertEquals(0, viewportMetricsCaptor.getValue().viewPaddingTop); + // Capture flutterView.setWindowInfoListenerDisplayFeatures. + WindowInfoRepositoryCallbackAdapterWrapper windowInfoRepo = mock(WindowInfoRepositoryCallbackAdapterWrapper.class); + doReturn(windowInfoRepo).when(flutterView).createWindowInfoRepo(); + ArgumentCaptor> consumerCaptor = ArgumentCaptor.forClass(Consumer.class); + flutterView.onAttachedToWindow(); + verify(windowInfoRepo).addWindowLayoutInfoListener(any(), any(), consumerCaptor.capture()); + Consumer consumer = consumerCaptor.getValue(); + + // Set display features in flutterView to ensure they are not overridden by display cutouts. + FoldingFeature displayFeature = mock(FoldingFeature.class); + Rect featureBounds = new Rect(10, 20, 30, 40); + when(displayFeature.getBounds()).thenReturn(featureBounds); + when(displayFeature.getOcclusionType()).thenReturn(FoldingFeature.OcclusionType.FULL); + when(displayFeature.getState()).thenReturn(FoldingFeature.State.FLAT); + WindowLayoutInfo windowLayout = new WindowLayoutInfo(Collections.singletonList(displayFeature)); + clearInvocations(flutterRenderer); + consumer.accept(windowLayout); + + // Assert the display feature is set. + verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); + List features = + viewportMetricsCaptor.getValue().displayFeatures; + assertEquals(1, features.size()); + assertEquals(FlutterRenderer.DisplayFeatureType.HINGE, features.get(0).type); + assertEquals(FlutterRenderer.DisplayFeatureState.POSTURE_FLAT, features.get(0).state); + assertEquals(featureBounds, features.get(0).bounds); + // Then we simulate the system applying a window inset. WindowInsets windowInsets = mock(WindowInsets.class); DisplayCutout displayCutout = mock(DisplayCutout.class); @@ -705,12 +734,19 @@ public void onApplyWindowInsetsSetsDisplayCutouts() { flutterView.onApplyWindowInsets(windowInsets); verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); - List features = + features = viewportMetricsCaptor.getValue().displayFeatures; - assertEquals(2, features.size()); + + // Assert the old display feature is still present. + assertEquals(FlutterRenderer.DisplayFeatureType.HINGE, features.get(0).type); + assertEquals(FlutterRenderer.DisplayFeatureState.POSTURE_FLAT, features.get(0).state); + assertEquals(featureBounds, features.get(0).bounds); + + // Asserts for display cutouts. + assertEquals(3, features.size()); for (int i = 0; i < 2; i++) { - assertEquals(FlutterRenderer.DisplayFeatureType.CUTOUT, features.get(i).type); - assertEquals(boundingRects.get(i), features.get(i).bounds); + assertEquals(FlutterRenderer.DisplayFeatureType.CUTOUT, features.get(i + 1).type); + assertEquals(boundingRects.get(i), features.get(i + 1).bounds); } } From a5dc4606d3a72933e2a3037cb82cf25516b66f1d Mon Sep 17 00:00:00 2001 From: Yaakov Schectman Date: Tue, 22 Oct 2024 16:12:18 -0400 Subject: [PATCH 11/21] Test cutout then feature --- .../embedding/android/FlutterViewTest.java | 51 +++++++++++++++++-- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java index 610744a242494..8b2be780efbb7 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -795,6 +795,47 @@ public void itSendsHingeDisplayFeatureToFlutter() { FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); + // Display features should be empty on attaching to engine. + flutterView.attachToFlutterEngine(flutterEngine); + ArgumentCaptor viewportMetricsCaptor = + ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class); + verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); + assertEquals(Collections.emptyList(), viewportMetricsCaptor.getValue().displayFeatures); + clearInvocations(flutterRenderer); + + // Test that display features do not override cutouts. + WindowInsets windowInsets = mock(WindowInsets.class); + DisplayCutout displayCutout = mock(DisplayCutout.class); + when(windowInsets.getDisplayCutout()).thenReturn(displayCutout); + List boundingRects = + Arrays.asList(new Rect(0, 200, 300, 400)); + when(displayCutout.getBoundingRects()).thenReturn(boundingRects); + // The following mocked methods are necessary to avoid a NullPointerException when calling + // onApplyWindowInsets, but are irrelevant to the behavior this test concerns. + Insets unusedInsets = Insets.of(100, 100, 100, 100); + // WindowInsets::getSystemGestureInsets was added in API 29, deprecated in API 30. + if (Build.VERSION.SDK_INT == 29) { + when(windowInsets.getSystemGestureInsets()).thenReturn(unusedInsets); + } + // WindowInsets::getInsets was added in API 30. + if (Build.VERSION.SDK_INT >= 30) { + when(windowInsets.getInsets(anyInt())).thenReturn(unusedInsets); + } + // DisplayCutout::getWaterfallInsets was added in API 30. + if (Build.VERSION.SDK_INT >= 30) { + when(displayCutout.getWaterfallInsets()).thenReturn(unusedInsets); + } + when(displayCutout.getSafeInsetTop()).thenReturn(100); + when(displayCutout.getSafeInsetLeft()).thenReturn(100); + when(displayCutout.getSafeInsetBottom()).thenReturn(100); + when(displayCutout.getSafeInsetRight()).thenReturn(100); + flutterView.onApplyWindowInsets(windowInsets); + verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); + assertEquals(1, viewportMetricsCaptor.getValue().displayFeatures.size()); + assertEquals(FlutterRenderer.DisplayFeatureType.CUTOUT, viewportMetricsCaptor.getValue().displayFeatures.get(0).type); + assertEquals(boundingRects.get(0), viewportMetricsCaptor.getValue().displayFeatures.get(0).bounds); + clearInvocations(flutterRenderer); + FoldingFeature displayFeature = mock(FoldingFeature.class); when(displayFeature.getBounds()).thenReturn(new Rect(0, 0, 100, 100)); when(displayFeature.getOcclusionType()).thenReturn(FoldingFeature.OcclusionType.FULL); @@ -804,11 +845,6 @@ public void itSendsHingeDisplayFeatureToFlutter() { new WindowLayoutInfo(Collections.singletonList(displayFeature)); // When FlutterView is attached to the engine and window, and a hinge display feature exists - flutterView.attachToFlutterEngine(flutterEngine); - ArgumentCaptor viewportMetricsCaptor = - ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class); - verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); - assertEquals(Collections.emptyList(), viewportMetricsCaptor.getValue().displayFeatures); flutterView.onAttachedToWindow(); ArgumentCaptor> wmConsumerCaptor = ArgumentCaptor.forClass(Consumer.class); @@ -819,6 +855,7 @@ public void itSendsHingeDisplayFeatureToFlutter() { // Then the Renderer receives the display feature verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); + assertEquals(2, viewportMetricsCaptor.getValue().displayFeatures.size()); assertEquals( FlutterRenderer.DisplayFeatureType.HINGE, viewportMetricsCaptor.getValue().displayFeatures.get(0).type); @@ -827,6 +864,10 @@ public void itSendsHingeDisplayFeatureToFlutter() { viewportMetricsCaptor.getValue().displayFeatures.get(0).state); assertEquals( new Rect(0, 0, 100, 100), viewportMetricsCaptor.getValue().displayFeatures.get(0).bounds); + + // Assert the display cutout is unaffected. + assertEquals(FlutterRenderer.DisplayFeatureType.CUTOUT, viewportMetricsCaptor.getValue().displayFeatures.get(1).type); + assertEquals(boundingRects.get(0), viewportMetricsCaptor.getValue().displayFeatures.get(1).bounds); } @Test From bd36a1e6322810876b15e1e3b74e9c22f14ee55b Mon Sep 17 00:00:00 2001 From: Yaakov Schectman Date: Tue, 22 Oct 2024 16:19:35 -0400 Subject: [PATCH 12/21] Format --- .../embedding/android/FlutterViewTest.java | 104 ++++++++---------- 1 file changed, 46 insertions(+), 58 deletions(-) diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java index 8b2be780efbb7..531c9c46d520b 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -675,9 +675,11 @@ public void onApplyWindowInsetsSetsDisplayCutouts() { assertEquals(0, viewportMetricsCaptor.getValue().viewPaddingTop); // Capture flutterView.setWindowInfoListenerDisplayFeatures. - WindowInfoRepositoryCallbackAdapterWrapper windowInfoRepo = mock(WindowInfoRepositoryCallbackAdapterWrapper.class); + WindowInfoRepositoryCallbackAdapterWrapper windowInfoRepo = + mock(WindowInfoRepositoryCallbackAdapterWrapper.class); doReturn(windowInfoRepo).when(flutterView).createWindowInfoRepo(); - ArgumentCaptor> consumerCaptor = ArgumentCaptor.forClass(Consumer.class); + ArgumentCaptor> consumerCaptor = + ArgumentCaptor.forClass(Consumer.class); flutterView.onAttachedToWindow(); verify(windowInfoRepo).addWindowLayoutInfoListener(any(), any(), consumerCaptor.capture()); Consumer consumer = consumerCaptor.getValue(); @@ -702,40 +704,15 @@ public void onApplyWindowInsetsSetsDisplayCutouts() { assertEquals(featureBounds, features.get(0).bounds); // Then we simulate the system applying a window inset. - WindowInsets windowInsets = mock(WindowInsets.class); - DisplayCutout displayCutout = mock(DisplayCutout.class); - when(windowInsets.getDisplayCutout()).thenReturn(displayCutout); - List boundingRects = Arrays.asList(new Rect(0, 200, 300, 400), new Rect(150, 0, 300, 150)); - when(displayCutout.getBoundingRects()).thenReturn(boundingRects); - - // The following mocked methods are necessary to avoid a NullPointerException when calling - // onApplyWindowInsets, but are irrelevant to the behavior this test concerns. - Insets unusedInsets = Insets.of(100, 100, 100, 100); - // WindowInsets::getSystemGestureInsets was added in API 29, deprecated in API 30. - if (Build.VERSION.SDK_INT == 29) { - when(windowInsets.getSystemGestureInsets()).thenReturn(unusedInsets); - } - // WindowInsets::getInsets was added in API 30. - if (Build.VERSION.SDK_INT >= 30) { - when(windowInsets.getInsets(anyInt())).thenReturn(unusedInsets); - } - // DisplayCutout::getWaterfallInsets was added in API 30. - if (Build.VERSION.SDK_INT >= 30) { - when(displayCutout.getWaterfallInsets()).thenReturn(unusedInsets); - } - when(displayCutout.getSafeInsetTop()).thenReturn(100); - when(displayCutout.getSafeInsetLeft()).thenReturn(100); - when(displayCutout.getSafeInsetBottom()).thenReturn(100); - when(displayCutout.getSafeInsetRight()).thenReturn(100); + WindowInsets windowInsets = setupMockDisplayCutout(boundingRects); clearInvocations(flutterRenderer); flutterView.onApplyWindowInsets(windowInsets); verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); - features = - viewportMetricsCaptor.getValue().displayFeatures; + features = viewportMetricsCaptor.getValue().displayFeatures; // Assert the old display feature is still present. assertEquals(FlutterRenderer.DisplayFeatureType.HINGE, features.get(0).type); @@ -804,36 +781,16 @@ public void itSendsHingeDisplayFeatureToFlutter() { clearInvocations(flutterRenderer); // Test that display features do not override cutouts. - WindowInsets windowInsets = mock(WindowInsets.class); - DisplayCutout displayCutout = mock(DisplayCutout.class); - when(windowInsets.getDisplayCutout()).thenReturn(displayCutout); - List boundingRects = - Arrays.asList(new Rect(0, 200, 300, 400)); - when(displayCutout.getBoundingRects()).thenReturn(boundingRects); - // The following mocked methods are necessary to avoid a NullPointerException when calling - // onApplyWindowInsets, but are irrelevant to the behavior this test concerns. - Insets unusedInsets = Insets.of(100, 100, 100, 100); - // WindowInsets::getSystemGestureInsets was added in API 29, deprecated in API 30. - if (Build.VERSION.SDK_INT == 29) { - when(windowInsets.getSystemGestureInsets()).thenReturn(unusedInsets); - } - // WindowInsets::getInsets was added in API 30. - if (Build.VERSION.SDK_INT >= 30) { - when(windowInsets.getInsets(anyInt())).thenReturn(unusedInsets); - } - // DisplayCutout::getWaterfallInsets was added in API 30. - if (Build.VERSION.SDK_INT >= 30) { - when(displayCutout.getWaterfallInsets()).thenReturn(unusedInsets); - } - when(displayCutout.getSafeInsetTop()).thenReturn(100); - when(displayCutout.getSafeInsetLeft()).thenReturn(100); - when(displayCutout.getSafeInsetBottom()).thenReturn(100); - when(displayCutout.getSafeInsetRight()).thenReturn(100); + List boundingRects = Arrays.asList(new Rect(0, 200, 300, 400)); + WindowInsets windowInsets = setupMockDisplayCutout(boundingRects); flutterView.onApplyWindowInsets(windowInsets); verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); assertEquals(1, viewportMetricsCaptor.getValue().displayFeatures.size()); - assertEquals(FlutterRenderer.DisplayFeatureType.CUTOUT, viewportMetricsCaptor.getValue().displayFeatures.get(0).type); - assertEquals(boundingRects.get(0), viewportMetricsCaptor.getValue().displayFeatures.get(0).bounds); + assertEquals( + FlutterRenderer.DisplayFeatureType.CUTOUT, + viewportMetricsCaptor.getValue().displayFeatures.get(0).type); + assertEquals( + boundingRects.get(0), viewportMetricsCaptor.getValue().displayFeatures.get(0).bounds); clearInvocations(flutterRenderer); FoldingFeature displayFeature = mock(FoldingFeature.class); @@ -866,8 +823,11 @@ public void itSendsHingeDisplayFeatureToFlutter() { new Rect(0, 0, 100, 100), viewportMetricsCaptor.getValue().displayFeatures.get(0).bounds); // Assert the display cutout is unaffected. - assertEquals(FlutterRenderer.DisplayFeatureType.CUTOUT, viewportMetricsCaptor.getValue().displayFeatures.get(1).type); - assertEquals(boundingRects.get(0), viewportMetricsCaptor.getValue().displayFeatures.get(1).bounds); + assertEquals( + FlutterRenderer.DisplayFeatureType.CUTOUT, + viewportMetricsCaptor.getValue().displayFeatures.get(1).type); + assertEquals( + boundingRects.get(0), viewportMetricsCaptor.getValue().displayFeatures.get(1).bounds); } @Test @@ -1317,6 +1277,34 @@ private void mockSystemGestureInsetsIfNeed(WindowInsets windowInsets) { } } + @SuppressWarnings("deprecation") + private WindowInsets setupMockDisplayCutout(List boundingRects) { + WindowInsets windowInsets = mock(WindowInsets.class); + DisplayCutout displayCutout = mock(DisplayCutout.class); + when(windowInsets.getDisplayCutout()).thenReturn(displayCutout); + when(displayCutout.getBoundingRects()).thenReturn(boundingRects); + // The following mocked methods are necessary to avoid a NullPointerException when calling + // onApplyWindowInsets, but are irrelevant to the behavior this test concerns. + Insets unusedInsets = Insets.of(100, 100, 100, 100); + // WindowInsets::getSystemGestureInsets was added in API 29, deprecated in API 30. + if (Build.VERSION.SDK_INT == 29) { + when(windowInsets.getSystemGestureInsets()).thenReturn(unusedInsets); + } + // WindowInsets::getInsets was added in API 30. + if (Build.VERSION.SDK_INT >= 30) { + when(windowInsets.getInsets(anyInt())).thenReturn(unusedInsets); + } + // DisplayCutout::getWaterfallInsets was added in API 30. + if (Build.VERSION.SDK_INT >= 30) { + when(displayCutout.getWaterfallInsets()).thenReturn(unusedInsets); + } + when(displayCutout.getSafeInsetTop()).thenReturn(100); + when(displayCutout.getSafeInsetLeft()).thenReturn(100); + when(displayCutout.getSafeInsetBottom()).thenReturn(100); + when(displayCutout.getSafeInsetRight()).thenReturn(100); + return windowInsets; + } + /* * A custom shadow that reports fullscreen flag for system UI visibility */ From 1d6fde54f21db173af10eafd40d129d0f4bbee35 Mon Sep 17 00:00:00 2001 From: Yaakov Schectman Date: Fri, 25 Oct 2024 16:47:54 -0400 Subject: [PATCH 13/21] Separate features, cutouts lists --- .../embedding/android/FlutterView.java | 16 +++++------ .../engine/renderer/FlutterRenderer.java | 22 +++++++++++++-- .../embedding/android/FlutterViewTest.java | 28 +++++++++++++------ .../engine/renderer/FlutterRendererTest.java | 2 +- 4 files changed, 47 insertions(+), 21 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 55d4a89bd5413..85cdd70e44dcd 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -541,7 +541,7 @@ protected void onDetachedFromWindow() { @TargetApi(API_LEVELS.API_28) protected void setWindowInfoListenerDisplayFeatures(WindowLayoutInfo layoutInfo) { List newDisplayFeatures = layoutInfo.getDisplayFeatures(); - displayFeatures.clear(); + viewportMetrics.displayFeatures.clear(); // Data from WindowInfoTracker display features. Fold and hinge areas are // populated here. @@ -568,10 +568,10 @@ protected void setWindowInfoListenerDisplayFeatures(WindowLayoutInfo layoutInfo) } else { state = DisplayFeatureState.UNKNOWN; } - displayFeatures.add( + viewportMetrics.displayFeatures.add( new FlutterRenderer.DisplayFeature(displayFeature.getBounds(), type, state)); } else { - displayFeatures.add( + viewportMetrics.displayFeatures.add( new FlutterRenderer.DisplayFeature( displayFeature.getBounds(), DisplayFeatureType.UNKNOWN, @@ -772,13 +772,13 @@ navigationBarVisible && guessBottomKeyboardInset(insets) == 0 // Data from the DisplayCutout bounds. Cutouts for cameras and other sensors are // populated here. DisplayCutout was introduced in API 28. - displayCutouts.clear(); + viewportMetrics.displayCutouts.clear(); if (Build.VERSION.SDK_INT >= API_LEVELS.API_28) { DisplayCutout cutout = insets.getDisplayCutout(); if (cutout != null) { for (Rect bounds : cutout.getBoundingRects()) { Log.v(TAG, "DisplayCutout area reported with bounds = " + bounds.toString()); - displayCutouts.add(new FlutterRenderer.DisplayFeature(bounds, DisplayFeatureType.CUTOUT)); + viewportMetrics.displayCutouts.add(new FlutterRenderer.DisplayFeature(bounds, DisplayFeatureType.CUTOUT)); } } } @@ -1492,9 +1492,9 @@ private void sendViewportMetricsToFlutter() { viewportMetrics.devicePixelRatio = getResources().getDisplayMetrics().density; viewportMetrics.physicalTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); - viewportMetrics.displayFeatures.clear(); - viewportMetrics.displayFeatures.addAll(displayFeatures); - viewportMetrics.displayFeatures.addAll(displayCutouts); + //viewportMetrics.displayFeatures.clear(); + //viewportMetrics.displayFeatures.addAll(displayFeatures); + //viewportMetrics.displayFeatures.addAll(displayCutouts); flutterEngine.getRenderer().setViewportMetrics(viewportMetrics); } diff --git a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java index 54434c27d0594..04ee700c66bd2 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -1209,9 +1209,10 @@ public void setViewportMetrics(@NonNull ViewportMetrics viewportMetrics) { + "Display Features: " + viewportMetrics.displayFeatures.size()); - int[] displayFeaturesBounds = new int[viewportMetrics.displayFeatures.size() * 4]; - int[] displayFeaturesType = new int[viewportMetrics.displayFeatures.size()]; - int[] displayFeaturesState = new int[viewportMetrics.displayFeatures.size()]; + int totalFeaturesAndCutouts = viewportMetrics.displayFeatures.size() + viewportMetrics.displayCutouts.size(); + int[] displayFeaturesBounds = new int[totalFeaturesAndCutouts * 4]; + int[] displayFeaturesType = new int[totalFeaturesAndCutouts]; + int[] displayFeaturesState = new int[totalFeaturesAndCutouts]; for (int i = 0; i < viewportMetrics.displayFeatures.size(); i++) { DisplayFeature displayFeature = viewportMetrics.displayFeatures.get(i); displayFeaturesBounds[4 * i] = displayFeature.bounds.left; @@ -1221,6 +1222,17 @@ public void setViewportMetrics(@NonNull ViewportMetrics viewportMetrics) { displayFeaturesType[i] = displayFeature.type.encodedValue; displayFeaturesState[i] = displayFeature.state.encodedValue; } + int cutoutOffset = viewportMetrics.displayFeatures.size() * 4; + for (int i = 0; i < viewportMetrics.displayCutouts.size(); i++) { + DisplayFeature displayCutout = viewportMetrics.displayCutouts.get(i); + displayFeaturesBounds[cutoutOffset + 4 * i] = displayCutout.bounds.left; + displayFeaturesBounds[cutoutOffset + 4 * i + 1] = displayCutout.bounds.top; + displayFeaturesBounds[cutoutOffset + 4 * i + 2] = displayCutout.bounds.right; + displayFeaturesBounds[cutoutOffset + 4 * i + 3] = displayCutout.bounds.bottom; + // Display cutouts always have type CUTOUT and state UNKNOWN. + displayFeaturesType[viewportMetrics.displayFeatures.size() + i] = DisplayFeatureType.CUTOUT.encodedValue; + displayFeaturesState[viewportMetrics.displayFeatures.size() + i] = DisplayFeatureState.UNKNOWN.encodedValue; + } flutterJNI.setViewportMetrics( viewportMetrics.devicePixelRatio, @@ -1335,7 +1347,11 @@ boolean validate() { return width > 0 && height > 0 && devicePixelRatio > 0; } + // Features public List displayFeatures = new ArrayList<>(); + + // Specifically display cutouts. + public List displayCutouts = new ArrayList<>(); } /** diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java index 531c9c46d520b..f74a155834960 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -715,15 +715,18 @@ public void onApplyWindowInsetsSetsDisplayCutouts() { features = viewportMetricsCaptor.getValue().displayFeatures; // Assert the old display feature is still present. + assertEquals(1, features.size()); assertEquals(FlutterRenderer.DisplayFeatureType.HINGE, features.get(0).type); assertEquals(FlutterRenderer.DisplayFeatureState.POSTURE_FLAT, features.get(0).state); assertEquals(featureBounds, features.get(0).bounds); + features = viewportMetricsCaptor.getValue().displayCutouts; // Asserts for display cutouts. - assertEquals(3, features.size()); + assertEquals(2, features.size()); for (int i = 0; i < 2; i++) { - assertEquals(FlutterRenderer.DisplayFeatureType.CUTOUT, features.get(i + 1).type); - assertEquals(boundingRects.get(i), features.get(i + 1).bounds); + assertEquals(FlutterRenderer.DisplayFeatureType.CUTOUT, features.get(i).type); + assertEquals(FlutterRenderer.DisplayFeatureState.UNKNOWN, features.get(i).state); + assertEquals(boundingRects.get(i), features.get(i).bounds); } } @@ -785,12 +788,15 @@ public void itSendsHingeDisplayFeatureToFlutter() { WindowInsets windowInsets = setupMockDisplayCutout(boundingRects); flutterView.onApplyWindowInsets(windowInsets); verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); - assertEquals(1, viewportMetricsCaptor.getValue().displayFeatures.size()); + assertEquals(1, viewportMetricsCaptor.getValue().displayCutouts.size()); assertEquals( FlutterRenderer.DisplayFeatureType.CUTOUT, - viewportMetricsCaptor.getValue().displayFeatures.get(0).type); + viewportMetricsCaptor.getValue().displayCutouts.get(0).type); assertEquals( - boundingRects.get(0), viewportMetricsCaptor.getValue().displayFeatures.get(0).bounds); + FlutterRenderer.DisplayFeatureState.UNKNOWN, + viewportMetricsCaptor.getValue().displayCutouts.get(0).state); + assertEquals( + boundingRects.get(0), viewportMetricsCaptor.getValue().displayCutouts.get(0).bounds); clearInvocations(flutterRenderer); FoldingFeature displayFeature = mock(FoldingFeature.class); @@ -812,7 +818,7 @@ public void itSendsHingeDisplayFeatureToFlutter() { // Then the Renderer receives the display feature verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); - assertEquals(2, viewportMetricsCaptor.getValue().displayFeatures.size()); + assertEquals(1, viewportMetricsCaptor.getValue().displayFeatures.size()); assertEquals( FlutterRenderer.DisplayFeatureType.HINGE, viewportMetricsCaptor.getValue().displayFeatures.get(0).type); @@ -823,11 +829,15 @@ public void itSendsHingeDisplayFeatureToFlutter() { new Rect(0, 0, 100, 100), viewportMetricsCaptor.getValue().displayFeatures.get(0).bounds); // Assert the display cutout is unaffected. + assertEquals(1, viewportMetricsCaptor.getValue().displayCutouts.size()); assertEquals( FlutterRenderer.DisplayFeatureType.CUTOUT, - viewportMetricsCaptor.getValue().displayFeatures.get(1).type); + viewportMetricsCaptor.getValue().displayCutouts.get(0).type); + assertEquals( + FlutterRenderer.DisplayFeatureState.UNKNOWN, + viewportMetricsCaptor.getValue().displayCutouts.get(0).state); assertEquals( - boundingRects.get(0), viewportMetricsCaptor.getValue().displayFeatures.get(1).bounds); + boundingRects.get(0), viewportMetricsCaptor.getValue().displayCutouts.get(0).bounds); } @Test diff --git a/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java b/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java index eaada1ef26fee..1f8e3c8b79716 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java @@ -293,7 +293,7 @@ public void itConvertsDisplayFeatureArrayToPrimitiveArrays() { new Rect(10, 20, 30, 40), FlutterRenderer.DisplayFeatureType.FOLD, FlutterRenderer.DisplayFeatureState.POSTURE_HALF_OPENED)); - metrics.displayFeatures.add( + metrics.displayCutouts.add( new FlutterRenderer.DisplayFeature( new Rect(50, 60, 70, 80), FlutterRenderer.DisplayFeatureType.CUTOUT)); From 078e796b411e12e9cd2c6ca1712f69f22d7e306b Mon Sep 17 00:00:00 2001 From: Yaakov Schectman Date: Fri, 25 Oct 2024 17:01:53 -0400 Subject: [PATCH 14/21] Define DisplayCutout --- .../embedding/android/FlutterView.java | 2 +- .../engine/renderer/FlutterRenderer.java | 17 +++++--- .../embedding/android/FlutterViewTest.java | 43 +++++++------------ .../engine/renderer/FlutterRendererTest.java | 4 +- 4 files changed, 31 insertions(+), 35 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 85cdd70e44dcd..8a930015193fe 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -778,7 +778,7 @@ navigationBarVisible && guessBottomKeyboardInset(insets) == 0 if (cutout != null) { for (Rect bounds : cutout.getBoundingRects()) { Log.v(TAG, "DisplayCutout area reported with bounds = " + bounds.toString()); - viewportMetrics.displayCutouts.add(new FlutterRenderer.DisplayFeature(bounds, DisplayFeatureType.CUTOUT)); + viewportMetrics.displayCutouts.add(new FlutterRenderer.DisplayCutout(bounds)); } } } diff --git a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java index 04ee700c66bd2..52d3c671cbad1 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -1224,7 +1224,7 @@ public void setViewportMetrics(@NonNull ViewportMetrics viewportMetrics) { } int cutoutOffset = viewportMetrics.displayFeatures.size() * 4; for (int i = 0; i < viewportMetrics.displayCutouts.size(); i++) { - DisplayFeature displayCutout = viewportMetrics.displayCutouts.get(i); + DisplayCutout displayCutout = viewportMetrics.displayCutouts.get(i); displayFeaturesBounds[cutoutOffset + 4 * i] = displayCutout.bounds.left; displayFeaturesBounds[cutoutOffset + 4 * i + 1] = displayCutout.bounds.top; displayFeaturesBounds[cutoutOffset + 4 * i + 2] = displayCutout.bounds.right; @@ -1351,7 +1351,7 @@ boolean validate() { public List displayFeatures = new ArrayList<>(); // Specifically display cutouts. - public List displayCutouts = new ArrayList<>(); + public List displayCutouts = new ArrayList<>(); } /** @@ -1374,11 +1374,18 @@ public DisplayFeature(Rect bounds, DisplayFeatureType type, DisplayFeatureState this.type = type; this.state = state; } + } - public DisplayFeature(Rect bounds, DisplayFeatureType type) { + /** + * Description of a cutout on the physical display. + * + *

A simplified analog to {@link DisplayFeature} that specifically handles display cutouts. + */ + public static final class DisplayCutout { + public final Rect bounds; + + public DisplayCutout(Rect bounds) { this.bounds = bounds; - this.type = type; - this.state = DisplayFeatureState.UNKNOWN; } } diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java index f74a155834960..323f34e28b752 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -704,9 +704,9 @@ public void onApplyWindowInsetsSetsDisplayCutouts() { assertEquals(featureBounds, features.get(0).bounds); // Then we simulate the system applying a window inset. - List boundingRects = + List cutoutBoundingRects = Arrays.asList(new Rect(0, 200, 300, 400), new Rect(150, 0, 300, 150)); - WindowInsets windowInsets = setupMockDisplayCutout(boundingRects); + WindowInsets windowInsets = setupMockDisplayCutout(cutoutBoundingRects); clearInvocations(flutterRenderer); flutterView.onApplyWindowInsets(windowInsets); @@ -720,13 +720,11 @@ public void onApplyWindowInsetsSetsDisplayCutouts() { assertEquals(FlutterRenderer.DisplayFeatureState.POSTURE_FLAT, features.get(0).state); assertEquals(featureBounds, features.get(0).bounds); - features = viewportMetricsCaptor.getValue().displayCutouts; + List cutouts = viewportMetricsCaptor.getValue().displayCutouts; // Asserts for display cutouts. - assertEquals(2, features.size()); + assertEquals(2, cutouts.size()); for (int i = 0; i < 2; i++) { - assertEquals(FlutterRenderer.DisplayFeatureType.CUTOUT, features.get(i).type); - assertEquals(FlutterRenderer.DisplayFeatureState.UNKNOWN, features.get(i).state); - assertEquals(boundingRects.get(i), features.get(i).bounds); + assertEquals(cutoutBoundingRects.get(i), cutouts.get(i).bounds); } } @@ -784,23 +782,18 @@ public void itSendsHingeDisplayFeatureToFlutter() { clearInvocations(flutterRenderer); // Test that display features do not override cutouts. - List boundingRects = Arrays.asList(new Rect(0, 200, 300, 400)); - WindowInsets windowInsets = setupMockDisplayCutout(boundingRects); + List cutoutBoundingRects = Collections.singletonList(new Rect(0, 200, 300, 400)); + WindowInsets windowInsets = setupMockDisplayCutout(cutoutBoundingRects); flutterView.onApplyWindowInsets(windowInsets); verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); assertEquals(1, viewportMetricsCaptor.getValue().displayCutouts.size()); assertEquals( - FlutterRenderer.DisplayFeatureType.CUTOUT, - viewportMetricsCaptor.getValue().displayCutouts.get(0).type); - assertEquals( - FlutterRenderer.DisplayFeatureState.UNKNOWN, - viewportMetricsCaptor.getValue().displayCutouts.get(0).state); - assertEquals( - boundingRects.get(0), viewportMetricsCaptor.getValue().displayCutouts.get(0).bounds); + cutoutBoundingRects.get(0), viewportMetricsCaptor.getValue().displayCutouts.get(0).bounds); clearInvocations(flutterRenderer); FoldingFeature displayFeature = mock(FoldingFeature.class); - when(displayFeature.getBounds()).thenReturn(new Rect(0, 0, 100, 100)); + Rect featureRect = new Rect(0, 0, 100, 100); + when(displayFeature.getBounds()).thenReturn(featureRect); when(displayFeature.getOcclusionType()).thenReturn(FoldingFeature.OcclusionType.FULL); when(displayFeature.getState()).thenReturn(FoldingFeature.State.FLAT); @@ -819,25 +812,21 @@ public void itSendsHingeDisplayFeatureToFlutter() { // Then the Renderer receives the display feature verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); assertEquals(1, viewportMetricsCaptor.getValue().displayFeatures.size()); + FlutterRenderer.DisplayFeature feature = viewportMetricsCaptor.getValue().displayFeatures.get(0); assertEquals( FlutterRenderer.DisplayFeatureType.HINGE, - viewportMetricsCaptor.getValue().displayFeatures.get(0).type); + feature.type); assertEquals( FlutterRenderer.DisplayFeatureState.POSTURE_FLAT, - viewportMetricsCaptor.getValue().displayFeatures.get(0).state); + feature.state); assertEquals( - new Rect(0, 0, 100, 100), viewportMetricsCaptor.getValue().displayFeatures.get(0).bounds); + featureRect, feature.bounds); // Assert the display cutout is unaffected. assertEquals(1, viewportMetricsCaptor.getValue().displayCutouts.size()); + FlutterRenderer.DisplayCutout cutout = viewportMetricsCaptor.getValue().displayCutouts.get(0); assertEquals( - FlutterRenderer.DisplayFeatureType.CUTOUT, - viewportMetricsCaptor.getValue().displayCutouts.get(0).type); - assertEquals( - FlutterRenderer.DisplayFeatureState.UNKNOWN, - viewportMetricsCaptor.getValue().displayCutouts.get(0).state); - assertEquals( - boundingRects.get(0), viewportMetricsCaptor.getValue().displayCutouts.get(0).bounds); + cutoutBoundingRects.get(0), cutout.bounds); } @Test diff --git a/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java b/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java index 1f8e3c8b79716..46e1790e99629 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java @@ -294,8 +294,8 @@ public void itConvertsDisplayFeatureArrayToPrimitiveArrays() { FlutterRenderer.DisplayFeatureType.FOLD, FlutterRenderer.DisplayFeatureState.POSTURE_HALF_OPENED)); metrics.displayCutouts.add( - new FlutterRenderer.DisplayFeature( - new Rect(50, 60, 70, 80), FlutterRenderer.DisplayFeatureType.CUTOUT)); + new FlutterRenderer.DisplayCutout( + new Rect(50, 60, 70, 80))); // Execute the behavior under test. flutterRenderer.setViewportMetrics(metrics); From 2829a1ad918bd80619f5f3a81ecfaf65ed058d64 Mon Sep 17 00:00:00 2001 From: Yaakov Schectman Date: Fri, 25 Oct 2024 17:02:08 -0400 Subject: [PATCH 15/21] Format --- .../flutter/embedding/android/FlutterView.java | 6 +++--- .../engine/renderer/FlutterRenderer.java | 9 ++++++--- .../embedding/android/FlutterViewTest.java | 17 ++++++----------- .../engine/renderer/FlutterRendererTest.java | 4 +--- 4 files changed, 16 insertions(+), 20 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 8a930015193fe..648df67630b9c 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -1492,9 +1492,9 @@ private void sendViewportMetricsToFlutter() { viewportMetrics.devicePixelRatio = getResources().getDisplayMetrics().density; viewportMetrics.physicalTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); - //viewportMetrics.displayFeatures.clear(); - //viewportMetrics.displayFeatures.addAll(displayFeatures); - //viewportMetrics.displayFeatures.addAll(displayCutouts); + // viewportMetrics.displayFeatures.clear(); + // viewportMetrics.displayFeatures.addAll(displayFeatures); + // viewportMetrics.displayFeatures.addAll(displayCutouts); flutterEngine.getRenderer().setViewportMetrics(viewportMetrics); } diff --git a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java index 52d3c671cbad1..8cbfacbfcd456 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -1209,7 +1209,8 @@ public void setViewportMetrics(@NonNull ViewportMetrics viewportMetrics) { + "Display Features: " + viewportMetrics.displayFeatures.size()); - int totalFeaturesAndCutouts = viewportMetrics.displayFeatures.size() + viewportMetrics.displayCutouts.size(); + int totalFeaturesAndCutouts = + viewportMetrics.displayFeatures.size() + viewportMetrics.displayCutouts.size(); int[] displayFeaturesBounds = new int[totalFeaturesAndCutouts * 4]; int[] displayFeaturesType = new int[totalFeaturesAndCutouts]; int[] displayFeaturesState = new int[totalFeaturesAndCutouts]; @@ -1230,8 +1231,10 @@ public void setViewportMetrics(@NonNull ViewportMetrics viewportMetrics) { displayFeaturesBounds[cutoutOffset + 4 * i + 2] = displayCutout.bounds.right; displayFeaturesBounds[cutoutOffset + 4 * i + 3] = displayCutout.bounds.bottom; // Display cutouts always have type CUTOUT and state UNKNOWN. - displayFeaturesType[viewportMetrics.displayFeatures.size() + i] = DisplayFeatureType.CUTOUT.encodedValue; - displayFeaturesState[viewportMetrics.displayFeatures.size() + i] = DisplayFeatureState.UNKNOWN.encodedValue; + displayFeaturesType[viewportMetrics.displayFeatures.size() + i] = + DisplayFeatureType.CUTOUT.encodedValue; + displayFeaturesState[viewportMetrics.displayFeatures.size() + i] = + DisplayFeatureState.UNKNOWN.encodedValue; } flutterJNI.setViewportMetrics( diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java index 323f34e28b752..2da3796aae5c9 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -812,21 +812,16 @@ public void itSendsHingeDisplayFeatureToFlutter() { // Then the Renderer receives the display feature verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); assertEquals(1, viewportMetricsCaptor.getValue().displayFeatures.size()); - FlutterRenderer.DisplayFeature feature = viewportMetricsCaptor.getValue().displayFeatures.get(0); - assertEquals( - FlutterRenderer.DisplayFeatureType.HINGE, - feature.type); - assertEquals( - FlutterRenderer.DisplayFeatureState.POSTURE_FLAT, - feature.state); - assertEquals( - featureRect, feature.bounds); + FlutterRenderer.DisplayFeature feature = + viewportMetricsCaptor.getValue().displayFeatures.get(0); + assertEquals(FlutterRenderer.DisplayFeatureType.HINGE, feature.type); + assertEquals(FlutterRenderer.DisplayFeatureState.POSTURE_FLAT, feature.state); + assertEquals(featureRect, feature.bounds); // Assert the display cutout is unaffected. assertEquals(1, viewportMetricsCaptor.getValue().displayCutouts.size()); FlutterRenderer.DisplayCutout cutout = viewportMetricsCaptor.getValue().displayCutouts.get(0); - assertEquals( - cutoutBoundingRects.get(0), cutout.bounds); + assertEquals(cutoutBoundingRects.get(0), cutout.bounds); } @Test diff --git a/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java b/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java index 46e1790e99629..30f4357cdbb7e 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java @@ -293,9 +293,7 @@ public void itConvertsDisplayFeatureArrayToPrimitiveArrays() { new Rect(10, 20, 30, 40), FlutterRenderer.DisplayFeatureType.FOLD, FlutterRenderer.DisplayFeatureState.POSTURE_HALF_OPENED)); - metrics.displayCutouts.add( - new FlutterRenderer.DisplayCutout( - new Rect(50, 60, 70, 80))); + metrics.displayCutouts.add(new FlutterRenderer.DisplayCutout(new Rect(50, 60, 70, 80))); // Execute the behavior under test. flutterRenderer.setViewportMetrics(metrics); From e4c26bbce74453eb0c6de3002c03bd36ed18d643 Mon Sep 17 00:00:00 2001 From: Yaakov Schectman Date: Fri, 25 Oct 2024 17:05:44 -0400 Subject: [PATCH 16/21] Remove dead code --- .../android/io/flutter/embedding/android/FlutterView.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 648df67630b9c..bc33f60093d54 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -1492,10 +1492,6 @@ private void sendViewportMetricsToFlutter() { viewportMetrics.devicePixelRatio = getResources().getDisplayMetrics().density; viewportMetrics.physicalTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); - // viewportMetrics.displayFeatures.clear(); - // viewportMetrics.displayFeatures.addAll(displayFeatures); - // viewportMetrics.displayFeatures.addAll(displayCutouts); - flutterEngine.getRenderer().setViewportMetrics(viewportMetrics); } From 0578caedad397fabc4bb8118a5253eb57bf96f94 Mon Sep 17 00:00:00 2001 From: Yaakov Schectman Date: Mon, 28 Oct 2024 16:04:34 -0400 Subject: [PATCH 17/21] PR Feedback --- .../engine/renderer/FlutterRenderer.java | 17 +++++++++-------- .../embedding/android/FlutterViewTest.java | 2 ++ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java index 8cbfacbfcd456..a3bc76b573fe0 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -1157,6 +1157,13 @@ public void stopRenderingToSurface() { } } + private void translateFeatureBounds(int[] displayFeatureBounds, int offset, Rect bounds) { + displayFeatureBounds[offset] = bounds.left; + displayFeatureBounds[offset + 1] = bounds.top; + displayFeatureBounds[offset + 2] = bounds.right; + displayFeatureBounds[offset + 3] = bounds.bottom; + } + /** * Notifies Flutter that the viewport metrics, e.g. window height and width, have changed. * @@ -1216,20 +1223,14 @@ public void setViewportMetrics(@NonNull ViewportMetrics viewportMetrics) { int[] displayFeaturesState = new int[totalFeaturesAndCutouts]; for (int i = 0; i < viewportMetrics.displayFeatures.size(); i++) { DisplayFeature displayFeature = viewportMetrics.displayFeatures.get(i); - displayFeaturesBounds[4 * i] = displayFeature.bounds.left; - displayFeaturesBounds[4 * i + 1] = displayFeature.bounds.top; - displayFeaturesBounds[4 * i + 2] = displayFeature.bounds.right; - displayFeaturesBounds[4 * i + 3] = displayFeature.bounds.bottom; + translateFeatureBounds(displayFeaturesBounds, 4 * i, displayFeature.bounds); displayFeaturesType[i] = displayFeature.type.encodedValue; displayFeaturesState[i] = displayFeature.state.encodedValue; } int cutoutOffset = viewportMetrics.displayFeatures.size() * 4; for (int i = 0; i < viewportMetrics.displayCutouts.size(); i++) { DisplayCutout displayCutout = viewportMetrics.displayCutouts.get(i); - displayFeaturesBounds[cutoutOffset + 4 * i] = displayCutout.bounds.left; - displayFeaturesBounds[cutoutOffset + 4 * i + 1] = displayCutout.bounds.top; - displayFeaturesBounds[cutoutOffset + 4 * i + 2] = displayCutout.bounds.right; - displayFeaturesBounds[cutoutOffset + 4 * i + 3] = displayCutout.bounds.bottom; + translateFeatureBounds(displayFeaturesBounds, cutoutOffset + 4 * i, displayCutout.bounds); // Display cutouts always have type CUTOUT and state UNKNOWN. displayFeaturesType[viewportMetrics.displayFeatures.size() + i] = DisplayFeatureType.CUTOUT.encodedValue; diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java index 2da3796aae5c9..57b809d997430 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -646,6 +646,8 @@ public void systemInsetDisplayCutoutSimple() { flutterView.onApplyWindowInsets(windowInsets); verify(flutterRenderer, times(2)).setViewportMetrics(viewportMetricsCaptor.capture()); + // Each dimension of the viewport metric paddings should be the maximum of the corresponding + // dimension from the display cutout's safe insets and waterfall insets. validateViewportMetricPadding(viewportMetricsCaptor, 200, 120, 250, 140); assertEquals(100, viewportMetricsCaptor.getValue().viewInsetTop); From 3673fde1228bed4cb962ad3d5d1609928c2c1308 Mon Sep 17 00:00:00 2001 From: Yaakov Schectman Date: Mon, 28 Oct 2024 16:50:33 -0400 Subject: [PATCH 18/21] Cutout type and state --- .../embedding/engine/renderer/FlutterRenderer.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java index a3bc76b573fe0..9db9065a69c6f 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -1231,11 +1231,10 @@ public void setViewportMetrics(@NonNull ViewportMetrics viewportMetrics) { for (int i = 0; i < viewportMetrics.displayCutouts.size(); i++) { DisplayCutout displayCutout = viewportMetrics.displayCutouts.get(i); translateFeatureBounds(displayFeaturesBounds, cutoutOffset + 4 * i, displayCutout.bounds); - // Display cutouts always have type CUTOUT and state UNKNOWN. displayFeaturesType[viewportMetrics.displayFeatures.size() + i] = - DisplayFeatureType.CUTOUT.encodedValue; + DisplayCutout.type.encodedValue; displayFeaturesState[viewportMetrics.displayFeatures.size() + i] = - DisplayFeatureState.UNKNOWN.encodedValue; + DisplayCutout.state.encodedValue; } flutterJNI.setViewportMetrics( @@ -1387,6 +1386,9 @@ public DisplayFeature(Rect bounds, DisplayFeatureType type, DisplayFeatureState */ public static final class DisplayCutout { public final Rect bounds; + // Display cutouts always have type CUTOUT and state UNKNOWN. + public static final DisplayFeatureType type = DisplayFeatureType.CUTOUT; + public static final DisplayFeatureState state = DisplayFeatureState.UNKNOWN; public DisplayCutout(Rect bounds) { this.bounds = bounds; From 4f57f904e6426f91b2048df528deeff6facf561b Mon Sep 17 00:00:00 2001 From: Yaakov Schectman Date: Tue, 29 Oct 2024 06:56:28 -0400 Subject: [PATCH 19/21] PR org --- .../embedding/android/FlutterView.java | 3 -- .../engine/renderer/FlutterRenderer.java | 37 ++++++++++--------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index bc33f60093d54..6e076908021d9 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -73,7 +73,6 @@ import io.flutter.view.AccessibilityBridge; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -146,8 +145,6 @@ public class FlutterView extends FrameLayout // Directly implemented View behavior that communicates with Flutter. private final FlutterRenderer.ViewportMetrics viewportMetrics = new FlutterRenderer.ViewportMetrics(); - private List displayFeatures = new ArrayList<>(); - private final List displayCutouts = new ArrayList<>(); private final AccessibilityBridge.OnAccessibilityChangeListener onAccessibilityChangeListener = new AccessibilityBridge.OnAccessibilityChangeListener() { diff --git a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java index 9db9065a69c6f..3f7374e595ac2 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -1214,7 +1214,10 @@ public void setViewportMetrics(@NonNull ViewportMetrics viewportMetrics) { + viewportMetrics.systemGestureInsetRight + "\n" + "Display Features: " - + viewportMetrics.displayFeatures.size()); + + viewportMetrics.displayFeatures.size() + + "\n" + + "Display Cutouts: " + + viewportMetrics.displayCutouts.size()); int totalFeaturesAndCutouts = viewportMetrics.displayFeatures.size() + viewportMetrics.displayCutouts.size(); @@ -1357,6 +1360,22 @@ boolean validate() { public List displayCutouts = new ArrayList<>(); } + /** + * Description of a cutout on the physical display. + * + *

A simplified analog to {@link DisplayFeature} that specifically handles display cutouts. + */ + public static final class DisplayCutout { + public final Rect bounds; + // Display cutouts always have type CUTOUT and state UNKNOWN. + public static final DisplayFeatureType type = DisplayFeatureType.CUTOUT; + public static final DisplayFeatureState state = DisplayFeatureState.UNKNOWN; + + public DisplayCutout(Rect bounds) { + this.bounds = bounds; + } + } + /** * Description of a physical feature on the display. * @@ -1379,22 +1398,6 @@ public DisplayFeature(Rect bounds, DisplayFeatureType type, DisplayFeatureState } } - /** - * Description of a cutout on the physical display. - * - *

A simplified analog to {@link DisplayFeature} that specifically handles display cutouts. - */ - public static final class DisplayCutout { - public final Rect bounds; - // Display cutouts always have type CUTOUT and state UNKNOWN. - public static final DisplayFeatureType type = DisplayFeatureType.CUTOUT; - public static final DisplayFeatureState state = DisplayFeatureState.UNKNOWN; - - public DisplayCutout(Rect bounds) { - this.bounds = bounds; - } - } - /** * Types of display features that can appear on the viewport. * From 5bf9b381ece931e26827a146e6d1c49b8e23b0c2 Mon Sep 17 00:00:00 2001 From: Yaakov Schectman Date: Tue, 29 Oct 2024 15:05:16 -0400 Subject: [PATCH 20/21] Remove DisplayCutout class --- .../embedding/android/FlutterView.java | 2 +- .../engine/renderer/FlutterRenderer.java | 26 +++---------------- .../embedding/android/FlutterViewTest.java | 8 ++++-- .../engine/renderer/FlutterRendererTest.java | 2 +- 4 files changed, 12 insertions(+), 26 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 6e076908021d9..3832980c45a83 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -775,7 +775,7 @@ navigationBarVisible && guessBottomKeyboardInset(insets) == 0 if (cutout != null) { for (Rect bounds : cutout.getBoundingRects()) { Log.v(TAG, "DisplayCutout area reported with bounds = " + bounds.toString()); - viewportMetrics.displayCutouts.add(new FlutterRenderer.DisplayCutout(bounds)); + viewportMetrics.displayCutouts.add(new FlutterRenderer.DisplayFeature(bounds, DisplayFeatureType.CUTOUT, DisplayFeatureState.UNKNOWN)); } } } diff --git a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java index 3f7374e595ac2..509bf45b574ca 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -1232,12 +1232,10 @@ public void setViewportMetrics(@NonNull ViewportMetrics viewportMetrics) { } int cutoutOffset = viewportMetrics.displayFeatures.size() * 4; for (int i = 0; i < viewportMetrics.displayCutouts.size(); i++) { - DisplayCutout displayCutout = viewportMetrics.displayCutouts.get(i); + DisplayFeature displayCutout = viewportMetrics.displayCutouts.get(i); translateFeatureBounds(displayFeaturesBounds, cutoutOffset + 4 * i, displayCutout.bounds); - displayFeaturesType[viewportMetrics.displayFeatures.size() + i] = - DisplayCutout.type.encodedValue; - displayFeaturesState[viewportMetrics.displayFeatures.size() + i] = - DisplayCutout.state.encodedValue; + displayFeaturesType[viewportMetrics.displayFeatures.size() + i] = displayCutout.type.encodedValue; + displayFeaturesState[viewportMetrics.displayFeatures.size() + i] = displayCutout.state.encodedValue; } flutterJNI.setViewportMetrics( @@ -1357,23 +1355,7 @@ boolean validate() { public List displayFeatures = new ArrayList<>(); // Specifically display cutouts. - public List displayCutouts = new ArrayList<>(); - } - - /** - * Description of a cutout on the physical display. - * - *

A simplified analog to {@link DisplayFeature} that specifically handles display cutouts. - */ - public static final class DisplayCutout { - public final Rect bounds; - // Display cutouts always have type CUTOUT and state UNKNOWN. - public static final DisplayFeatureType type = DisplayFeatureType.CUTOUT; - public static final DisplayFeatureState state = DisplayFeatureState.UNKNOWN; - - public DisplayCutout(Rect bounds) { - this.bounds = bounds; - } + public List displayCutouts = new ArrayList<>(); } /** diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java index 57b809d997430..2c598691e2cb4 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -722,11 +722,13 @@ public void onApplyWindowInsetsSetsDisplayCutouts() { assertEquals(FlutterRenderer.DisplayFeatureState.POSTURE_FLAT, features.get(0).state); assertEquals(featureBounds, features.get(0).bounds); - List cutouts = viewportMetricsCaptor.getValue().displayCutouts; + List cutouts = viewportMetricsCaptor.getValue().displayCutouts; // Asserts for display cutouts. assertEquals(2, cutouts.size()); for (int i = 0; i < 2; i++) { assertEquals(cutoutBoundingRects.get(i), cutouts.get(i).bounds); + assertEquals(FlutterRenderer.DisplayFeatureType.CUTOUT, cutouts.get(i).type); + assertEquals(FlutterRenderer.DisplayFeatureState.UNKNOWN, cutouts.get(i).state); } } @@ -822,8 +824,10 @@ public void itSendsHingeDisplayFeatureToFlutter() { // Assert the display cutout is unaffected. assertEquals(1, viewportMetricsCaptor.getValue().displayCutouts.size()); - FlutterRenderer.DisplayCutout cutout = viewportMetricsCaptor.getValue().displayCutouts.get(0); + FlutterRenderer.DisplayFeature cutout = viewportMetricsCaptor.getValue().displayCutouts.get(0); assertEquals(cutoutBoundingRects.get(0), cutout.bounds); + assertEquals(FlutterRenderer.DisplayFeatureType.CUTOUT, cutout.type); + assertEquals(FlutterRenderer.DisplayFeatureState.UNKNOWN, cutout.state); } @Test diff --git a/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java b/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java index 30f4357cdbb7e..8a97ef0630ec3 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java @@ -293,7 +293,7 @@ public void itConvertsDisplayFeatureArrayToPrimitiveArrays() { new Rect(10, 20, 30, 40), FlutterRenderer.DisplayFeatureType.FOLD, FlutterRenderer.DisplayFeatureState.POSTURE_HALF_OPENED)); - metrics.displayCutouts.add(new FlutterRenderer.DisplayCutout(new Rect(50, 60, 70, 80))); + metrics.displayCutouts.add(new FlutterRenderer.DisplayFeature(new Rect(50, 60, 70, 80), FlutterRenderer.DisplayFeatureType.CUTOUT, FlutterRenderer.DisplayFeatureState.UNKNOWN)); // Execute the behavior under test. flutterRenderer.setViewportMetrics(metrics); From 91f506dd5f6c0f3e454e9616f926716c7478d8f0 Mon Sep 17 00:00:00 2001 From: Yaakov Schectman Date: Tue, 29 Oct 2024 15:34:18 -0400 Subject: [PATCH 21/21] Move list logic --- .../embedding/android/FlutterView.java | 15 ++++++---- .../engine/renderer/FlutterRenderer.java | 28 ++++++++++++++++--- .../embedding/android/FlutterViewTest.java | 23 ++++++++------- .../engine/renderer/FlutterRendererTest.java | 20 +++++++++---- 4 files changed, 61 insertions(+), 25 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 3832980c45a83..a1eaed6da7f03 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -73,6 +73,7 @@ import io.flutter.view.AccessibilityBridge; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -538,7 +539,7 @@ protected void onDetachedFromWindow() { @TargetApi(API_LEVELS.API_28) protected void setWindowInfoListenerDisplayFeatures(WindowLayoutInfo layoutInfo) { List newDisplayFeatures = layoutInfo.getDisplayFeatures(); - viewportMetrics.displayFeatures.clear(); + List flutterDisplayFeatures = new ArrayList<>(); // Data from WindowInfoTracker display features. Fold and hinge areas are // populated here. @@ -565,16 +566,17 @@ protected void setWindowInfoListenerDisplayFeatures(WindowLayoutInfo layoutInfo) } else { state = DisplayFeatureState.UNKNOWN; } - viewportMetrics.displayFeatures.add( + flutterDisplayFeatures.add( new FlutterRenderer.DisplayFeature(displayFeature.getBounds(), type, state)); } else { - viewportMetrics.displayFeatures.add( + flutterDisplayFeatures.add( new FlutterRenderer.DisplayFeature( displayFeature.getBounds(), DisplayFeatureType.UNKNOWN, DisplayFeatureState.UNKNOWN)); } } + viewportMetrics.setDisplayFeatures(flutterDisplayFeatures); sendViewportMetricsToFlutter(); } @@ -769,16 +771,19 @@ navigationBarVisible && guessBottomKeyboardInset(insets) == 0 // Data from the DisplayCutout bounds. Cutouts for cameras and other sensors are // populated here. DisplayCutout was introduced in API 28. - viewportMetrics.displayCutouts.clear(); + List displayCutouts = new ArrayList<>(); if (Build.VERSION.SDK_INT >= API_LEVELS.API_28) { DisplayCutout cutout = insets.getDisplayCutout(); if (cutout != null) { for (Rect bounds : cutout.getBoundingRects()) { Log.v(TAG, "DisplayCutout area reported with bounds = " + bounds.toString()); - viewportMetrics.displayCutouts.add(new FlutterRenderer.DisplayFeature(bounds, DisplayFeatureType.CUTOUT, DisplayFeatureState.UNKNOWN)); + displayCutouts.add( + new FlutterRenderer.DisplayFeature( + bounds, DisplayFeatureType.CUTOUT, DisplayFeatureState.UNKNOWN)); } } } + viewportMetrics.setDisplayCutouts(displayCutouts); // The caption bar inset is a new addition, and the APIs called to query it utilize a list of // bounding Rects instead of an Insets object, which is a newer API method, as compared to the diff --git a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java index 509bf45b574ca..fe1fa6428eb87 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -1234,8 +1234,10 @@ public void setViewportMetrics(@NonNull ViewportMetrics viewportMetrics) { for (int i = 0; i < viewportMetrics.displayCutouts.size(); i++) { DisplayFeature displayCutout = viewportMetrics.displayCutouts.get(i); translateFeatureBounds(displayFeaturesBounds, cutoutOffset + 4 * i, displayCutout.bounds); - displayFeaturesType[viewportMetrics.displayFeatures.size() + i] = displayCutout.type.encodedValue; - displayFeaturesState[viewportMetrics.displayFeatures.size() + i] = displayCutout.state.encodedValue; + displayFeaturesType[viewportMetrics.displayFeatures.size() + i] = + displayCutout.type.encodedValue; + displayFeaturesState[viewportMetrics.displayFeatures.size() + i] = + displayCutout.state.encodedValue; } flutterJNI.setViewportMetrics( @@ -1352,10 +1354,28 @@ boolean validate() { } // Features - public List displayFeatures = new ArrayList<>(); + private final List displayFeatures = new ArrayList<>(); // Specifically display cutouts. - public List displayCutouts = new ArrayList<>(); + private final List displayCutouts = new ArrayList<>(); + + public List getDisplayFeatures() { + return displayFeatures; + } + + public List getDisplayCutouts() { + return displayCutouts; + } + + public void setDisplayFeatures(List newFeatures) { + displayFeatures.clear(); + displayFeatures.addAll(newFeatures); + } + + public void setDisplayCutouts(List newCutouts) { + displayCutouts.clear(); + displayCutouts.addAll(newCutouts); + } } /** diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java index 2c598691e2cb4..7247873ef1e0d 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -699,7 +699,7 @@ public void onApplyWindowInsetsSetsDisplayCutouts() { // Assert the display feature is set. verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); List features = - viewportMetricsCaptor.getValue().displayFeatures; + viewportMetricsCaptor.getValue().getDisplayFeatures(); assertEquals(1, features.size()); assertEquals(FlutterRenderer.DisplayFeatureType.HINGE, features.get(0).type); assertEquals(FlutterRenderer.DisplayFeatureState.POSTURE_FLAT, features.get(0).state); @@ -714,7 +714,7 @@ public void onApplyWindowInsetsSetsDisplayCutouts() { flutterView.onApplyWindowInsets(windowInsets); verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); - features = viewportMetricsCaptor.getValue().displayFeatures; + features = viewportMetricsCaptor.getValue().getDisplayFeatures(); // Assert the old display feature is still present. assertEquals(1, features.size()); @@ -722,7 +722,8 @@ public void onApplyWindowInsetsSetsDisplayCutouts() { assertEquals(FlutterRenderer.DisplayFeatureState.POSTURE_FLAT, features.get(0).state); assertEquals(featureBounds, features.get(0).bounds); - List cutouts = viewportMetricsCaptor.getValue().displayCutouts; + List cutouts = + viewportMetricsCaptor.getValue().getDisplayCutouts(); // Asserts for display cutouts. assertEquals(2, cutouts.size()); for (int i = 0; i < 2; i++) { @@ -782,7 +783,7 @@ public void itSendsHingeDisplayFeatureToFlutter() { ArgumentCaptor viewportMetricsCaptor = ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class); verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); - assertEquals(Collections.emptyList(), viewportMetricsCaptor.getValue().displayFeatures); + assertEquals(Collections.emptyList(), viewportMetricsCaptor.getValue().getDisplayFeatures()); clearInvocations(flutterRenderer); // Test that display features do not override cutouts. @@ -790,9 +791,10 @@ public void itSendsHingeDisplayFeatureToFlutter() { WindowInsets windowInsets = setupMockDisplayCutout(cutoutBoundingRects); flutterView.onApplyWindowInsets(windowInsets); verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); - assertEquals(1, viewportMetricsCaptor.getValue().displayCutouts.size()); + assertEquals(1, viewportMetricsCaptor.getValue().getDisplayCutouts().size()); assertEquals( - cutoutBoundingRects.get(0), viewportMetricsCaptor.getValue().displayCutouts.get(0).bounds); + cutoutBoundingRects.get(0), + viewportMetricsCaptor.getValue().getDisplayCutouts().get(0).bounds); clearInvocations(flutterRenderer); FoldingFeature displayFeature = mock(FoldingFeature.class); @@ -815,16 +817,17 @@ public void itSendsHingeDisplayFeatureToFlutter() { // Then the Renderer receives the display feature verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); - assertEquals(1, viewportMetricsCaptor.getValue().displayFeatures.size()); + assertEquals(1, viewportMetricsCaptor.getValue().getDisplayFeatures().size()); FlutterRenderer.DisplayFeature feature = - viewportMetricsCaptor.getValue().displayFeatures.get(0); + viewportMetricsCaptor.getValue().getDisplayFeatures().get(0); assertEquals(FlutterRenderer.DisplayFeatureType.HINGE, feature.type); assertEquals(FlutterRenderer.DisplayFeatureState.POSTURE_FLAT, feature.state); assertEquals(featureRect, feature.bounds); // Assert the display cutout is unaffected. - assertEquals(1, viewportMetricsCaptor.getValue().displayCutouts.size()); - FlutterRenderer.DisplayFeature cutout = viewportMetricsCaptor.getValue().displayCutouts.get(0); + assertEquals(1, viewportMetricsCaptor.getValue().getDisplayCutouts().size()); + FlutterRenderer.DisplayFeature cutout = + viewportMetricsCaptor.getValue().getDisplayCutouts().get(0); assertEquals(cutoutBoundingRects.get(0), cutout.bounds); assertEquals(FlutterRenderer.DisplayFeatureType.CUTOUT, cutout.type); assertEquals(FlutterRenderer.DisplayFeatureState.UNKNOWN, cutout.state); diff --git a/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java b/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java index 8a97ef0630ec3..862d00a064aac 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java @@ -288,12 +288,20 @@ public void itConvertsDisplayFeatureArrayToPrimitiveArrays() { metrics.width = 1000; metrics.height = 1000; metrics.devicePixelRatio = 2; - metrics.displayFeatures.add( - new FlutterRenderer.DisplayFeature( - new Rect(10, 20, 30, 40), - FlutterRenderer.DisplayFeatureType.FOLD, - FlutterRenderer.DisplayFeatureState.POSTURE_HALF_OPENED)); - metrics.displayCutouts.add(new FlutterRenderer.DisplayFeature(new Rect(50, 60, 70, 80), FlutterRenderer.DisplayFeatureType.CUTOUT, FlutterRenderer.DisplayFeatureState.UNKNOWN)); + metrics + .getDisplayFeatures() + .add( + new FlutterRenderer.DisplayFeature( + new Rect(10, 20, 30, 40), + FlutterRenderer.DisplayFeatureType.FOLD, + FlutterRenderer.DisplayFeatureState.POSTURE_HALF_OPENED)); + metrics + .getDisplayCutouts() + .add( + new FlutterRenderer.DisplayFeature( + new Rect(50, 60, 70, 80), + FlutterRenderer.DisplayFeatureType.CUTOUT, + FlutterRenderer.DisplayFeatureState.UNKNOWN)); // Execute the behavior under test. flutterRenderer.setViewportMetrics(metrics);