blob: c7ede28e64ba6a87b8ef9d69f38fdf14ccc28f15 [file] [log] [blame]
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.window.layout.FoldingFeature;
import androidx.window.layout.WindowLayoutInfo;
import io.flutter.TestUtils;
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.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
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(RobolectricTestRunner.class)
@TargetApi(30)
public class FlutterViewTest {
@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(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni));
when(flutterEngine.getPlatformViewsController()).thenReturn(platformViewsController);
flutterView.attachToFlutterEngine(flutterEngine);
verify(platformViewsController, times(1)).attachToView(flutterView);
}
@Test
public void detachFromFlutterEngine_alertsPlatformViews() {
FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class));
FlutterEngine flutterEngine =
spy(new FlutterEngine(RuntimeEnvironment.application, 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(RuntimeEnvironment.application, 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(RuntimeEnvironment.application);
FlutterEngine flutterEngine =
spy(new FlutterEngine(RuntimeEnvironment.application, 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(RuntimeEnvironment.application);
FlutterEngine flutterEngine =
spy(new FlutterEngine(RuntimeEnvironment.application, 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(RuntimeEnvironment.application, 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(RuntimeEnvironment.application));
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 onConfigurationChanged_fizzlesWhenNullEngine() {
FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class));
FlutterEngine flutterEngine =
spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni));
Configuration configuration = RuntimeEnvironment.application.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();
}
// TODO(mattcarroll): turn this into an e2e test. GitHub #42990
@Test
public void itSendsLightPlatformBrightnessToFlutter() {
// Setup test.
AtomicReference<SettingsChannel.PlatformBrightness> 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(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni));
SettingsChannel fakeSettingsChannel = mock(SettingsChannel.class);
SettingsChannel.MessageBuilder fakeMessageBuilder = mock(SettingsChannel.MessageBuilder.class);
when(fakeMessageBuilder.setTextScaleFactor(any(Float.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<SettingsChannel.MessageBuilder>() {
@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<SettingsChannel.PlatformBrightness> 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(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni));
SettingsChannel fakeSettingsChannel = mock(SettingsChannel.class);
SettingsChannel.MessageBuilder fakeMessageBuilder = mock(SettingsChannel.MessageBuilder.class);
when(fakeMessageBuilder.setTextScaleFactor(any(Float.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<SettingsChannel.MessageBuilder>() {
@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<Boolean> reportedShowPassword = new AtomicReference<>();
FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class));
FlutterEngine flutterEngine =
spy(new FlutterEngine(RuntimeEnvironment.application, 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.setPlatformBrightness(any(SettingsChannel.PlatformBrightness.class)))
.thenReturn(fakeMessageBuilder);
when(fakeMessageBuilder.setUse24HourFormat(any(Boolean.class))).thenReturn(fakeMessageBuilder);
when(fakeMessageBuilder.setBrieflyShowPassword(any(Boolean.class)))
.thenAnswer(
new Answer<SettingsChannel.MessageBuilder>() {
@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<Boolean> reportedShowPassword = new AtomicReference<>();
FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class));
FlutterEngine flutterEngine =
spy(new FlutterEngine(RuntimeEnvironment.application, 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<SettingsChannel.MessageBuilder>() {
@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(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<FlutterRenderer.ViewportMetrics> 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(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<FlutterRenderer.ViewportMetrics> 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(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<FlutterRenderer.ViewportMetrics> 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(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<FlutterRenderer.ViewportMetrics> 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)
public void systemInsetHandlesFullscreenNavbarRight() {
RuntimeEnvironment.setQualifiers("+land");
FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.systemContext));
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(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<FlutterRenderer.ViewportMetrics> 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)
public void systemInsetHandlesFullscreenNavbarRightBelowSDK23() {
RuntimeEnvironment.setQualifiers("+land");
FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.systemContext));
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(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<FlutterRenderer.ViewportMetrics> 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)
public void systemInsetHandlesFullscreenNavbarLeft() {
RuntimeEnvironment.setQualifiers("+land");
FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.systemContext));
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(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<FlutterRenderer.ViewportMetrics> 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)
public void systemInsetGetInsetsFullscreen() {
RuntimeEnvironment.setQualifiers("+land");
FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.systemContext));
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(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<FlutterRenderer.ViewportMetrics> 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)
public void systemInsetGetInsetsFullscreenLegacy() {
RuntimeEnvironment.setQualifiers("+land");
FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.systemContext));
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(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<FlutterRenderer.ViewportMetrics> 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)
public void systemInsetDisplayCutoutSimple() {
RuntimeEnvironment.setQualifiers("+land");
FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.systemContext));
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<FlutterRenderer.ViewportMetrics> 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)
RuntimeEnvironment.systemContext.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)
RuntimeEnvironment.systemContext.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(RuntimeEnvironment.application, 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<FlutterRenderer.ViewportMetrics> viewportMetricsCaptor =
ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class);
verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture());
assertEquals(Arrays.asList(), viewportMetricsCaptor.getValue().displayFeatures);
flutterView.onAttachedToWindow();
ArgumentCaptor<Consumer<WindowLayoutInfo>> wmConsumerCaptor =
ArgumentCaptor.forClass((Class) Consumer.class);
verify(windowInfoRepo).addWindowLayoutInfoListener(any(), any(), wmConsumerCaptor.capture());
Consumer<WindowLayoutInfo> 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(
RuntimeEnvironment.application,
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(
RuntimeEnvironment.application,
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(
RuntimeEnvironment.application,
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(
RuntimeEnvironment.application,
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(
RuntimeEnvironment.application,
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(RuntimeEnvironment.application);
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")
public void findViewByAccessibilityIdTraversal_returnsRootViewOnAndroid28() throws Exception {
TestUtils.setApiVersion(28);
FlutterView flutterView = new FlutterView(RuntimeEnvironment.application);
Method getAccessibilityViewIdMethod = View.class.getDeclaredMethod("getAccessibilityViewId");
Integer accessibilityViewId = (Integer) getAccessibilityViewIdMethod.invoke(flutterView);
assertEquals(flutterView, flutterView.findViewByAccessibilityIdTraversal(accessibilityViewId));
}
@Test
@SuppressLint("PrivateApi")
public void findViewByAccessibilityIdTraversal_returnsChildViewOnAndroid28() throws Exception {
TestUtils.setApiVersion(28);
FlutterView flutterView = new FlutterView(RuntimeEnvironment.application);
FrameLayout childView1 = new FrameLayout(RuntimeEnvironment.application);
flutterView.addView(childView1);
FrameLayout childView2 = new FrameLayout(RuntimeEnvironment.application);
childView1.addView(childView2);
Method getAccessibilityViewIdMethod = View.class.getDeclaredMethod("getAccessibilityViewId");
Integer accessibilityViewId = (Integer) getAccessibilityViewIdMethod.invoke(childView2);
assertEquals(childView2, flutterView.findViewByAccessibilityIdTraversal(accessibilityViewId));
}
@Test
@SuppressLint("PrivateApi")
public void findViewByAccessibilityIdTraversal_returnsRootViewOnAndroid29() throws Exception {
TestUtils.setApiVersion(29);
FlutterView flutterView = new FlutterView(RuntimeEnvironment.application);
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(RuntimeEnvironment.application);
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(RuntimeEnvironment.application);
FlutterEngine flutterEngine =
spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni));
FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni));
when(flutterEngine.getRenderer()).thenReturn(flutterRenderer);
flutterView.attachToFlutterEngine(flutterEngine);
ArgumentCaptor<FlutterRenderer.ViewportMetrics> 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)
RuntimeEnvironment.systemContext.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay());
display.setRotation(rotation);
}
private void validateViewportMetricPadding(
ArgumentCaptor<FlutterRenderer.ViewportMetrics> 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 {}
}