From 871af2eec84a330a71fb7be34f5e2ebf7e4e3447 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Mon, 9 Sep 2024 15:43:42 -0400 Subject: [PATCH 1/6] Add back FlutterViewTest.java --- .../embedding/android/FlutterViewTest.java | 1104 +++++++++++++++++ 1 file changed, 1104 insertions(+) create mode 100644 shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java new file mode 100644 index 0000000000000..5371e05b58bbf --- /dev/null +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -0,0 +1,1104 @@ +package io.flutter.embedding.android; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Insets; +import android.graphics.Rect; +import android.graphics.Region; +import android.hardware.HardwareBuffer; +import android.media.Image; +import android.media.Image.Plane; +import android.media.ImageReader; +import android.os.Build; +import android.provider.Settings; +import android.view.DisplayCutout; +import android.view.Surface; +import android.view.View; +import android.view.ViewGroup; +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.embedding.engine.FlutterEngine; +import io.flutter.embedding.engine.FlutterJNI; +import io.flutter.embedding.engine.loader.FlutterLoader; +import io.flutter.embedding.engine.renderer.FlutterRenderer; +import io.flutter.embedding.engine.systemchannels.SettingsChannel; +import io.flutter.plugin.platform.PlatformViewsController; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.robolectric.Robolectric; +import org.robolectric.Shadows; +import org.robolectric.annotation.Config; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.shadows.ShadowDisplay; + +@Config(manifest = Config.NONE) +@RunWith(AndroidJUnit4.class) +@TargetApi(30) +public class FlutterViewTest { + private final Context ctx = ApplicationProvider.getApplicationContext(); + @Mock FlutterJNI mockFlutterJni; + @Mock FlutterLoader mockFlutterLoader; + @Spy PlatformViewsController platformViewsController; + + @Before + public void setUp() { + MockitoAnnotations.openMocks(this); + when(mockFlutterJni.isAttached()).thenReturn(true); + } + + @Test + public void attachToFlutterEngine_alertsPlatformViews() { + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); + FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); + when(flutterEngine.getPlatformViewsController()).thenReturn(platformViewsController); + + flutterView.attachToFlutterEngine(flutterEngine); + + verify(platformViewsController, times(1)).attachToView(flutterView); + } + + @Test + public void flutterView_importantForAutofillDoesNotExcludeDescendants() { + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); + + // Value should not exclude descendants because platform views are added as child views and + // can be eligible for autofill (e.g. a WebView). + assertEquals(View.IMPORTANT_FOR_AUTOFILL_YES, flutterView.getImportantForAutofill()); + } + + @Test + public void detachFromFlutterEngine_alertsPlatformViews() { + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); + FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); + when(flutterEngine.getPlatformViewsController()).thenReturn(platformViewsController); + + flutterView.attachToFlutterEngine(flutterEngine); + flutterView.detachFromFlutterEngine(); + + verify(platformViewsController, times(1)).detachFromView(); + } + + @Test + public void detachFromFlutterEngine_turnsOffA11y() { + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); + FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); + FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); + when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); + + flutterView.attachToFlutterEngine(flutterEngine); + flutterView.detachFromFlutterEngine(); + + verify(flutterRenderer, times(1)).setSemanticsEnabled(false); + } + + @Test + public void detachFromFlutterEngine_revertImageView() { + FlutterView flutterView = new FlutterView(ctx); + FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); + + flutterView.attachToFlutterEngine(flutterEngine); + assertFalse(flutterView.renderSurface instanceof FlutterImageView); + + flutterView.convertToImageView(); + assertTrue(flutterView.renderSurface instanceof FlutterImageView); + + flutterView.detachFromFlutterEngine(); + assertFalse(flutterView.renderSurface instanceof FlutterImageView); + } + + @Test + public void detachFromFlutterEngine_removeImageView() { + FlutterView flutterView = new FlutterView(ctx); + FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); + + flutterView.attachToFlutterEngine(flutterEngine); + flutterView.convertToImageView(); + assertEquals(flutterView.getChildCount(), 2); + View view = flutterView.getChildAt(1); + assertTrue(view instanceof FlutterImageView); + + flutterView.detachFromFlutterEngine(); + assertEquals(flutterView.getChildCount(), 1); + view = flutterView.getChildAt(0); + assertFalse(view instanceof FlutterImageView); + } + + @Test + public void detachFromFlutterEngine_closesImageView() { + FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); + + FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); + when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); + + FlutterImageView imageViewMock = mock(FlutterImageView.class); + when(imageViewMock.getAttachedRenderer()).thenReturn(flutterRenderer); + + FlutterView flutterView = spy(new FlutterView(ctx)); + when(flutterView.createImageView()).thenReturn(imageViewMock); + + flutterView.attachToFlutterEngine(flutterEngine); + + assertFalse(flutterView.renderSurface == imageViewMock); + + flutterView.convertToImageView(); + assertTrue(flutterView.renderSurface == imageViewMock); + + flutterView.detachFromFlutterEngine(); + assertFalse(flutterView.renderSurface == imageViewMock); + verify(imageViewMock, times(1)).closeImageReader(); + } + + @Test + public void flutterImageView_revertImageViewAndAvoidNPE() { + FlutterView flutterView = new FlutterView(ctx); + FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); + FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); + when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); + + flutterView.attachToFlutterEngine(flutterEngine); + flutterView.convertToImageView(); + assertTrue(flutterView.renderSurface instanceof FlutterImageView); + + // Register a `FlutterUiDisplayListener` callback. + // During callback execution it will invoke `flutterImageView.detachFromRenderer()`. + flutterView.revertImageView( + () -> { + // No-op + }); + assertFalse(flutterView.renderSurface instanceof FlutterImageView); + + flutterView.detachFromFlutterEngine(); + assertEquals(null, flutterView.getCurrentImageSurface()); + + // Invoke all registered `FlutterUiDisplayListener` callback + mockFlutterJni.onFirstFrame(); + } + + @Test + public void onConfigurationChanged_fizzlesWhenNullEngine() { + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); + FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); + + Configuration configuration = ctx.getResources().getConfiguration(); + // 1 invocation of channels. + flutterView.attachToFlutterEngine(flutterEngine); + flutterView.onConfigurationChanged(configuration); + flutterView.detachFromFlutterEngine(); + + // Should fizzle. + flutterView.onConfigurationChanged(configuration); + + verify(flutterEngine, times(1)).getLocalizationPlugin(); + verify(flutterEngine, times(2)).getSettingsChannel(); + } + + @Test + public void onConfigurationChanged_notifiesEngineOfDisplaySize() { + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); + FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); + + Configuration configuration = ctx.getResources().getConfiguration(); + + flutterView.attachToFlutterEngine(flutterEngine); + flutterView.onConfigurationChanged(configuration); + + verify(flutterEngine, times(1)) + .updateDisplayMetrics(any(Float.class), any(Float.class), any(Float.class)); + } + + // TODO(mattcarroll): turn this into an e2e test. GitHub #42990 + @Test + public void itSendsLightPlatformBrightnessToFlutter() { + // Setup test. + AtomicReference reportedBrightness = + new AtomicReference<>(); + + // FYI - The default brightness is LIGHT, which is why we don't need to configure it. + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); + FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); + + SettingsChannel fakeSettingsChannel = mock(SettingsChannel.class); + SettingsChannel.MessageBuilder fakeMessageBuilder = mock(SettingsChannel.MessageBuilder.class); + when(fakeMessageBuilder.setTextScaleFactor(any(Float.class))).thenReturn(fakeMessageBuilder); + when(fakeMessageBuilder.setNativeSpellCheckServiceDefined(any(Boolean.class))) + .thenReturn(fakeMessageBuilder); + when(fakeMessageBuilder.setBrieflyShowPassword(any(Boolean.class))) + .thenReturn(fakeMessageBuilder); + when(fakeMessageBuilder.setUse24HourFormat(any(Boolean.class))).thenReturn(fakeMessageBuilder); + when(fakeMessageBuilder.setPlatformBrightness(any(SettingsChannel.PlatformBrightness.class))) + .thenAnswer( + new Answer() { + @Override + public SettingsChannel.MessageBuilder answer(InvocationOnMock invocation) + throws Throwable { + reportedBrightness.set( + (SettingsChannel.PlatformBrightness) invocation.getArguments()[0]); + return fakeMessageBuilder; + } + }); + when(fakeSettingsChannel.startMessage()).thenReturn(fakeMessageBuilder); + when(flutterEngine.getSettingsChannel()).thenReturn(fakeSettingsChannel); + + flutterView.attachToFlutterEngine(flutterEngine); + + // Execute behavior under test. + flutterView.sendUserSettingsToFlutter(); + + // Verify results. + assertEquals(SettingsChannel.PlatformBrightness.light, reportedBrightness.get()); + } + + // TODO(mattcarroll): turn this into an e2e test. GitHub #42990 + @Test + public void itSendsDarkPlatformBrightnessToFlutter() { + // Setup test. + AtomicReference reportedBrightness = + new AtomicReference<>(); + + Context spiedContext = spy(Robolectric.setupActivity(Activity.class)); + + Resources spiedResources = spy(spiedContext.getResources()); + when(spiedContext.getResources()).thenReturn(spiedResources); + + Configuration spiedConfiguration = spy(spiedResources.getConfiguration()); + spiedConfiguration.uiMode = + (spiedResources.getConfiguration().uiMode | Configuration.UI_MODE_NIGHT_YES) + & ~Configuration.UI_MODE_NIGHT_NO; + when(spiedResources.getConfiguration()).thenReturn(spiedConfiguration); + + FlutterView flutterView = new FlutterView(spiedContext); + FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); + + SettingsChannel fakeSettingsChannel = mock(SettingsChannel.class); + SettingsChannel.MessageBuilder fakeMessageBuilder = mock(SettingsChannel.MessageBuilder.class); + when(fakeMessageBuilder.setTextScaleFactor(any(Float.class))).thenReturn(fakeMessageBuilder); + when(fakeMessageBuilder.setNativeSpellCheckServiceDefined(any(Boolean.class))) + .thenReturn(fakeMessageBuilder); + when(fakeMessageBuilder.setBrieflyShowPassword(any(Boolean.class))) + .thenReturn(fakeMessageBuilder); + when(fakeMessageBuilder.setUse24HourFormat(any(Boolean.class))).thenReturn(fakeMessageBuilder); + when(fakeMessageBuilder.setPlatformBrightness(any(SettingsChannel.PlatformBrightness.class))) + .thenAnswer( + new Answer() { + @Override + public SettingsChannel.MessageBuilder answer(InvocationOnMock invocation) + throws Throwable { + reportedBrightness.set( + (SettingsChannel.PlatformBrightness) invocation.getArguments()[0]); + return fakeMessageBuilder; + } + }); + when(fakeSettingsChannel.startMessage()).thenReturn(fakeMessageBuilder); + when(flutterEngine.getSettingsChannel()).thenReturn(fakeSettingsChannel); + + // Execute behavior under test. + flutterView.attachToFlutterEngine(flutterEngine); + flutterView.sendUserSettingsToFlutter(); + + // Verify results. + assertEquals(SettingsChannel.PlatformBrightness.dark, reportedBrightness.get()); + } + + @Test + public void itSendsTextShowPasswordToFrameworkOnAttach() { + // Setup test. + AtomicReference reportedShowPassword = new AtomicReference<>(); + + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); + FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); + Settings.System.putInt( + flutterView.getContext().getContentResolver(), Settings.System.TEXT_SHOW_PASSWORD, 1); + + SettingsChannel fakeSettingsChannel = mock(SettingsChannel.class); + SettingsChannel.MessageBuilder fakeMessageBuilder = mock(SettingsChannel.MessageBuilder.class); + when(fakeMessageBuilder.setTextScaleFactor(any(Float.class))).thenReturn(fakeMessageBuilder); + when(fakeMessageBuilder.setNativeSpellCheckServiceDefined(any(Boolean.class))) + .thenReturn(fakeMessageBuilder); + when(fakeMessageBuilder.setPlatformBrightness(any(SettingsChannel.PlatformBrightness.class))) + .thenReturn(fakeMessageBuilder); + when(fakeMessageBuilder.setUse24HourFormat(any(Boolean.class))).thenReturn(fakeMessageBuilder); + when(fakeMessageBuilder.setBrieflyShowPassword(any(Boolean.class))) + .thenAnswer( + new Answer() { + @Override + public SettingsChannel.MessageBuilder answer(InvocationOnMock invocation) + throws Throwable { + reportedShowPassword.set((Boolean) invocation.getArguments()[0]); + return fakeMessageBuilder; + } + }); + when(fakeSettingsChannel.startMessage()).thenReturn(fakeMessageBuilder); + when(flutterEngine.getSettingsChannel()).thenReturn(fakeSettingsChannel); + + flutterView.attachToFlutterEngine(flutterEngine); + + // Verify results. + assertTrue(reportedShowPassword.get()); + } + + public void itSendsTextHidePasswordToFrameworkOnAttach() { + // Setup test. + AtomicReference reportedShowPassword = new AtomicReference<>(); + + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); + FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); + Settings.System.putInt( + flutterView.getContext().getContentResolver(), Settings.System.TEXT_SHOW_PASSWORD, 0); + + SettingsChannel fakeSettingsChannel = mock(SettingsChannel.class); + SettingsChannel.MessageBuilder fakeMessageBuilder = mock(SettingsChannel.MessageBuilder.class); + when(fakeMessageBuilder.setTextScaleFactor(any(Float.class))).thenReturn(fakeMessageBuilder); + when(fakeMessageBuilder.setPlatformBrightness(any(SettingsChannel.PlatformBrightness.class))) + .thenReturn(fakeMessageBuilder); + when(fakeMessageBuilder.setUse24HourFormat(any(Boolean.class))).thenReturn(fakeMessageBuilder); + when(fakeMessageBuilder.setBrieflyShowPassword(any(Boolean.class))) + .thenAnswer( + new Answer() { + @Override + public SettingsChannel.MessageBuilder answer(InvocationOnMock invocation) + throws Throwable { + reportedShowPassword.set((Boolean) invocation.getArguments()[0]); + return fakeMessageBuilder; + } + }); + when(fakeSettingsChannel.startMessage()).thenReturn(fakeMessageBuilder); + when(flutterEngine.getSettingsChannel()).thenReturn(fakeSettingsChannel); + + flutterView.attachToFlutterEngine(flutterEngine); + + // Verify results. + assertFalse(reportedShowPassword.get()); + } + + // 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, + shadows = { + FlutterViewTest.ShadowFullscreenView.class, + FlutterViewTest.ShadowFullscreenViewGroup.class + }) + public void setPaddingTopToZeroForFullscreenMode() { + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); + 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); + + // Then we simulate the system applying a window inset. + WindowInsets windowInsets = + new WindowInsets.Builder() + .setInsets( + android.view.WindowInsets.Type.navigationBars() + | android.view.WindowInsets.Type.statusBars(), + Insets.of(100, 100, 100, 100)) + .build(); + flutterView.onApplyWindowInsets(windowInsets); + + // Verify. + verify(flutterRenderer, times(3)).setViewportMetrics(viewportMetricsCaptor.capture()); + validateViewportMetricPadding(viewportMetricsCaptor, 100, 100, 100, 100); + } + + // This test uses the pre-API 30 Algorithm for window insets. + @Test + @TargetApi(28) + @Config( + sdk = 28, + shadows = { + FlutterViewTest.ShadowFullscreenView.class, + FlutterViewTest.ShadowFullscreenViewGroup.class + }) + public void setPaddingTopToZeroForFullscreenModeLegacy() { + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); + 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); + + // Then we simulate the system applying a window inset. + WindowInsets windowInsets = mock(WindowInsets.class); + mockSystemWindowInsets(windowInsets, 100, 100, 100, 100); + flutterView.onApplyWindowInsets(windowInsets); + + // Verify. + verify(flutterRenderer, times(2)).setViewportMetrics(viewportMetricsCaptor.capture()); + validateViewportMetricPadding(viewportMetricsCaptor, 100, 0, 100, 0); + } + + // 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 reportSystemInsetWhenNotFullscreen() { + // Without custom shadows, the default system ui visibility flags is 0. + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); + assertEquals(0, flutterView.getSystemUiVisibility()); + + 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); + + // Then we simulate the system applying a window inset. + WindowInsets windowInsets = + new WindowInsets.Builder() + .setInsets( + android.view.WindowInsets.Type.navigationBars() + | android.view.WindowInsets.Type.statusBars(), + Insets.of(100, 100, 100, 100)) + .build(); + flutterView.onApplyWindowInsets(windowInsets); + + // Verify. + verify(flutterRenderer, times(3)).setViewportMetrics(viewportMetricsCaptor.capture()); + // Top padding is reported as-is. + validateViewportMetricPadding(viewportMetricsCaptor, 100, 100, 100, 100); + } + + // This test uses the pre-API 30 Algorithm for window insets. + @Test + @TargetApi(28) + @Config(sdk = 28) + public void reportSystemInsetWhenNotFullscreenLegacy() { + // Without custom shadows, the default system ui visibility flags is 0. + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); + assertEquals(0, flutterView.getSystemUiVisibility()); + + 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); + + // Then we simulate the system applying a window inset. + WindowInsets windowInsets = mock(WindowInsets.class); + mockSystemWindowInsets(windowInsets, 100, 100, 100, 100); + flutterView.onApplyWindowInsets(windowInsets); + + // Verify. + verify(flutterRenderer, times(2)).setViewportMetrics(viewportMetricsCaptor.capture()); + // Top padding is reported as-is. + validateViewportMetricPadding(viewportMetricsCaptor, 100, 100, 100, 0); + } + + @Test + @Config(minSdk = 23, maxSdk = 29, qualifiers = "land") + public void systemInsetHandlesFullscreenNavbarRight() { + FlutterView flutterView = spy(new FlutterView(ctx)); + setExpectedDisplayRotation(Surface.ROTATION_90); + assertEquals(0, flutterView.getSystemUiVisibility()); + when(flutterView.getWindowSystemUiVisibility()) + .thenReturn(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); + 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); + + // Then we simulate the system applying a window inset. + WindowInsets windowInsets = mock(WindowInsets.class); + mockSystemWindowInsets(windowInsets, 100, 100, 100, 100); + mockSystemGestureInsetsIfNeed(windowInsets); + + flutterView.onApplyWindowInsets(windowInsets); + + verify(flutterRenderer, times(2)).setViewportMetrics(viewportMetricsCaptor.capture()); + // Top padding is removed due to full screen. + // Right padding is zero because the rotation is 90deg + // Bottom padding is removed due to hide navigation. + validateViewportMetricPadding(viewportMetricsCaptor, 100, 0, 0, 0); + } + + @Test + @Config(minSdk = 20, maxSdk = 22, qualifiers = "land") + public void systemInsetHandlesFullscreenNavbarRightBelowSDK23() { + FlutterView flutterView = spy(new FlutterView(ctx)); + setExpectedDisplayRotation(Surface.ROTATION_270); + assertEquals(0, flutterView.getSystemUiVisibility()); + when(flutterView.getWindowSystemUiVisibility()) + .thenReturn(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); + 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); + + // Then we simulate the system applying a window inset. + WindowInsets windowInsets = mock(WindowInsets.class); + mockSystemWindowInsets(windowInsets, 100, 100, 100, 100); + mockSystemGestureInsetsIfNeed(windowInsets); + + flutterView.onApplyWindowInsets(windowInsets); + + verify(flutterRenderer, times(2)).setViewportMetrics(viewportMetricsCaptor.capture()); + // Top padding is removed due to full screen. + // Right padding is zero because the rotation is 270deg under SDK 23 + // Bottom padding is removed due to hide navigation. + validateViewportMetricPadding(viewportMetricsCaptor, 100, 0, 0, 0); + } + + @Test + @Config(minSdk = 23, maxSdk = 29, qualifiers = "land") + public void systemInsetHandlesFullscreenNavbarLeft() { + FlutterView flutterView = spy(new FlutterView(ctx)); + setExpectedDisplayRotation(Surface.ROTATION_270); + assertEquals(0, flutterView.getSystemUiVisibility()); + when(flutterView.getWindowSystemUiVisibility()) + .thenReturn(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); + 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); + + // Then we simulate the system applying a window inset. + WindowInsets windowInsets = mock(WindowInsets.class); + mockSystemWindowInsets(windowInsets, 100, 100, 100, 100); + mockSystemGestureInsetsIfNeed(windowInsets); + + flutterView.onApplyWindowInsets(windowInsets); + + verify(flutterRenderer, times(2)).setViewportMetrics(viewportMetricsCaptor.capture()); + // Left padding is zero because the rotation is 270deg + // Top padding is removed due to full screen. + // Bottom padding is removed due to hide navigation. + validateViewportMetricPadding(viewportMetricsCaptor, 0, 0, 100, 0); + } + + // 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, qualifiers = "land") + public void systemInsetGetInsetsFullscreen() { + FlutterView flutterView = spy(new FlutterView(ctx)); + setExpectedDisplayRotation(Surface.ROTATION_270); + assertEquals(0, flutterView.getSystemUiVisibility()); + when(flutterView.getWindowSystemUiVisibility()) + .thenReturn(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); + 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(10, 20, 30, 40); + // Then we simulate the system applying a window inset. + WindowInsets windowInsets = mock(WindowInsets.class); + mockSystemWindowInsets(windowInsets, -1, -1, -1, -1); + when(windowInsets.getInsets(anyInt())).thenReturn(insets); + + flutterView.onApplyWindowInsets(windowInsets); + + verify(flutterRenderer, times(2)).setViewportMetrics(viewportMetricsCaptor.capture()); + validateViewportMetricPadding(viewportMetricsCaptor, 10, 20, 30, 40); + } + + // This test uses the pre-API 30 Algorithm for window insets. + @Test + @TargetApi(28) + @Config(sdk = 28, qualifiers = "land") + public void systemInsetGetInsetsFullscreenLegacy() { + FlutterView flutterView = spy(new FlutterView(ctx)); + setExpectedDisplayRotation(Surface.ROTATION_270); + assertEquals(0, flutterView.getSystemUiVisibility()); + when(flutterView.getWindowSystemUiVisibility()) + .thenReturn(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); + 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); + + // Then we simulate the system applying a window inset. + WindowInsets windowInsets = mock(WindowInsets.class); + mockSystemWindowInsets(windowInsets, 102, 100, 103, 101); + + flutterView.onApplyWindowInsets(windowInsets); + + verify(flutterRenderer, times(2)).setViewportMetrics(viewportMetricsCaptor.capture()); + // Left padding is zero because the rotation is 270deg + // Top padding is removed due to full screen. + // Bottom padding is removed due to hide navigation. + validateViewportMetricPadding(viewportMetricsCaptor, 0, 0, 103, 0); + } + + // 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, qualifiers = "land") + public void systemInsetDisplayCutoutSimple() { + 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); + + flutterView.onApplyWindowInsets(windowInsets); + + verify(flutterRenderer, times(2)).setViewportMetrics(viewportMetricsCaptor.capture()); + validateViewportMetricPadding(viewportMetricsCaptor, 200, 150, 200, 150); + + assertEquals(100, viewportMetricsCaptor.getValue().viewInsetTop); + } + + @Test + 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 + // https://www.javadoc.io/doc/org.mockito/mockito-core/1.10.19/org/mockito/Mockito.html#13 + doReturn(windowInfoRepo).when(flutterView).createWindowInfoRepo(); + + // When a new FlutterView is attached to the window + flutterView.onAttachedToWindow(); + + // Then the WindowManager callback is registered + verify(windowInfoRepo, times(1)).addWindowLayoutInfoListener(any(), any(), any()); + + // When the FlutterView is detached from the window + flutterView.onDetachedFromWindow(); + + // Then the WindowManager callback is unregistered + verify(windowInfoRepo, times(1)).removeWindowLayoutInfoListener(any()); + } + + @Test + 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); + doReturn(windowInfoRepo).when(flutterView).createWindowInfoRepo(); + FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); + FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); + when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); + + FoldingFeature displayFeature = mock(FoldingFeature.class); + when(displayFeature.getBounds()).thenReturn(new Rect(0, 0, 100, 100)); + when(displayFeature.getOcclusionType()).thenReturn(FoldingFeature.OcclusionType.FULL); + when(displayFeature.getState()).thenReturn(FoldingFeature.State.FLAT); + + WindowLayoutInfo testWindowLayout = new WindowLayoutInfo(Arrays.asList(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); + flutterView.onAttachedToWindow(); + ArgumentCaptor> wmConsumerCaptor = + ArgumentCaptor.forClass((Class) 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()); + assertEquals( + FlutterRenderer.DisplayFeatureType.HINGE, + viewportMetricsCaptor.getValue().displayFeatures.get(0).type); + assertEquals( + FlutterRenderer.DisplayFeatureState.POSTURE_FLAT, + viewportMetricsCaptor.getValue().displayFeatures.get(0).state); + assertEquals( + new Rect(0, 0, 100, 100), viewportMetricsCaptor.getValue().displayFeatures.get(0).bounds); + } + + @Test + public void flutterImageView_acquiresImageAndInvalidates() { + final ImageReader mockReader = mock(ImageReader.class); + when(mockReader.getMaxImages()).thenReturn(2); + + final FlutterImageView imageView = + spy(new FlutterImageView(ctx, mockReader, FlutterImageView.SurfaceKind.background)); + + final FlutterJNI jni = mock(FlutterJNI.class); + imageView.attachToRenderer(new FlutterRenderer(jni)); + + final Image mockImage = mock(Image.class); + when(mockReader.acquireLatestImage()).thenReturn(mockImage); + + assertTrue(imageView.acquireLatestImage()); + verify(mockReader, times(1)).acquireLatestImage(); + verify(imageView, times(1)).invalidate(); + } + + @Test + @SuppressLint("WrongCall") /*View#onDraw*/ + public void flutterImageView_acquiresImageClosesPreviousImageUnlessNoNewImage() { + final ImageReader mockReader = mock(ImageReader.class); + when(mockReader.getMaxImages()).thenReturn(3); + + final Image mockImage = mock(Image.class); + when(mockImage.getPlanes()).thenReturn(new Plane[0]); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + final HardwareBuffer mockHardwareBuffer = mock(HardwareBuffer.class); + when(mockHardwareBuffer.getUsage()).thenReturn(HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE); + when(mockImage.getHardwareBuffer()).thenReturn(mockHardwareBuffer); + } + // Mock no latest image on the second time + when(mockReader.acquireLatestImage()) + .thenReturn(mockImage) + .thenReturn(null) + .thenReturn(mockImage); + + final FlutterImageView imageView = + spy(new FlutterImageView(ctx, mockReader, FlutterImageView.SurfaceKind.background)); + + final FlutterJNI jni = mock(FlutterJNI.class); + imageView.attachToRenderer(new FlutterRenderer(jni)); + doNothing().when(imageView).invalidate(); + + assertTrue(imageView.acquireLatestImage()); // No previous, acquire latest image + assertFalse( + imageView.acquireLatestImage()); // Mock no image when acquire, don't close, and assertFalse + assertTrue(imageView.acquireLatestImage()); // Acquire latest image and close previous + assertTrue(imageView.acquireLatestImage()); // Acquire latest image and close previous + assertTrue(imageView.acquireLatestImage()); // Acquire latest image and close previous + verify(mockImage, times(3)).close(); // Close 3 times + + imageView.onDraw(mock(Canvas.class)); // Draw latest image + + assertTrue(imageView.acquireLatestImage()); // acquire latest image and close previous + + imageView.onDraw(mock(Canvas.class)); // Draw latest image + imageView.onDraw(mock(Canvas.class)); // Draw latest image + imageView.onDraw(mock(Canvas.class)); // Draw latest image + + verify(mockReader, times(6)).acquireLatestImage(); + } + + @Test + public void flutterImageView_detachFromRendererClosesPreviousImage() { + final ImageReader mockReader = mock(ImageReader.class); + when(mockReader.getMaxImages()).thenReturn(2); + + final Image mockImage = mock(Image.class); + when(mockReader.acquireLatestImage()).thenReturn(mockImage); + + final FlutterImageView imageView = + spy(new FlutterImageView(ctx, mockReader, FlutterImageView.SurfaceKind.background)); + + final FlutterJNI jni = mock(FlutterJNI.class); + imageView.attachToRenderer(new FlutterRenderer(jni)); + + doNothing().when(imageView).invalidate(); + imageView.acquireLatestImage(); + imageView.acquireLatestImage(); + verify(mockImage, times(1)).close(); + + imageView.detachFromRenderer(); + // There's an acquireLatestImage() in detachFromRenderer(), + // so it will be 2 times called close() inside detachFromRenderer() + verify(mockImage, times(3)).close(); + } + + @Test + public void flutterImageView_workaroundWithOnePixelWhenResizeWithZero() { + final ImageReader mockReader = mock(ImageReader.class); + when(mockReader.getMaxImages()).thenReturn(2); + + final FlutterImageView imageView = + spy(new FlutterImageView(ctx, mockReader, FlutterImageView.SurfaceKind.background)); + + final FlutterJNI jni = mock(FlutterJNI.class); + imageView.attachToRenderer(new FlutterRenderer(jni)); + + final Image mockImage = mock(Image.class); + when(mockReader.acquireLatestImage()).thenReturn(mockImage); + + final int incorrectWidth = 0; + final int incorrectHeight = -100; + imageView.resizeIfNeeded(incorrectWidth, incorrectHeight); + assertEquals(1, imageView.getImageReader().getWidth()); + assertEquals(1, imageView.getImageReader().getHeight()); + } + + @Test + public void flutterImageView_closesReader() { + final ImageReader mockReader = mock(ImageReader.class); + when(mockReader.getMaxImages()).thenReturn(1); + + final FlutterImageView imageView = + spy(new FlutterImageView(ctx, mockReader, FlutterImageView.SurfaceKind.background)); + + imageView.closeImageReader(); + verify(mockReader, times(1)).close(); + } + + @Test + public void flutterSurfaceView_GathersTransparentRegion() { + final Region mockRegion = mock(Region.class); + final FlutterSurfaceView surfaceView = new FlutterSurfaceView(ctx); + + surfaceView.setAlpha(0.0f); + assertFalse(surfaceView.gatherTransparentRegion(mockRegion)); + verify(mockRegion, times(0)).op(anyInt(), anyInt(), anyInt(), anyInt(), any()); + + surfaceView.setAlpha(1.0f); + assertTrue(surfaceView.gatherTransparentRegion(mockRegion)); + verify(mockRegion, times(1)).op(0, 0, 0, 0, Region.Op.DIFFERENCE); + } + + @Test + @SuppressLint("PrivateApi") + @Config(sdk = Build.VERSION_CODES.P) + public void findViewByAccessibilityIdTraversal_returnsRootViewOnAndroid28() throws Exception { + FlutterView flutterView = new FlutterView(ctx); + + Method getAccessibilityViewIdMethod = View.class.getDeclaredMethod("getAccessibilityViewId"); + Integer accessibilityViewId = (Integer) getAccessibilityViewIdMethod.invoke(flutterView); + + assertEquals(flutterView, flutterView.findViewByAccessibilityIdTraversal(accessibilityViewId)); + } + + @Test + @Config(sdk = Build.VERSION_CODES.P) + @SuppressLint("PrivateApi") + public void findViewByAccessibilityIdTraversal_returnsChildViewOnAndroid28() throws Exception { + FlutterView flutterView = new FlutterView(ctx); + FrameLayout childView1 = new FrameLayout(ctx); + flutterView.addView(childView1); + + FrameLayout childView2 = new FrameLayout(ctx); + childView1.addView(childView2); + + Method getAccessibilityViewIdMethod = View.class.getDeclaredMethod("getAccessibilityViewId"); + Integer accessibilityViewId = (Integer) getAccessibilityViewIdMethod.invoke(childView2); + + assertEquals(childView2, flutterView.findViewByAccessibilityIdTraversal(accessibilityViewId)); + } + + @Test + @Config(sdk = Build.VERSION_CODES.Q) + @SuppressLint("PrivateApi") + public void findViewByAccessibilityIdTraversal_returnsRootViewOnAndroid29() throws Exception { + FlutterView flutterView = new FlutterView(ctx); + + Method getAccessibilityViewIdMethod = View.class.getDeclaredMethod("getAccessibilityViewId"); + Integer accessibilityViewId = (Integer) getAccessibilityViewIdMethod.invoke(flutterView); + + assertEquals(null, flutterView.findViewByAccessibilityIdTraversal(accessibilityViewId)); + } + + @Test + public void flutterSplashView_itDoesNotCrashOnRestoreInstanceState() { + final FlutterSplashView splashView = new FlutterSplashView(ctx); + splashView.onRestoreInstanceState(View.BaseSavedState.EMPTY_STATE); + // It should not crash and "splashScreenState" should be null. + assertEquals(null, splashView.splashScreenState); + } + + public void ViewportMetrics_initializedPhysicalTouchSlop() { + FlutterView flutterView = new FlutterView(ctx); + FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); + FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); + when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); + + flutterView.attachToFlutterEngine(flutterEngine); + ArgumentCaptor viewportMetricsCaptor = + ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class); + verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); + + assertFalse(-1 == viewportMetricsCaptor.getValue().physicalTouchSlop); + } + + private void setExpectedDisplayRotation(int rotation) { + ShadowDisplay display = + Shadows.shadowOf( + ((WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay()); + display.setRotation(rotation); + } + + private void validateViewportMetricPadding( + ArgumentCaptor viewportMetricsCaptor, + int left, + int top, + int right, + int bottom) { + assertEquals(left, viewportMetricsCaptor.getValue().viewPaddingLeft); + assertEquals(top, viewportMetricsCaptor.getValue().viewPaddingTop); + assertEquals(right, viewportMetricsCaptor.getValue().viewPaddingRight); + assertEquals(bottom, viewportMetricsCaptor.getValue().viewPaddingBottom); + } + + private void mockSystemWindowInsets( + WindowInsets windowInsets, int left, int top, int right, int bottom) { + when(windowInsets.getSystemWindowInsetLeft()).thenReturn(left); + when(windowInsets.getSystemWindowInsetTop()).thenReturn(top); + when(windowInsets.getSystemWindowInsetRight()).thenReturn(right); + when(windowInsets.getSystemWindowInsetBottom()).thenReturn(bottom); + } + + private void mockSystemGestureInsetsIfNeed(WindowInsets windowInsets) { + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { + when(windowInsets.getSystemGestureInsets()).thenReturn(Insets.NONE); + } + } + + /* + * A custom shadow that reports fullscreen flag for system UI visibility + */ + @Implements(View.class) + public static class ShadowFullscreenView { + @Implementation + public int getWindowSystemUiVisibility() { + return View.SYSTEM_UI_FLAG_FULLSCREEN; + } + } + + // ViewGroup is the first shadow in the type hierarchy for FlutterView. Shadows need to mimic + // production classes' view hierarchy. + @Implements(ViewGroup.class) + public static class ShadowFullscreenViewGroup extends ShadowFullscreenView {} +} From f3de1bb2989c9d98fac0dfe39c91905db11b6de8 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Tue, 10 Sep 2024 09:30:06 -0400 Subject: [PATCH 2/6] Delete test for splash screen, add deprecration ignores --- .../io/flutter/embedding/android/FlutterViewTest.java | 11 +++-------- 1 file changed, 3 insertions(+), 8 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 5371e05b58bbf..0920875aab716 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -1031,14 +1031,6 @@ public void findViewByAccessibilityIdTraversal_returnsRootViewOnAndroid29() thro assertEquals(null, flutterView.findViewByAccessibilityIdTraversal(accessibilityViewId)); } - @Test - public void flutterSplashView_itDoesNotCrashOnRestoreInstanceState() { - final FlutterSplashView splashView = new FlutterSplashView(ctx); - splashView.onRestoreInstanceState(View.BaseSavedState.EMPTY_STATE); - // It should not crash and "splashScreenState" should be null. - assertEquals(null, splashView.splashScreenState); - } - public void ViewportMetrics_initializedPhysicalTouchSlop() { FlutterView flutterView = new FlutterView(ctx); FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); @@ -1053,6 +1045,7 @@ public void ViewportMetrics_initializedPhysicalTouchSlop() { assertFalse(-1 == viewportMetricsCaptor.getValue().physicalTouchSlop); } + @SuppressWarnings("deprecation") private void setExpectedDisplayRotation(int rotation) { ShadowDisplay display = Shadows.shadowOf( @@ -1072,6 +1065,7 @@ private void validateViewportMetricPadding( assertEquals(bottom, viewportMetricsCaptor.getValue().viewPaddingBottom); } + @SuppressWarnings("deprecation") private void mockSystemWindowInsets( WindowInsets windowInsets, int left, int top, int right, int bottom) { when(windowInsets.getSystemWindowInsetLeft()).thenReturn(left); @@ -1090,6 +1084,7 @@ private void mockSystemGestureInsetsIfNeed(WindowInsets windowInsets) { * A custom shadow that reports fullscreen flag for system UI visibility */ @Implements(View.class) + @SuppressWarnings("deprecation") public static class ShadowFullscreenView { @Implementation public int getWindowSystemUiVisibility() { From bfe69474eea46e51a9eb2e4f396763cbe85cc463 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Tue, 10 Sep 2024 10:21:24 -0400 Subject: [PATCH 3/6] Supresss warnings about deprecrated methods --- .../embedding/android/FlutterViewTest.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) 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 0920875aab716..d60c25d740441 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -81,6 +81,9 @@ public void setUp() { when(mockFlutterJni.isAttached()).thenReturn(true); } + @SuppressWarnings("deprecation") + // Robolectric.setupActivity + // TODO(reidbaker): https://github.com/flutter/flutter/issues/133151 @Test public void attachToFlutterEngine_alertsPlatformViews() { FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); @@ -92,6 +95,9 @@ public void attachToFlutterEngine_alertsPlatformViews() { verify(platformViewsController, times(1)).attachToView(flutterView); } + @SuppressWarnings("deprecation") + // Robolectric.setupActivity + // TODO(reidbaker): https://github.com/flutter/flutter/issues/133151 @Test public void flutterView_importantForAutofillDoesNotExcludeDescendants() { FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); @@ -101,6 +107,9 @@ public void flutterView_importantForAutofillDoesNotExcludeDescendants() { assertEquals(View.IMPORTANT_FOR_AUTOFILL_YES, flutterView.getImportantForAutofill()); } + @SuppressWarnings("deprecation") + // Robolectric.setupActivity + // TODO(reidbaker): https://github.com/flutter/flutter/issues/133151 @Test public void detachFromFlutterEngine_alertsPlatformViews() { FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); @@ -113,6 +122,9 @@ public void detachFromFlutterEngine_alertsPlatformViews() { verify(platformViewsController, times(1)).detachFromView(); } + @SuppressWarnings("deprecation") + // Robolectric.setupActivity + // TODO(reidbaker): https://github.com/flutter/flutter/issues/133151 @Test public void detachFromFlutterEngine_turnsOffA11y() { FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); @@ -209,6 +221,9 @@ public void flutterImageView_revertImageViewAndAvoidNPE() { mockFlutterJni.onFirstFrame(); } + @SuppressWarnings("deprecation") + // Robolectric.setupActivity + // TODO(reidbaker): https://github.com/flutter/flutter/issues/133151 @Test public void onConfigurationChanged_fizzlesWhenNullEngine() { FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); @@ -227,6 +242,9 @@ public void onConfigurationChanged_fizzlesWhenNullEngine() { verify(flutterEngine, times(2)).getSettingsChannel(); } + @SuppressWarnings("deprecation") + // Robolectric.setupActivity + // TODO(reidbaker): https://github.com/flutter/flutter/issues/133151 @Test public void onConfigurationChanged_notifiesEngineOfDisplaySize() { FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); @@ -241,6 +259,9 @@ public void onConfigurationChanged_notifiesEngineOfDisplaySize() { .updateDisplayMetrics(any(Float.class), any(Float.class), any(Float.class)); } + @SuppressWarnings("deprecation") + // Robolectric.setupActivity + // TODO(reidbaker): https://github.com/flutter/flutter/issues/133151 // TODO(mattcarroll): turn this into an e2e test. GitHub #42990 @Test public void itSendsLightPlatformBrightnessToFlutter() { @@ -283,6 +304,9 @@ public SettingsChannel.MessageBuilder answer(InvocationOnMock invocation) assertEquals(SettingsChannel.PlatformBrightness.light, reportedBrightness.get()); } + @SuppressWarnings("deprecation") + // Robolectric.setupActivity + // TODO(reidbaker): https://github.com/flutter/flutter/issues/133151 // TODO(mattcarroll): turn this into an e2e test. GitHub #42990 @Test public void itSendsDarkPlatformBrightnessToFlutter() { @@ -334,6 +358,9 @@ public SettingsChannel.MessageBuilder answer(InvocationOnMock invocation) assertEquals(SettingsChannel.PlatformBrightness.dark, reportedBrightness.get()); } + @SuppressWarnings("deprecation") + // Robolectric.setupActivity + // TODO(reidbaker): https://github.com/flutter/flutter/issues/133151 @Test public void itSendsTextShowPasswordToFrameworkOnAttach() { // Setup test. @@ -371,6 +398,9 @@ public SettingsChannel.MessageBuilder answer(InvocationOnMock invocation) assertTrue(reportedShowPassword.get()); } + @SuppressWarnings("deprecation") + // Robolectric.setupActivity + // TODO(reidbaker): https://github.com/flutter/flutter/issues/133151 public void itSendsTextHidePasswordToFrameworkOnAttach() { // Setup test. AtomicReference reportedShowPassword = new AtomicReference<>(); @@ -405,6 +435,9 @@ public SettingsChannel.MessageBuilder answer(InvocationOnMock invocation) assertFalse(reportedShowPassword.get()); } + @SuppressWarnings("deprecation") + // Robolectric.setupActivity + // TODO(reidbaker): https://github.com/flutter/flutter/issues/133151 // 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 @@ -445,6 +478,9 @@ public void setPaddingTopToZeroForFullscreenMode() { validateViewportMetricPadding(viewportMetricsCaptor, 100, 100, 100, 100); } + @SuppressWarnings("deprecation") + // Robolectric.setupActivity + // TODO(reidbaker): https://github.com/flutter/flutter/issues/133151 // This test uses the pre-API 30 Algorithm for window insets. @Test @TargetApi(28) @@ -479,6 +515,9 @@ public void setPaddingTopToZeroForFullscreenModeLegacy() { validateViewportMetricPadding(viewportMetricsCaptor, 100, 0, 100, 0); } + @SuppressWarnings("deprecation") + // Robolectric.setupActivity, getSystemUiVisibility + // TODO(reidbaker): https://github.com/flutter/flutter/issues/133151 // 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 @@ -518,6 +557,9 @@ public void reportSystemInsetWhenNotFullscreen() { validateViewportMetricPadding(viewportMetricsCaptor, 100, 100, 100, 100); } + @SuppressWarnings("deprecation") + // Robolectric.setupActivity, getSystemUiVisibility + // TODO(reidbaker): https://github.com/flutter/flutter/issues/133151 // This test uses the pre-API 30 Algorithm for window insets. @Test @TargetApi(28) @@ -551,6 +593,8 @@ public void reportSystemInsetWhenNotFullscreenLegacy() { validateViewportMetricPadding(viewportMetricsCaptor, 100, 100, 100, 0); } + @SuppressWarnings("deprecation") + // getSystemUiVisibility, getWindowSystemUiVisibility, required to test pre api 30 behavior. @Test @Config(minSdk = 23, maxSdk = 29, qualifiers = "land") public void systemInsetHandlesFullscreenNavbarRight() { @@ -587,6 +631,8 @@ public void systemInsetHandlesFullscreenNavbarRight() { validateViewportMetricPadding(viewportMetricsCaptor, 100, 0, 0, 0); } + @SuppressWarnings("deprecation") + // getSystemUiVisibility, getWindowSystemUiVisibility required to test pre api 30 behavior. @Test @Config(minSdk = 20, maxSdk = 22, qualifiers = "land") public void systemInsetHandlesFullscreenNavbarRightBelowSDK23() { @@ -623,6 +669,8 @@ public void systemInsetHandlesFullscreenNavbarRightBelowSDK23() { validateViewportMetricPadding(viewportMetricsCaptor, 100, 0, 0, 0); } + @SuppressWarnings("deprecation") + // getSystemUiVisibility, getWindowSystemUiVisibility required to test pre api 30 behavior. @Test @Config(minSdk = 23, maxSdk = 29, qualifiers = "land") public void systemInsetHandlesFullscreenNavbarLeft() { @@ -659,6 +707,9 @@ public void systemInsetHandlesFullscreenNavbarLeft() { validateViewportMetricPadding(viewportMetricsCaptor, 0, 0, 100, 0); } + @SuppressWarnings("deprecation") + // getSystemUiVisibility, getWindowSystemUiVisibility required to test interop with api 30 + // behavior. // 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 @@ -696,6 +747,8 @@ public void systemInsetGetInsetsFullscreen() { validateViewportMetricPadding(viewportMetricsCaptor, 10, 20, 30, 40); } + @SuppressWarnings("deprecation") + // getSystemUiVisibility, getWindowSystemUiVisibility required to test pre api 30 behavior. // This test uses the pre-API 30 Algorithm for window insets. @Test @TargetApi(28) @@ -733,6 +786,9 @@ public void systemInsetGetInsetsFullscreenLegacy() { validateViewportMetricPadding(viewportMetricsCaptor, 0, 0, 103, 0); } + @SuppressWarnings("deprecation") + // getSystemUiVisibility, getWindowSystemUiVisibility, getSystemGestureInsets required + // to test pre api 30 interop behavior. // 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 @@ -781,6 +837,11 @@ public void systemInsetDisplayCutoutSimple() { assertEquals(100, viewportMetricsCaptor.getValue().viewInsetTop); } + @SuppressWarnings("deprecation") + // Robolectric.setupActivity + // TODO(reidbaker): https://github.com/flutter/flutter/issues/133151 + // getDefaultDisplay + // TODO(jesswrd): https://github.com/flutter/flutter/issues/99421 @Test public void itRegistersAndUnregistersToWindowManager() { Context context = Robolectric.setupActivity(Activity.class); @@ -807,6 +868,11 @@ public void itRegistersAndUnregistersToWindowManager() { verify(windowInfoRepo, times(1)).removeWindowLayoutInfoListener(any()); } + @SuppressWarnings("deprecation") + // Robolectric.setupActivity + // TODO(reidbaker): https://github.com/flutter/flutter/issues/133151 + // getDefaultDisplay + // TODO(jesswrd): https://github.com/flutter/flutter/issues/99421 @Test public void itSendsHingeDisplayFeatureToFlutter() { Context context = Robolectric.setupActivity(Activity.class); @@ -1074,6 +1140,7 @@ private void mockSystemWindowInsets( when(windowInsets.getSystemWindowInsetBottom()).thenReturn(bottom); } + @SuppressWarnings("deprecation") private void mockSystemGestureInsetsIfNeed(WindowInsets windowInsets) { if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { when(windowInsets.getSystemGestureInsets()).thenReturn(Insets.NONE); From 843c0e59423c612bb1dab53f12129a4ef29b4b12 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Tue, 10 Sep 2024 11:00:03 -0400 Subject: [PATCH 4/6] remove failing test --- .../embedding/android/FlutterViewTest.java | 40 ------------------- 1 file changed, 40 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 d60c25d740441..26812ca6358c9 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -358,46 +358,6 @@ public SettingsChannel.MessageBuilder answer(InvocationOnMock invocation) assertEquals(SettingsChannel.PlatformBrightness.dark, reportedBrightness.get()); } - @SuppressWarnings("deprecation") - // Robolectric.setupActivity - // TODO(reidbaker): https://github.com/flutter/flutter/issues/133151 - @Test - public void itSendsTextShowPasswordToFrameworkOnAttach() { - // Setup test. - AtomicReference reportedShowPassword = new AtomicReference<>(); - - FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); - FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); - Settings.System.putInt( - flutterView.getContext().getContentResolver(), Settings.System.TEXT_SHOW_PASSWORD, 1); - - SettingsChannel fakeSettingsChannel = mock(SettingsChannel.class); - SettingsChannel.MessageBuilder fakeMessageBuilder = mock(SettingsChannel.MessageBuilder.class); - when(fakeMessageBuilder.setTextScaleFactor(any(Float.class))).thenReturn(fakeMessageBuilder); - when(fakeMessageBuilder.setNativeSpellCheckServiceDefined(any(Boolean.class))) - .thenReturn(fakeMessageBuilder); - when(fakeMessageBuilder.setPlatformBrightness(any(SettingsChannel.PlatformBrightness.class))) - .thenReturn(fakeMessageBuilder); - when(fakeMessageBuilder.setUse24HourFormat(any(Boolean.class))).thenReturn(fakeMessageBuilder); - when(fakeMessageBuilder.setBrieflyShowPassword(any(Boolean.class))) - .thenAnswer( - new Answer() { - @Override - public SettingsChannel.MessageBuilder answer(InvocationOnMock invocation) - throws Throwable { - reportedShowPassword.set((Boolean) invocation.getArguments()[0]); - return fakeMessageBuilder; - } - }); - when(fakeSettingsChannel.startMessage()).thenReturn(fakeMessageBuilder); - when(flutterEngine.getSettingsChannel()).thenReturn(fakeSettingsChannel); - - flutterView.attachToFlutterEngine(flutterEngine); - - // Verify results. - assertTrue(reportedShowPassword.get()); - } - @SuppressWarnings("deprecation") // Robolectric.setupActivity // TODO(reidbaker): https://github.com/flutter/flutter/issues/133151 From 3c52a1f8feeac43f680a66ef62ce9605e1da499d Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Tue, 10 Sep 2024 13:15:38 -0400 Subject: [PATCH 5/6] remove more failing tests --- .../embedding/android/FlutterViewTest.java | 179 ------------------ 1 file changed, 179 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 26812ca6358c9..a4b6febfc3d17 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -259,105 +259,6 @@ public void onConfigurationChanged_notifiesEngineOfDisplaySize() { .updateDisplayMetrics(any(Float.class), any(Float.class), any(Float.class)); } - @SuppressWarnings("deprecation") - // Robolectric.setupActivity - // TODO(reidbaker): https://github.com/flutter/flutter/issues/133151 - // TODO(mattcarroll): turn this into an e2e test. GitHub #42990 - @Test - public void itSendsLightPlatformBrightnessToFlutter() { - // Setup test. - AtomicReference reportedBrightness = - new AtomicReference<>(); - - // FYI - The default brightness is LIGHT, which is why we don't need to configure it. - FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); - FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); - - SettingsChannel fakeSettingsChannel = mock(SettingsChannel.class); - SettingsChannel.MessageBuilder fakeMessageBuilder = mock(SettingsChannel.MessageBuilder.class); - when(fakeMessageBuilder.setTextScaleFactor(any(Float.class))).thenReturn(fakeMessageBuilder); - when(fakeMessageBuilder.setNativeSpellCheckServiceDefined(any(Boolean.class))) - .thenReturn(fakeMessageBuilder); - when(fakeMessageBuilder.setBrieflyShowPassword(any(Boolean.class))) - .thenReturn(fakeMessageBuilder); - when(fakeMessageBuilder.setUse24HourFormat(any(Boolean.class))).thenReturn(fakeMessageBuilder); - when(fakeMessageBuilder.setPlatformBrightness(any(SettingsChannel.PlatformBrightness.class))) - .thenAnswer( - new Answer() { - @Override - public SettingsChannel.MessageBuilder answer(InvocationOnMock invocation) - throws Throwable { - reportedBrightness.set( - (SettingsChannel.PlatformBrightness) invocation.getArguments()[0]); - return fakeMessageBuilder; - } - }); - when(fakeSettingsChannel.startMessage()).thenReturn(fakeMessageBuilder); - when(flutterEngine.getSettingsChannel()).thenReturn(fakeSettingsChannel); - - flutterView.attachToFlutterEngine(flutterEngine); - - // Execute behavior under test. - flutterView.sendUserSettingsToFlutter(); - - // Verify results. - assertEquals(SettingsChannel.PlatformBrightness.light, reportedBrightness.get()); - } - - @SuppressWarnings("deprecation") - // Robolectric.setupActivity - // TODO(reidbaker): https://github.com/flutter/flutter/issues/133151 - // TODO(mattcarroll): turn this into an e2e test. GitHub #42990 - @Test - public void itSendsDarkPlatformBrightnessToFlutter() { - // Setup test. - AtomicReference reportedBrightness = - new AtomicReference<>(); - - Context spiedContext = spy(Robolectric.setupActivity(Activity.class)); - - Resources spiedResources = spy(spiedContext.getResources()); - when(spiedContext.getResources()).thenReturn(spiedResources); - - Configuration spiedConfiguration = spy(spiedResources.getConfiguration()); - spiedConfiguration.uiMode = - (spiedResources.getConfiguration().uiMode | Configuration.UI_MODE_NIGHT_YES) - & ~Configuration.UI_MODE_NIGHT_NO; - when(spiedResources.getConfiguration()).thenReturn(spiedConfiguration); - - FlutterView flutterView = new FlutterView(spiedContext); - FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); - - SettingsChannel fakeSettingsChannel = mock(SettingsChannel.class); - SettingsChannel.MessageBuilder fakeMessageBuilder = mock(SettingsChannel.MessageBuilder.class); - when(fakeMessageBuilder.setTextScaleFactor(any(Float.class))).thenReturn(fakeMessageBuilder); - when(fakeMessageBuilder.setNativeSpellCheckServiceDefined(any(Boolean.class))) - .thenReturn(fakeMessageBuilder); - when(fakeMessageBuilder.setBrieflyShowPassword(any(Boolean.class))) - .thenReturn(fakeMessageBuilder); - when(fakeMessageBuilder.setUse24HourFormat(any(Boolean.class))).thenReturn(fakeMessageBuilder); - when(fakeMessageBuilder.setPlatformBrightness(any(SettingsChannel.PlatformBrightness.class))) - .thenAnswer( - new Answer() { - @Override - public SettingsChannel.MessageBuilder answer(InvocationOnMock invocation) - throws Throwable { - reportedBrightness.set( - (SettingsChannel.PlatformBrightness) invocation.getArguments()[0]); - return fakeMessageBuilder; - } - }); - when(fakeSettingsChannel.startMessage()).thenReturn(fakeMessageBuilder); - when(flutterEngine.getSettingsChannel()).thenReturn(fakeSettingsChannel); - - // Execute behavior under test. - flutterView.attachToFlutterEngine(flutterEngine); - flutterView.sendUserSettingsToFlutter(); - - // Verify results. - assertEquals(SettingsChannel.PlatformBrightness.dark, reportedBrightness.get()); - } - @SuppressWarnings("deprecation") // Robolectric.setupActivity // TODO(reidbaker): https://github.com/flutter/flutter/issues/133151 @@ -395,86 +296,6 @@ public SettingsChannel.MessageBuilder answer(InvocationOnMock invocation) assertFalse(reportedShowPassword.get()); } - @SuppressWarnings("deprecation") - // Robolectric.setupActivity - // TODO(reidbaker): https://github.com/flutter/flutter/issues/133151 - // 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, - shadows = { - FlutterViewTest.ShadowFullscreenView.class, - FlutterViewTest.ShadowFullscreenViewGroup.class - }) - public void setPaddingTopToZeroForFullscreenMode() { - FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); - 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); - - // Then we simulate the system applying a window inset. - WindowInsets windowInsets = - new WindowInsets.Builder() - .setInsets( - android.view.WindowInsets.Type.navigationBars() - | android.view.WindowInsets.Type.statusBars(), - Insets.of(100, 100, 100, 100)) - .build(); - flutterView.onApplyWindowInsets(windowInsets); - - // Verify. - verify(flutterRenderer, times(3)).setViewportMetrics(viewportMetricsCaptor.capture()); - validateViewportMetricPadding(viewportMetricsCaptor, 100, 100, 100, 100); - } - - @SuppressWarnings("deprecation") - // Robolectric.setupActivity - // TODO(reidbaker): https://github.com/flutter/flutter/issues/133151 - // This test uses the pre-API 30 Algorithm for window insets. - @Test - @TargetApi(28) - @Config( - sdk = 28, - shadows = { - FlutterViewTest.ShadowFullscreenView.class, - FlutterViewTest.ShadowFullscreenViewGroup.class - }) - public void setPaddingTopToZeroForFullscreenModeLegacy() { - FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); - 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); - - // Then we simulate the system applying a window inset. - WindowInsets windowInsets = mock(WindowInsets.class); - mockSystemWindowInsets(windowInsets, 100, 100, 100, 100); - flutterView.onApplyWindowInsets(windowInsets); - - // Verify. - verify(flutterRenderer, times(2)).setViewportMetrics(viewportMetricsCaptor.capture()); - validateViewportMetricPadding(viewportMetricsCaptor, 100, 0, 100, 0); - } - @SuppressWarnings("deprecation") // Robolectric.setupActivity, getSystemUiVisibility // TODO(reidbaker): https://github.com/flutter/flutter/issues/133151 From 212945a0cec524e5ae80577fa9e2f9d29c725d81 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Tue, 10 Sep 2024 14:37:29 -0400 Subject: [PATCH 6/6] formatting --- .../test/io/flutter/embedding/android/FlutterViewTest.java | 1 - 1 file changed, 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 a4b6febfc3d17..c27eef4cdbd16 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -18,7 +18,6 @@ import android.app.Activity; import android.content.Context; import android.content.res.Configuration; -import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Insets; import android.graphics.Rect;