From 728173125edd952b95de5c917f40a358524726ad Mon Sep 17 00:00:00 2001 From: garyqian Date: Tue, 1 Sep 2020 05:37:01 -0700 Subject: [PATCH 1/8] Initial impl --- .../embedding/android/FlutterView.java | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 4b19d7e67cb24..262602d9a332a 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -14,6 +14,7 @@ import android.text.format.DateFormat; import android.util.AttributeSet; import android.util.SparseArray; +import android.view.DisplayCutout; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.PointerIcon; @@ -43,6 +44,7 @@ import io.flutter.plugin.mouse.MouseCursorPlugin; import io.flutter.plugin.platform.PlatformViewsController; import io.flutter.view.AccessibilityBridge; +import java.lang.Math; import java.util.HashSet; import java.util.Set; @@ -513,25 +515,38 @@ public final WindowInsets onApplyWindowInsets(@NonNull WindowInsets insets) { (SYSTEM_UI_FLAG_HIDE_NAVIGATION & getWindowSystemUiVisibility()) == 0; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - int mask = 0; + int mask = android.view.WindowInsets.Type.ime(); if (navigationBarVisible) { mask = mask | android.view.WindowInsets.Type.navigationBars(); } if (statusBarVisible) { mask = mask | android.view.WindowInsets.Type.statusBars(); } - mask = mask | android.view.WindowInsets.Type.ime(); + Insets uiInsets = insets.getInsets(mask); - Insets finalInsets = insets.getInsets(mask); - viewportMetrics.paddingTop = finalInsets.top; - viewportMetrics.paddingRight = finalInsets.right; + viewportMetrics.paddingTop = uiInsets.top; + viewportMetrics.paddingRight = uiInsets.right; viewportMetrics.paddingBottom = 0; - viewportMetrics.paddingLeft = finalInsets.left; + viewportMetrics.paddingLeft = uiInsets.left; viewportMetrics.viewInsetTop = 0; viewportMetrics.viewInsetRight = 0; - viewportMetrics.viewInsetBottom = finalInsets.bottom; + viewportMetrics.viewInsetBottom = uiInsets.bottom; viewportMetrics.viewInsetLeft = 0; + + // TODO(garyq): Expose the full rects of the display cutout. + + // Take the max of the display cutout insets and ui element insets to merge them + DisplayCutout cutout = insets.getDisplayCutout(); + if (cutout != null) { + Insets waterfallInsets = cutout.getWaterfallInsets(); + + viewportMetrics.paddingTop = Math.max(Math.max(viewportMetrics.paddingTop, waterfallInsets.top), cutout.getSafeInsetTop()); + viewportMetrics.paddingRight = Math.max(Math.max(viewportMetrics.paddingRight, waterfallInsets.right), cutout.getSafeInsetRight()); + viewportMetrics.paddingLeft = Math.max(Math.max(viewportMetrics.paddingLeft, waterfallInsets.left), cutout.getSafeInsetLeft()); + + viewportMetrics.viewInsetBottom = Math.max(Math.max(viewportMetrics.viewInsetBottom, waterfallInsets.bottom), cutout.getSafeInsetBottom()); + } } else { // We zero the left and/or right sides to prevent the padding the // navigation bar would have caused. From 036aa8c410709c48d6024644676b8a8f4e5cb25c Mon Sep 17 00:00:00 2001 From: garyqian Date: Tue, 1 Sep 2020 05:42:23 -0700 Subject: [PATCH 2/8] DisplayCutouts as bottomPadding --- .../android/io/flutter/embedding/android/FlutterView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 262602d9a332a..a7af0065d96f1 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -543,9 +543,9 @@ public final WindowInsets onApplyWindowInsets(@NonNull WindowInsets insets) { viewportMetrics.paddingTop = Math.max(Math.max(viewportMetrics.paddingTop, waterfallInsets.top), cutout.getSafeInsetTop()); viewportMetrics.paddingRight = Math.max(Math.max(viewportMetrics.paddingRight, waterfallInsets.right), cutout.getSafeInsetRight()); + viewportMetrics.paddingBottom = Math.max(waterfallInsets.bottom, cutout.getSafeInsetBottom()); viewportMetrics.paddingLeft = Math.max(Math.max(viewportMetrics.paddingLeft, waterfallInsets.left), cutout.getSafeInsetLeft()); - viewportMetrics.viewInsetBottom = Math.max(Math.max(viewportMetrics.viewInsetBottom, waterfallInsets.bottom), cutout.getSafeInsetBottom()); } } else { // We zero the left and/or right sides to prevent the padding the From 8fb6c9404209d193244f6ac658d0b72840460b6e Mon Sep 17 00:00:00 2001 From: garyqian Date: Tue, 1 Sep 2020 11:45:31 -0700 Subject: [PATCH 3/8] Apply to system gesture insets --- .../embedding/android/FlutterView.java | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index a7af0065d96f1..947805216622b 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -510,6 +510,14 @@ private int guessBottomKeyboardInset(WindowInsets insets) { public final WindowInsets onApplyWindowInsets(@NonNull WindowInsets insets) { WindowInsets newInsets = super.onApplyWindowInsets(insets); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + Insets systemGestureInsets = insets.getSystemGestureInsets(); + viewportMetrics.systemGestureInsetTop = systemGestureInsets.top; + viewportMetrics.systemGestureInsetRight = systemGestureInsets.right; + viewportMetrics.systemGestureInsetBottom = systemGestureInsets.bottom; + viewportMetrics.systemGestureInsetLeft = systemGestureInsets.left; + } + boolean statusBarVisible = (SYSTEM_UI_FLAG_FULLSCREEN & getWindowSystemUiVisibility()) == 0; boolean navigationBarVisible = (SYSTEM_UI_FLAG_HIDE_NAVIGATION & getWindowSystemUiVisibility()) == 0; @@ -537,16 +545,15 @@ public final WindowInsets onApplyWindowInsets(@NonNull WindowInsets insets) { // TODO(garyq): Expose the full rects of the display cutout. // Take the max of the display cutout insets and ui element insets to merge them - DisplayCutout cutout = insets.getDisplayCutout(); + DisplayCutout cutout = getDisplay().getCutout(); if (cutout != null) { Insets waterfallInsets = cutout.getWaterfallInsets(); - - viewportMetrics.paddingTop = Math.max(Math.max(viewportMetrics.paddingTop, waterfallInsets.top), cutout.getSafeInsetTop()); - viewportMetrics.paddingRight = Math.max(Math.max(viewportMetrics.paddingRight, waterfallInsets.right), cutout.getSafeInsetRight()); - viewportMetrics.paddingBottom = Math.max(waterfallInsets.bottom, cutout.getSafeInsetBottom()); - viewportMetrics.paddingLeft = Math.max(Math.max(viewportMetrics.paddingLeft, waterfallInsets.left), cutout.getSafeInsetLeft()); - + viewportMetrics.systemGestureInsetTop = Math.max(Math.max(viewportMetrics.systemGestureInsetTop, waterfallInsets.top), cutout.getSafeInsetTop()); + viewportMetrics.systemGestureInsetRight = Math.max(Math.max(viewportMetrics.systemGestureInsetRight, waterfallInsets.right), cutout.getSafeInsetRight()); + viewportMetrics.systemGestureInsetBottom = Math.max(Math.max(viewportMetrics.systemGestureInsetBottom, waterfallInsets.bottom), cutout.getSafeInsetBottom()); + viewportMetrics.systemGestureInsetLeft = Math.max(Math.max(viewportMetrics.systemGestureInsetLeft, waterfallInsets.left), cutout.getSafeInsetLeft()); } + } else { // We zero the left and/or right sides to prevent the padding the // navigation bar would have caused. @@ -578,13 +585,6 @@ public final WindowInsets onApplyWindowInsets(@NonNull WindowInsets insets) { viewportMetrics.viewInsetLeft = 0; } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - Insets systemGestureInsets = insets.getSystemGestureInsets(); - viewportMetrics.systemGestureInsetTop = systemGestureInsets.top; - viewportMetrics.systemGestureInsetRight = systemGestureInsets.right; - viewportMetrics.systemGestureInsetBottom = systemGestureInsets.bottom; - viewportMetrics.systemGestureInsetLeft = systemGestureInsets.left; - } Log.v( TAG, From 7e26c7d68978a3f3b0699ed33ba914a1eb32c5c9 Mon Sep 17 00:00:00 2001 From: garyqian Date: Tue, 1 Sep 2020 12:28:39 -0700 Subject: [PATCH 4/8] Replicate in embedding v1 --- .../embedding/android/FlutterView.java | 3 +- .../android/io/flutter/view/FlutterView.java | 104 ++++++++++++------ 2 files changed, 71 insertions(+), 36 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 947805216622b..b94d70b9f2e2c 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -44,7 +44,6 @@ import io.flutter.plugin.mouse.MouseCursorPlugin; import io.flutter.plugin.platform.PlatformViewsController; import io.flutter.view.AccessibilityBridge; -import java.lang.Math; import java.util.HashSet; import java.util.Set; @@ -544,7 +543,7 @@ public final WindowInsets onApplyWindowInsets(@NonNull WindowInsets insets) { // TODO(garyq): Expose the full rects of the display cutout. - // Take the max of the display cutout insets and ui element insets to merge them + // Take the max of the display cutout insets and existing insets to merge them DisplayCutout cutout = getDisplay().getCutout(); if (cutout != null) { Insets waterfallInsets = cutout.getWaterfallInsets(); diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java index 9c20937cac8b8..51d9243ae416d 100644 --- a/shell/platform/android/io/flutter/view/FlutterView.java +++ b/shell/platform/android/io/flutter/view/FlutterView.java @@ -21,6 +21,7 @@ import android.util.AttributeSet; import android.util.Log; import android.util.SparseArray; +import android.view.DisplayCutout; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.PointerIcon; @@ -588,40 +589,6 @@ private int guessBottomKeyboardInset(WindowInsets insets) { @RequiresApi(20) @SuppressLint({"InlinedApi", "NewApi"}) public final WindowInsets onApplyWindowInsets(WindowInsets insets) { - boolean statusBarHidden = (SYSTEM_UI_FLAG_FULLSCREEN & getWindowSystemUiVisibility()) != 0; - boolean navigationBarHidden = - (SYSTEM_UI_FLAG_HIDE_NAVIGATION & getWindowSystemUiVisibility()) != 0; - - // We zero the left and/or right sides to prevent the padding the - // navigation bar would have caused. - ZeroSides zeroSides = ZeroSides.NONE; - if (navigationBarHidden) { - zeroSides = calculateShouldZeroSides(); - } - - // The padding on top should be removed when the statusbar is hidden. - mMetrics.physicalPaddingTop = statusBarHidden ? 0 : insets.getSystemWindowInsetTop(); - mMetrics.physicalPaddingRight = - zeroSides == ZeroSides.RIGHT || zeroSides == ZeroSides.BOTH - ? 0 - : insets.getSystemWindowInsetRight(); - mMetrics.physicalPaddingBottom = 0; - mMetrics.physicalPaddingLeft = - zeroSides == ZeroSides.LEFT || zeroSides == ZeroSides.BOTH - ? 0 - : insets.getSystemWindowInsetLeft(); - - // Bottom system inset (keyboard) should adjust scrollable bottom edge (inset). - mMetrics.physicalViewInsetTop = 0; - mMetrics.physicalViewInsetRight = 0; - // We perform hidden navbar and keyboard handling if the navbar is set to hidden. Otherwise, - // the navbar padding should always be provided. - mMetrics.physicalViewInsetBottom = - navigationBarHidden - ? guessBottomKeyboardInset(insets) - : insets.getSystemWindowInsetBottom(); - mMetrics.physicalViewInsetLeft = 0; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { Insets systemGestureInsets = insets.getSystemGestureInsets(); mMetrics.systemGestureInsetTop = systemGestureInsets.top; @@ -629,6 +596,75 @@ public final WindowInsets onApplyWindowInsets(WindowInsets insets) { mMetrics.systemGestureInsetBottom = systemGestureInsets.bottom; mMetrics.systemGestureInsetLeft = systemGestureInsets.left; } + + boolean statusBarVisible = (SYSTEM_UI_FLAG_FULLSCREEN & getWindowSystemUiVisibility()) == 0; + boolean navigationBarVisible = + (SYSTEM_UI_FLAG_HIDE_NAVIGATION & getWindowSystemUiVisibility()) == 0; + + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + int mask = android.view.WindowInsets.Type.ime(); + if (navigationBarVisible) { + mask = mask | android.view.WindowInsets.Type.navigationBars(); + } + if (statusBarVisible) { + mask = mask | android.view.WindowInsets.Type.statusBars(); + } + Insets uiInsets = insets.getInsets(mask); + + mMetrics.physicalPaddingTop = uiInsets.top; + mMetrics.physicalPaddingRight = uiInsets.right; + mMetrics.physicalPaddingBottom = 0; + mMetrics.physicalPaddingLeft = uiInsets.left; + + mMetrics.physicalViewInsetTop = 0; + mMetrics.physicalViewInsetRight = 0; + mMetrics.physicalViewInsetBottom = uiInsets.bottom; + mMetrics.physicalViewInsetLeft = 0; + + // TODO(garyq): Expose the full rects of the display cutout. + + // Take the max of the display cutout insets and existing insets to merge them + DisplayCutout cutout = getDisplay().getCutout(); + if (cutout != null) { + Insets waterfallInsets = cutout.getWaterfallInsets(); + mMetrics.systemGestureInsetTop = Math.max(Math.max(mMetrics.systemGestureInsetTop, waterfallInsets.top), cutout.getSafeInsetTop()); + mMetrics.systemGestureInsetRight = Math.max(Math.max(mMetrics.systemGestureInsetRight, waterfallInsets.right), cutout.getSafeInsetRight()); + mMetrics.systemGestureInsetBottom = Math.max(Math.max(mMetrics.systemGestureInsetBottom, waterfallInsets.bottom), cutout.getSafeInsetBottom()); + mMetrics.systemGestureInsetLeft = Math.max(Math.max(mMetrics.systemGestureInsetLeft, waterfallInsets.left), cutout.getSafeInsetLeft()); + } + + } else { + // We zero the left and/or right sides to prevent the padding the + // navigation bar would have caused. + ZeroSides zeroSides = ZeroSides.NONE; + if (!navigationBarVisible) { + zeroSides = calculateShouldZeroSides(); + } + + // Status bar (top) and left/right system insets should partially obscure the content + // (padding). + mMetrics.physicalPaddingTop = statusBarVisible ? insets.getSystemWindowInsetTop() : 0; + mMetrics.physicalPaddingRight = + zeroSides == ZeroSides.RIGHT || zeroSides == ZeroSides.BOTH + ? 0 + : insets.getSystemWindowInsetRight(); + mMetrics.physicalPaddingBottom = 0; + mMetrics.physicalPaddingLeft = + zeroSides == ZeroSides.LEFT || zeroSides == ZeroSides.BOTH + ? 0 + : insets.getSystemWindowInsetLeft(); + + // Bottom system inset (keyboard) should adjust scrollable bottom edge (inset). + mMetrics.physicalViewInsetTop = 0; + mMetrics.physicalViewInsetRight = 0; + mMetrics.physicalViewInsetBottom = + navigationBarVisible + ? insets.getSystemWindowInsetBottom() + : guessBottomKeyboardInset(insets); + mMetrics.physicalViewInsetLeft = 0; + } + updateViewportMetrics(); return super.onApplyWindowInsets(insets); } From 56fb0613bc44c1c09b55ce078b4eaca1c3a80091 Mon Sep 17 00:00:00 2001 From: garyqian Date: Tue, 1 Sep 2020 12:46:18 -0700 Subject: [PATCH 5/8] Add test --- .../embedding/android/FlutterView.java | 2 +- .../android/io/flutter/view/FlutterView.java | 2 +- .../embedding/android/FlutterViewTest.java | 62 +++++++++++++++++++ 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index b94d70b9f2e2c..5f605f6850a7c 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -544,7 +544,7 @@ public final WindowInsets onApplyWindowInsets(@NonNull WindowInsets insets) { // TODO(garyq): Expose the full rects of the display cutout. // Take the max of the display cutout insets and existing insets to merge them - DisplayCutout cutout = getDisplay().getCutout(); + DisplayCutout cutout = insets.getCutout(); if (cutout != null) { Insets waterfallInsets = cutout.getWaterfallInsets(); viewportMetrics.systemGestureInsetTop = Math.max(Math.max(viewportMetrics.systemGestureInsetTop, waterfallInsets.top), cutout.getSafeInsetTop()); diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java index 51d9243ae416d..9cf58e6eeb63c 100644 --- a/shell/platform/android/io/flutter/view/FlutterView.java +++ b/shell/platform/android/io/flutter/view/FlutterView.java @@ -625,7 +625,7 @@ public final WindowInsets onApplyWindowInsets(WindowInsets insets) { // TODO(garyq): Expose the full rects of the display cutout. // Take the max of the display cutout insets and existing insets to merge them - DisplayCutout cutout = getDisplay().getCutout(); + DisplayCutout cutout = insets.getCutout(); if (cutout != null) { Insets waterfallInsets = cutout.getWaterfallInsets(); mMetrics.systemGestureInsetTop = Math.max(Math.max(mMetrics.systemGestureInsetTop, waterfallInsets.top), cutout.getSafeInsetTop()); 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 8129ecc9a3ed3..bd414349474f5 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -483,6 +483,68 @@ public void systemInsetGetInsetsFullscreenLegacy() { assertEquals(103, viewportMetricsCaptor.getValue().paddingRight); } + // This test uses the API 30+ Algorithm for window insets. The legacy algorithm is + // set to -1 values, so it is clear if the wrong algorithm is used. + @Test + @TargetApi(30) + @Config(sdk = 30) + public void systemInsetDisplayCutoutSimple() { + RuntimeEnvironment.setQualifiers("+land"); + FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.systemContext)); + ShadowDisplay display = + Shadows.shadowOf( + ((WindowManager) + RuntimeEnvironment.systemContext.getSystemService(Context.WINDOW_SERVICE)) + .getDefaultDisplay()); + // display.setRotation(0); + assertEquals(0, flutterView.getSystemUiVisibility()); + when(flutterView.getWindowSystemUiVisibility()).thenReturn(0); + when(flutterView.getContext()).thenReturn(RuntimeEnvironment.systemContext); + + FlutterEngine flutterEngine = + spy(new FlutterEngine(RuntimeEnvironment.application, 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().paddingTop); + + 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); + when(windowInsets.getSystemWindowInsetTop()).thenReturn(-1); + when(windowInsets.getSystemWindowInsetBottom()).thenReturn(-1); + when(windowInsets.getSystemWindowInsetLeft()).thenReturn(-1); + when(windowInsets.getSystemWindowInsetRight()).thenReturn(-1); + when(windowInsets.getInsets(anyInt())).thenReturn(insets); + when(windowInsets.getSystemGestureInsets(anyInt())).thenReturn(systemGestureInsets); + when(windowInsets.getCutout()).thenReturn(displayCutout); + + Insets waterfallInsets = Insets.of(200, 0, 200, 0); + when(displayCutout.getWaterfallInsets()).thenReturn(waterfallInsets); + when(displatCutout.getSafeInsetTop()).thenReturn(150); + when(displatCutout.getSafeInsetBottom()).thenReturn(150); + when(displatCutout.getSafeInsetLeft()).thenReturn(150); + when(displatCutout.getSafeInsetRight()).thenReturn(150); + + flutterView.onApplyWindowInsets(windowInsets); + + verify(flutterRenderer, times(2)).setViewportMetrics(viewportMetricsCaptor.capture()); + assertEquals(150, viewportMetricsCaptor.getValue().systemGestureInsetTop); + assertEquals(150, viewportMetricsCaptor.getValue().systemGestureInsetBottom); + assertEquals(200, viewportMetricsCaptor.getValue().systemGestureInsetLeft); + assertEquals(200, viewportMetricsCaptor.getValue().systemGestureInsetRight); + + assertEquals(100, viewportMetricsCaptor.getValue().paddingTop); + } + @Test public void flutterImageView_acquiresImageAndInvalidates() { final ImageReader mockReader = mock(ImageReader.class); From 9504f64bd7e1f9ef1e9283dedf308498cbd72640 Mon Sep 17 00:00:00 2001 From: garyqian Date: Tue, 1 Sep 2020 16:24:38 -0700 Subject: [PATCH 6/8] Address comments, formatting --- .../embedding/android/FlutterView.java | 49 +++++++++++++------ .../android/io/flutter/view/FlutterView.java | 49 +++++++++++++------ .../embedding/android/FlutterViewTest.java | 19 ++++--- 3 files changed, 75 insertions(+), 42 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 5f605f6850a7c..4aec99b742103 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -509,7 +509,8 @@ private int guessBottomKeyboardInset(WindowInsets insets) { public final WindowInsets onApplyWindowInsets(@NonNull WindowInsets insets) { WindowInsets newInsets = super.onApplyWindowInsets(insets); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + // getSystemGestureInsets() was introduced in API 29 and immediately deprecated in 30. + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { Insets systemGestureInsets = insets.getSystemGestureInsets(); viewportMetrics.systemGestureInsetTop = systemGestureInsets.top; viewportMetrics.systemGestureInsetRight = systemGestureInsets.right; @@ -522,7 +523,7 @@ public final WindowInsets onApplyWindowInsets(@NonNull WindowInsets insets) { (SYSTEM_UI_FLAG_HIDE_NAVIGATION & getWindowSystemUiVisibility()) == 0; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - int mask = android.view.WindowInsets.Type.ime(); + int mask = 0; if (navigationBarVisible) { mask = mask | android.view.WindowInsets.Type.navigationBars(); } @@ -530,29 +531,46 @@ public final WindowInsets onApplyWindowInsets(@NonNull WindowInsets insets) { mask = mask | android.view.WindowInsets.Type.statusBars(); } Insets uiInsets = insets.getInsets(mask); - viewportMetrics.paddingTop = uiInsets.top; viewportMetrics.paddingRight = uiInsets.right; - viewportMetrics.paddingBottom = 0; + viewportMetrics.paddingBottom = uiInsets.bottom; viewportMetrics.paddingLeft = uiInsets.left; - viewportMetrics.viewInsetTop = 0; - viewportMetrics.viewInsetRight = 0; - viewportMetrics.viewInsetBottom = uiInsets.bottom; - viewportMetrics.viewInsetLeft = 0; + Insets imeInsets = insets.getInsets(android.view.WindowInsets.Type.ime()); + viewportMetrics.viewInsetTop = imeInsets.top; + viewportMetrics.viewInsetRight = imeInsets.right; + viewportMetrics.viewInsetBottom = imeInsets.bottom; // Typically, only bottom is non-zero + viewportMetrics.viewInsetLeft = imeInsets.left; + + Insets systemGestureInsets = insets.getInsets(android.view.WindowInsets.Type.systemGestures()); + viewportMetrics.systemGestureInsetTop = systemGestureInsets.top; + viewportMetrics.systemGestureInsetRight = systemGestureInsets.right; + viewportMetrics.systemGestureInsetBottom = systemGestureInsets.bottom; + viewportMetrics.systemGestureInsetLeft = systemGestureInsets.left; // TODO(garyq): Expose the full rects of the display cutout. - // Take the max of the display cutout insets and existing insets to merge them - DisplayCutout cutout = insets.getCutout(); + // Take the max of the display cutout insets and existing padding to merge them + DisplayCutout cutout = insets.getDisplayCutout(); if (cutout != null) { Insets waterfallInsets = cutout.getWaterfallInsets(); - viewportMetrics.systemGestureInsetTop = Math.max(Math.max(viewportMetrics.systemGestureInsetTop, waterfallInsets.top), cutout.getSafeInsetTop()); - viewportMetrics.systemGestureInsetRight = Math.max(Math.max(viewportMetrics.systemGestureInsetRight, waterfallInsets.right), cutout.getSafeInsetRight()); - viewportMetrics.systemGestureInsetBottom = Math.max(Math.max(viewportMetrics.systemGestureInsetBottom, waterfallInsets.bottom), cutout.getSafeInsetBottom()); - viewportMetrics.systemGestureInsetLeft = Math.max(Math.max(viewportMetrics.systemGestureInsetLeft, waterfallInsets.left), cutout.getSafeInsetLeft()); + viewportMetrics.paddingTop = + Math.max( + Math.max(viewportMetrics.paddingTop, waterfallInsets.top), + cutout.getSafeInsetTop()); + viewportMetrics.paddingRight = + Math.max( + Math.max(viewportMetrics.paddingRight, waterfallInsets.right), + cutout.getSafeInsetRight()); + viewportMetrics.paddingBottom = + Math.max( + Math.max(viewportMetrics.paddingBottom, waterfallInsets.bottom), + cutout.getSafeInsetBottom()); + viewportMetrics.paddingLeft = + Math.max( + Math.max(viewportMetrics.paddingLeft, waterfallInsets.left), + cutout.getSafeInsetLeft()); } - } else { // We zero the left and/or right sides to prevent the padding the // navigation bar would have caused. @@ -584,7 +602,6 @@ public final WindowInsets onApplyWindowInsets(@NonNull WindowInsets insets) { viewportMetrics.viewInsetLeft = 0; } - Log.v( TAG, "Updating window insets (onApplyWindowInsets()):\n" diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java index 9cf58e6eeb63c..26f482652ac57 100644 --- a/shell/platform/android/io/flutter/view/FlutterView.java +++ b/shell/platform/android/io/flutter/view/FlutterView.java @@ -589,7 +589,8 @@ private int guessBottomKeyboardInset(WindowInsets insets) { @RequiresApi(20) @SuppressLint({"InlinedApi", "NewApi"}) public final WindowInsets onApplyWindowInsets(WindowInsets insets) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + // getSystemGestureInsets() was introduced in API 29 and immediately deprecated in 30. + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { Insets systemGestureInsets = insets.getSystemGestureInsets(); mMetrics.systemGestureInsetTop = systemGestureInsets.top; mMetrics.systemGestureInsetRight = systemGestureInsets.right; @@ -601,9 +602,8 @@ public final WindowInsets onApplyWindowInsets(WindowInsets insets) { boolean navigationBarVisible = (SYSTEM_UI_FLAG_HIDE_NAVIGATION & getWindowSystemUiVisibility()) == 0; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - int mask = android.view.WindowInsets.Type.ime(); + int mask = 0; if (navigationBarVisible) { mask = mask | android.view.WindowInsets.Type.navigationBars(); } @@ -611,29 +611,46 @@ public final WindowInsets onApplyWindowInsets(WindowInsets insets) { mask = mask | android.view.WindowInsets.Type.statusBars(); } Insets uiInsets = insets.getInsets(mask); - mMetrics.physicalPaddingTop = uiInsets.top; mMetrics.physicalPaddingRight = uiInsets.right; - mMetrics.physicalPaddingBottom = 0; + mMetrics.physicalPaddingBottom = uiInsets.bottom; mMetrics.physicalPaddingLeft = uiInsets.left; - mMetrics.physicalViewInsetTop = 0; - mMetrics.physicalViewInsetRight = 0; - mMetrics.physicalViewInsetBottom = uiInsets.bottom; - mMetrics.physicalViewInsetLeft = 0; + Insets imeInsets = insets.getInsets(android.view.WindowInsets.Type.ime()); + mMetrics.physicalViewInsetTop = imeInsets.top; + mMetrics.physicalViewInsetRight = imeInsets.right; + mMetrics.physicalViewInsetBottom = imeInsets.bottom; // Typically, only bottom is non-zero + mMetrics.physicalViewInsetLeft = imeInsets.left; + + Insets systemGestureInsets = insets.getInsets(android.view.WindowInsets.Type.systemGestures()); + mMetrics.systemGestureInsetTop = systemGestureInsets.top; + mMetrics.systemGestureInsetRight = systemGestureInsets.right; + mMetrics.systemGestureInsetBottom = systemGestureInsets.bottom; + mMetrics.systemGestureInsetLeft = systemGestureInsets.left; // TODO(garyq): Expose the full rects of the display cutout. - // Take the max of the display cutout insets and existing insets to merge them - DisplayCutout cutout = insets.getCutout(); + // Take the max of the display cutout insets and existing padding to merge them + DisplayCutout cutout = insets.getDisplayCutout(); if (cutout != null) { Insets waterfallInsets = cutout.getWaterfallInsets(); - mMetrics.systemGestureInsetTop = Math.max(Math.max(mMetrics.systemGestureInsetTop, waterfallInsets.top), cutout.getSafeInsetTop()); - mMetrics.systemGestureInsetRight = Math.max(Math.max(mMetrics.systemGestureInsetRight, waterfallInsets.right), cutout.getSafeInsetRight()); - mMetrics.systemGestureInsetBottom = Math.max(Math.max(mMetrics.systemGestureInsetBottom, waterfallInsets.bottom), cutout.getSafeInsetBottom()); - mMetrics.systemGestureInsetLeft = Math.max(Math.max(mMetrics.systemGestureInsetLeft, waterfallInsets.left), cutout.getSafeInsetLeft()); + mMetrics.physicalPaddingTop = + Math.max( + Math.max(mMetrics.physicalPaddingTop, waterfallInsets.top), + cutout.getSafeInsetTop()); + mMetrics.physicalPaddingRight = + Math.max( + Math.max(mMetrics.physicalPaddingRight, waterfallInsets.right), + cutout.getSafeInsetRight()); + mMetrics.physicalPaddingBottom = + Math.max( + Math.max(mMetrics.physicalPaddingBottom, waterfallInsets.bottom), + cutout.getSafeInsetBottom()); + mMetrics.physicalPaddingLeft = + Math.max( + Math.max(mMetrics.physicalPaddingLeft, waterfallInsets.left), + cutout.getSafeInsetLeft()); } - } else { // We zero the left and/or right sides to prevent the padding the // navigation bar would have caused. 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 bd414349474f5..45ea225062b78 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -496,7 +496,6 @@ public void systemInsetDisplayCutoutSimple() { ((WindowManager) RuntimeEnvironment.systemContext.getSystemService(Context.WINDOW_SERVICE)) .getDefaultDisplay()); - // display.setRotation(0); assertEquals(0, flutterView.getSystemUiVisibility()); when(flutterView.getWindowSystemUiVisibility()).thenReturn(0); when(flutterView.getContext()).thenReturn(RuntimeEnvironment.systemContext); @@ -529,20 +528,20 @@ public void systemInsetDisplayCutoutSimple() { Insets waterfallInsets = Insets.of(200, 0, 200, 0); when(displayCutout.getWaterfallInsets()).thenReturn(waterfallInsets); - when(displatCutout.getSafeInsetTop()).thenReturn(150); - when(displatCutout.getSafeInsetBottom()).thenReturn(150); - when(displatCutout.getSafeInsetLeft()).thenReturn(150); - when(displatCutout.getSafeInsetRight()).thenReturn(150); + when(displayCutout.getSafeInsetTop()).thenReturn(150); + when(displayCutout.getSafeInsetBottom()).thenReturn(150); + when(displayCutout.getSafeInsetLeft()).thenReturn(150); + when(displayCutout.getSafeInsetRight()).thenReturn(150); flutterView.onApplyWindowInsets(windowInsets); verify(flutterRenderer, times(2)).setViewportMetrics(viewportMetricsCaptor.capture()); - assertEquals(150, viewportMetricsCaptor.getValue().systemGestureInsetTop); - assertEquals(150, viewportMetricsCaptor.getValue().systemGestureInsetBottom); - assertEquals(200, viewportMetricsCaptor.getValue().systemGestureInsetLeft); - assertEquals(200, viewportMetricsCaptor.getValue().systemGestureInsetRight); + assertEquals(150, viewportMetricsCaptor.getValue().paddingTop); + assertEquals(150, viewportMetricsCaptor.getValue().paddingBottom); + assertEquals(200, viewportMetricsCaptor.getValue().paddingLeft); + assertEquals(200, viewportMetricsCaptor.getValue().paddingRight); - assertEquals(100, viewportMetricsCaptor.getValue().paddingTop); + assertEquals(100, viewportMetricsCaptor.getValue().viewInsetTop); } @Test From b3cc87fa558805a28661992c16716f98fbc56101 Mon Sep 17 00:00:00 2001 From: garyqian Date: Tue, 1 Sep 2020 21:25:17 -0700 Subject: [PATCH 7/8] Formatting --- .../android/io/flutter/embedding/android/FlutterView.java | 3 ++- shell/platform/android/io/flutter/view/FlutterView.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 4aec99b742103..2f1942034a7e5 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -542,7 +542,8 @@ public final WindowInsets onApplyWindowInsets(@NonNull WindowInsets insets) { viewportMetrics.viewInsetBottom = imeInsets.bottom; // Typically, only bottom is non-zero viewportMetrics.viewInsetLeft = imeInsets.left; - Insets systemGestureInsets = insets.getInsets(android.view.WindowInsets.Type.systemGestures()); + Insets systemGestureInsets = + insets.getInsets(android.view.WindowInsets.Type.systemGestures()); viewportMetrics.systemGestureInsetTop = systemGestureInsets.top; viewportMetrics.systemGestureInsetRight = systemGestureInsets.right; viewportMetrics.systemGestureInsetBottom = systemGestureInsets.bottom; diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java index 26f482652ac57..2ed917b9f918d 100644 --- a/shell/platform/android/io/flutter/view/FlutterView.java +++ b/shell/platform/android/io/flutter/view/FlutterView.java @@ -622,7 +622,8 @@ public final WindowInsets onApplyWindowInsets(WindowInsets insets) { mMetrics.physicalViewInsetBottom = imeInsets.bottom; // Typically, only bottom is non-zero mMetrics.physicalViewInsetLeft = imeInsets.left; - Insets systemGestureInsets = insets.getInsets(android.view.WindowInsets.Type.systemGestures()); + Insets systemGestureInsets = + insets.getInsets(android.view.WindowInsets.Type.systemGestures()); mMetrics.systemGestureInsetTop = systemGestureInsets.top; mMetrics.systemGestureInsetRight = systemGestureInsets.right; mMetrics.systemGestureInsetBottom = systemGestureInsets.bottom; From 88e279ff356e0334a67443d31258df701e91d629 Mon Sep 17 00:00:00 2001 From: garyqian Date: Tue, 1 Sep 2020 21:38:00 -0700 Subject: [PATCH 8/8] Fix test --- .../test/io/flutter/embedding/android/FlutterViewTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 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 45ea225062b78..ccafbc1c16039 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -22,6 +22,7 @@ import android.media.Image; import android.media.Image.Plane; import android.media.ImageReader; +import android.view.DisplayCutout; import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; @@ -523,8 +524,8 @@ public void systemInsetDisplayCutoutSimple() { when(windowInsets.getSystemWindowInsetLeft()).thenReturn(-1); when(windowInsets.getSystemWindowInsetRight()).thenReturn(-1); when(windowInsets.getInsets(anyInt())).thenReturn(insets); - when(windowInsets.getSystemGestureInsets(anyInt())).thenReturn(systemGestureInsets); - when(windowInsets.getCutout()).thenReturn(displayCutout); + when(windowInsets.getSystemGestureInsets()).thenReturn(systemGestureInsets); + when(windowInsets.getDisplayCutout()).thenReturn(displayCutout); Insets waterfallInsets = Insets.of(200, 0, 200, 0); when(displayCutout.getWaterfallInsets()).thenReturn(waterfallInsets);