Add back FlutterViewTest.java (flutter/engine#55048)
Add back FlutterViewTest.java from 8e096b4 (last commit before it was deleted in https://github.com/flutter/engine/pull/44047)
Fixes flutter/flutter/issues/154746
This commit is contained in:
@@ -0,0 +1,946 @@
|
||||
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.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);
|
||||
}
|
||||
|
||||
@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));
|
||||
FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
|
||||
when(flutterEngine.getPlatformViewsController()).thenReturn(platformViewsController);
|
||||
|
||||
flutterView.attachToFlutterEngine(flutterEngine);
|
||||
|
||||
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));
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
@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));
|
||||
FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
|
||||
when(flutterEngine.getPlatformViewsController()).thenReturn(platformViewsController);
|
||||
|
||||
flutterView.attachToFlutterEngine(flutterEngine);
|
||||
flutterView.detachFromFlutterEngine();
|
||||
|
||||
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));
|
||||
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();
|
||||
}
|
||||
|
||||
@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));
|
||||
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();
|
||||
}
|
||||
|
||||
@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));
|
||||
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));
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
// Robolectric.setupActivity
|
||||
// TODO(reidbaker): https://github.com/flutter/flutter/issues/133151
|
||||
public void itSendsTextHidePasswordToFrameworkOnAttach() {
|
||||
// Setup test.
|
||||
AtomicReference<Boolean> 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<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());
|
||||
}
|
||||
|
||||
@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
|
||||
@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<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);
|
||||
}
|
||||
|
||||
@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)
|
||||
@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<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);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
// getSystemUiVisibility, getWindowSystemUiVisibility, required to test pre api 30 behavior.
|
||||
@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<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);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
// getSystemUiVisibility, getWindowSystemUiVisibility required to test pre api 30 behavior.
|
||||
@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<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);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
// getSystemUiVisibility, getWindowSystemUiVisibility required to test pre api 30 behavior.
|
||||
@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<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);
|
||||
}
|
||||
|
||||
@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
|
||||
@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<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);
|
||||
}
|
||||
|
||||
@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)
|
||||
@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<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);
|
||||
}
|
||||
|
||||
@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
|
||||
@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<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);
|
||||
}
|
||||
|
||||
@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);
|
||||
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());
|
||||
}
|
||||
|
||||
@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);
|
||||
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<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(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));
|
||||
}
|
||||
|
||||
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<FlutterRenderer.ViewportMetrics> viewportMetricsCaptor =
|
||||
ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class);
|
||||
verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture());
|
||||
|
||||
assertFalse(-1 == viewportMetricsCaptor.getValue().physicalTouchSlop);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void setExpectedDisplayRotation(int rotation) {
|
||||
ShadowDisplay display =
|
||||
Shadows.shadowOf(
|
||||
((WindowManager) ctx.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);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
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);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
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)
|
||||
@SuppressWarnings("deprecation")
|
||||
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 {}
|
||||
}
|
||||
Reference in New Issue
Block a user