diff --git a/shell/platform/android/io/flutter/Build.java b/shell/platform/android/io/flutter/Build.java index 904bcc392d1c7..09662c6dadb8d 100644 --- a/shell/platform/android/io/flutter/Build.java +++ b/shell/platform/android/io/flutter/Build.java @@ -4,10 +4,13 @@ package io.flutter; +import androidx.annotation.VisibleForTesting; + /** A replacement of utilities from android.os.Build. */ public class Build { /** For use in place of the Android Build.VERSION_CODES class. */ public static class API_LEVELS { + @VisibleForTesting public static final int FLUTTER_MIN = 21; public static final int API_21 = 21; public static final int API_22 = 22; public static final int API_23 = 23; diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index d7c1387da804d..4c6d5448ae7f3 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.database.ContentObserver; import android.graphics.Insets; import android.graphics.Rect; +import android.hardware.display.DisplayManager; import android.os.Build; import android.os.Handler; import android.os.Looper; @@ -21,6 +22,7 @@ import android.text.format.DateFormat; import android.util.AttributeSet; import android.util.SparseArray; +import android.view.Display; import android.view.DisplayCutout; import android.view.KeyEvent; import android.view.MotionEvent; @@ -31,7 +33,6 @@ import android.view.ViewGroup; import android.view.ViewStructure; import android.view.WindowInsets; -import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeProvider; import android.view.autofill.AutofillValue; @@ -597,26 +598,35 @@ protected void setWindowInfoListenerDisplayFeatures(WindowLayoutInfo layoutInfo) // android may decide to place the software navigation bars on the side. When the nav // bar is hidden, the reported insets should be removed to prevent extra useless space // on the sides. - private enum ZeroSides { + @VisibleForTesting + public enum ZeroSides { NONE, LEFT, RIGHT, BOTH } - private ZeroSides calculateShouldZeroSides() { + /** + * This method can be run on APIs 30 and above but its intended use is for 30 and below. + * + * @return some ZeroSides enum + */ + @androidx.annotation.DeprecatedSinceApi(api = API_LEVELS.API_30) + @VisibleForTesting + public ZeroSides calculateShouldZeroSides() { // We get both orientation and rotation because rotation is all 4 // rotations relative to default rotation while orientation is portrait // or landscape. By combining both, we can obtain a more precise measure // of the rotation. Context context = getContext(); int orientation = context.getResources().getConfiguration().orientation; - int rotation = - ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)) - .getDefaultDisplay() - .getRotation(); if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + int rotation = + ((DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE)) + .getDisplay(Display.DEFAULT_DISPLAY) + .getRotation(); + if (rotation == Surface.ROTATION_90) { return ZeroSides.RIGHT; } else if (rotation == Surface.ROTATION_270) { 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 b4c6e040caee0..dc62638fb2e32 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -29,6 +29,7 @@ import android.graphics.Rect; import android.graphics.Region; import android.hardware.HardwareBuffer; +import android.hardware.display.DisplayManager; import android.media.Image; import android.media.Image.Plane; import android.media.ImageReader; @@ -39,13 +40,13 @@ import android.view.Surface; import android.view.View; import android.view.WindowInsets; -import android.view.WindowManager; import android.widget.FrameLayout; import androidx.core.util.Consumer; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.window.layout.FoldingFeature; import androidx.window.layout.WindowLayoutInfo; +import io.flutter.Build.API_LEVELS; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterJNI; import io.flutter.embedding.engine.loader.FlutterLoader; @@ -438,6 +439,48 @@ public void systemInsetHandlesFullscreenNavbarRightBelowSDK23() { validateViewportMetricPadding(viewportMetricsCaptor, 100, 0, 0, 0); } + @Test + @Config(minSdk = API_LEVELS.FLUTTER_MIN, maxSdk = API_LEVELS.API_29, qualifiers = "port") + public void calculateShouldZeroSidesInPortrait() { + FlutterView flutterView = spy(new FlutterView(ctx)); + assertEquals(FlutterView.ZeroSides.NONE, flutterView.calculateShouldZeroSides()); + } + + @Test + @Config(minSdk = API_LEVELS.FLUTTER_MIN, maxSdk = API_LEVELS.API_29, qualifiers = "land") + public void calculateShouldZeroSidesInLandscapeNeutralRotation() { + FlutterView flutterView = spy(new FlutterView(ctx)); + setExpectedDisplayRotation(Surface.ROTATION_0); + assertEquals(FlutterView.ZeroSides.BOTH, flutterView.calculateShouldZeroSides()); + + setExpectedDisplayRotation(Surface.ROTATION_180); + assertEquals(FlutterView.ZeroSides.BOTH, flutterView.calculateShouldZeroSides()); + } + + @Test + @Config(minSdk = API_LEVELS.FLUTTER_MIN, maxSdk = API_LEVELS.API_29, qualifiers = "land") + public void calculateShouldZeroSidesInLandscapeRotation90() { + FlutterView flutterView = spy(new FlutterView(ctx)); + setExpectedDisplayRotation(Surface.ROTATION_90); + assertEquals(FlutterView.ZeroSides.RIGHT, flutterView.calculateShouldZeroSides()); + } + + @Test + @Config(minSdk = API_LEVELS.API_21, maxSdk = API_LEVELS.API_22, qualifiers = "land") + public void calculateShouldZeroSidesInLandscapeRotation270API22() { + FlutterView flutterView = spy(new FlutterView(ctx)); + setExpectedDisplayRotation(Surface.ROTATION_270); + assertEquals(FlutterView.ZeroSides.RIGHT, flutterView.calculateShouldZeroSides()); + } + + @Test + @Config(minSdk = API_LEVELS.API_23, maxSdk = API_LEVELS.API_29, qualifiers = "land") + public void calculateShouldZeroSidesInLandscapeRotation270API23Plus() { + FlutterView flutterView = spy(new FlutterView(ctx)); + setExpectedDisplayRotation(Surface.ROTATION_270); + assertEquals(FlutterView.ZeroSides.LEFT, flutterView.calculateShouldZeroSides()); + } + @SuppressWarnings("deprecation") // getSystemUiVisibility, getWindowSystemUiVisibility required to test pre api 30 behavior. @Test @@ -615,9 +658,6 @@ public void systemInsetDisplayCutoutSimple() { public void itRegistersAndUnregistersToWindowManager() { Context context = Robolectric.setupActivity(Activity.class); FlutterView flutterView = spy(new FlutterView(context)); - ShadowDisplay display = - Shadows.shadowOf( - ((WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay()); WindowInfoRepositoryCallbackAdapterWrapper windowInfoRepo = mock(WindowInfoRepositoryCallbackAdapterWrapper.class); // For reasoning behing using doReturn instead of when, read "Important gotcha" at @@ -646,9 +686,6 @@ public void itRegistersAndUnregistersToWindowManager() { public void itSendsHingeDisplayFeatureToFlutter() { Context context = Robolectric.setupActivity(Activity.class); FlutterView flutterView = spy(new FlutterView(context)); - ShadowDisplay display = - Shadows.shadowOf( - ((WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay()); when(flutterView.getContext()).thenReturn(context); WindowInfoRepositoryCallbackAdapterWrapper windowInfoRepo = mock(WindowInfoRepositoryCallbackAdapterWrapper.class); @@ -1102,10 +1139,10 @@ public SettingsChannel.MessageBuilder answer(InvocationOnMock invocation) @SuppressWarnings("deprecation") private void setExpectedDisplayRotation(int rotation) { - ShadowDisplay display = + ShadowDisplay myDisplay = Shadows.shadowOf( - ((WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay()); - display.setRotation(rotation); + ((DisplayManager) ctx.getSystemService(Context.DISPLAY_SERVICE)).getDisplay(0)); + myDisplay.setRotation(rotation); } private void validateViewportMetricPadding( diff --git a/shell/platform/android/test/io/flutter/plugin/platform/WindowManagerHandlerTest.java b/shell/platform/android/test/io/flutter/plugin/platform/WindowManagerHandlerTest.java index 7e37231774ca9..230172d9e6cf8 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/WindowManagerHandlerTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/WindowManagerHandlerTest.java @@ -109,6 +109,11 @@ public void windowManagerHandler_forwardsAllOtherCallsToDelegate() { @SuppressWarnings("Unchecked cast") Consumer mockListener = (Consumer) mock(Consumer.class); + // Windowmanager's getDefaultDisplay() function is deprecated in API 30 level 30. + // See Android docs here: + // https://developer.android.com/reference/android/view/WindowManager#getDefaultDisplay() + // We expect this behavior because this unit test expects a blind forward that includes + // deprecated function calls. See comment above for more details. windowManagerHandler.getDefaultDisplay(); verify(mockWindowManager).getDefaultDisplay(); diff --git a/tools/android_lint/baseline.xml b/tools/android_lint/baseline.xml index a73601b794503..c3932724a8245 100644 --- a/tools/android_lint/baseline.xml +++ b/tools/android_lint/baseline.xml @@ -1,5 +1,16 @@ - + + + + + @@ -30,7 +41,7 @@ errorLine2=" ~~~~~~~~~~~~">