blob: 38c919cae33dcce9708f23044948a3b879fc1d38 [file] [log] [blame]
package io.flutter.plugin.platform;
import static io.flutter.embedding.engine.systemchannels.PlatformViewsChannel.PlatformViewTouch;
import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.SurfaceTexture;
import android.util.SparseArray;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewParent;
import android.widget.FrameLayout.LayoutParams;
import io.flutter.embedding.android.FlutterImageView;
import io.flutter.embedding.android.FlutterView;
import io.flutter.embedding.android.MotionEventTracker;
import io.flutter.embedding.android.RenderMode;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.embedding.engine.FlutterOverlaySurface;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.embedding.engine.mutatorsstack.FlutterMutatorView;
import io.flutter.embedding.engine.mutatorsstack.FlutterMutatorsStack;
import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.embedding.engine.systemchannels.AccessibilityChannel;
import io.flutter.embedding.engine.systemchannels.KeyEventChannel;
import io.flutter.embedding.engine.systemchannels.MouseCursorChannel;
import io.flutter.embedding.engine.systemchannels.SettingsChannel;
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.StandardMethodCodec;
import io.flutter.plugin.localization.LocalizationPlugin;
import io.flutter.view.TextureRegistry;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadows.ShadowSurfaceView;
@Config(manifest = Config.NONE)
@RunWith(RobolectricTestRunner.class)
public class PlatformViewsControllerTest {
@Ignore
@Test
public void itNotifiesVirtualDisplayControllersOfViewAttachmentAndDetachment() {
// Setup test structure.
// Create a fake View that represents the View that renders a Flutter UI.
View fakeFlutterView = new View(RuntimeEnvironment.systemContext);
// Create fake VirtualDisplayControllers. This requires internal knowledge of
// PlatformViewsController. We know that all PlatformViewsController does is
// forward view attachment/detachment calls to it's VirtualDisplayControllers.
//
// TODO(mattcarroll): once PlatformViewsController is refactored into testable
// pieces, remove this test and avoid verifying private behavior.
VirtualDisplayController fakeVdController1 = mock(VirtualDisplayController.class);
VirtualDisplayController fakeVdController2 = mock(VirtualDisplayController.class);
// Create the PlatformViewsController that is under test.
PlatformViewsController platformViewsController = new PlatformViewsController();
// Manually inject fake VirtualDisplayControllers into the PlatformViewsController.
platformViewsController.vdControllers.put(0, fakeVdController1);
platformViewsController.vdControllers.put(1, fakeVdController1);
// Execute test & verify results.
// Attach PlatformViewsController to the fake Flutter View.
platformViewsController.attachToView(fakeFlutterView);
// Verify that all virtual display controllers were notified of View attachment.
verify(fakeVdController1, times(1)).onFlutterViewAttached(eq(fakeFlutterView));
verify(fakeVdController1, never()).onFlutterViewDetached();
verify(fakeVdController2, times(1)).onFlutterViewAttached(eq(fakeFlutterView));
verify(fakeVdController2, never()).onFlutterViewDetached();
// Detach PlatformViewsController from the fake Flutter View.
platformViewsController.detachFromView();
// Verify that all virtual display controllers were notified of the View detachment.
verify(fakeVdController1, times(1)).onFlutterViewAttached(eq(fakeFlutterView));
verify(fakeVdController1, times(1)).onFlutterViewDetached();
verify(fakeVdController2, times(1)).onFlutterViewAttached(eq(fakeFlutterView));
verify(fakeVdController2, times(1)).onFlutterViewDetached();
}
@Ignore
@Test
public void itCancelsOldPresentationOnResize() {
// Setup test structure.
// Create a fake View that represents the View that renders a Flutter UI.
View fakeFlutterView = new View(RuntimeEnvironment.systemContext);
// Create fake VirtualDisplayControllers. This requires internal knowledge of
// PlatformViewsController. We know that all PlatformViewsController does is
// forward view attachment/detachment calls to it's VirtualDisplayControllers.
//
// TODO(mattcarroll): once PlatformViewsController is refactored into testable
// pieces, remove this test and avoid verifying private behavior.
VirtualDisplayController fakeVdController1 = mock(VirtualDisplayController.class);
SingleViewPresentation presentation = fakeVdController1.presentation;
fakeVdController1.resize(10, 10, null);
assertEquals(fakeVdController1.presentation != presentation, true);
assertEquals(presentation.isShowing(), false);
}
@Test
public void itUsesActionEventTypeFromFrameworkEventForVirtualDisplays() {
MotionEventTracker motionEventTracker = MotionEventTracker.getInstance();
PlatformViewsController platformViewsController = new PlatformViewsController();
MotionEvent original =
MotionEvent.obtain(
100, // downTime
100, // eventTime
1, // action
0, // x
0, // y
0 // metaState
);
// track an event that will later get passed to us from framework
MotionEventTracker.MotionEventId motionEventId = motionEventTracker.track(original);
PlatformViewTouch frameWorkTouch =
new PlatformViewTouch(
0, // viewId
original.getDownTime(),
original.getEventTime(),
2, // action
1, // pointerCount
Arrays.asList(Arrays.asList(0, 0)), // pointer properties
Arrays.asList(Arrays.asList(0., 1., 2., 3., 4., 5., 6., 7., 8.)), // pointer coords
original.getMetaState(),
original.getButtonState(),
original.getXPrecision(),
original.getYPrecision(),
original.getDeviceId(),
original.getEdgeFlags(),
original.getSource(),
original.getFlags(),
motionEventId.getId());
MotionEvent resolvedEvent =
platformViewsController.toMotionEvent(
1, // density
frameWorkTouch,
true // usingVirtualDisplays
);
assertEquals(resolvedEvent.getAction(), frameWorkTouch.action);
assertNotEquals(resolvedEvent.getAction(), original.getAction());
}
@Ignore
@Test
public void itUsesActionEventTypeFromMotionEventForHybridPlatformViews() {
MotionEventTracker motionEventTracker = MotionEventTracker.getInstance();
PlatformViewsController platformViewsController = new PlatformViewsController();
MotionEvent original =
MotionEvent.obtain(
100, // downTime
100, // eventTime
1, // action
0, // x
0, // y
0 // metaState
);
// track an event that will later get passed to us from framework
MotionEventTracker.MotionEventId motionEventId = motionEventTracker.track(original);
PlatformViewTouch frameWorkTouch =
new PlatformViewTouch(
0, // viewId
original.getDownTime(),
original.getEventTime(),
2, // action
1, // pointerCount
Arrays.asList(Arrays.asList(0, 0)), // pointer properties
Arrays.asList(Arrays.asList(0., 1., 2., 3., 4., 5., 6., 7., 8.)), // pointer coords
original.getMetaState(),
original.getButtonState(),
original.getXPrecision(),
original.getYPrecision(),
original.getDeviceId(),
original.getEdgeFlags(),
original.getSource(),
original.getFlags(),
motionEventId.getId());
MotionEvent resolvedEvent =
platformViewsController.toMotionEvent(
1, // density
frameWorkTouch,
false // usingVirtualDisplays
);
assertNotEquals(resolvedEvent.getAction(), frameWorkTouch.action);
assertEquals(resolvedEvent.getAction(), original.getAction());
}
@Test
@Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class})
public void getPlatformViewById__hybridComposition() {
PlatformViewsController platformViewsController = new PlatformViewsController();
int platformViewId = 0;
assertNull(platformViewsController.getPlatformViewById(platformViewId));
PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
PlatformView platformView = mock(PlatformView.class);
View androidView = mock(View.class);
when(platformView.getView()).thenReturn(androidView);
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
platformViewsController.getRegistry().registerViewFactory("testType", viewFactory);
FlutterJNI jni = new FlutterJNI();
attach(jni, platformViewsController);
// Simulate create call from the framework.
createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true);
platformViewsController.initializePlatformViewIfNeeded(platformViewId);
View resultAndroidView = platformViewsController.getPlatformViewById(platformViewId);
assertNotNull(resultAndroidView);
assertEquals(resultAndroidView, androidView);
}
@Test
@Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class})
public void createPlatformViewMessage__initializesAndroidView() {
PlatformViewsController platformViewsController = new PlatformViewsController();
int platformViewId = 0;
assertNull(platformViewsController.getPlatformViewById(platformViewId));
PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
PlatformView platformView = mock(PlatformView.class);
when(platformView.getView()).thenReturn(mock(View.class));
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
platformViewsController.getRegistry().registerViewFactory("testType", viewFactory);
FlutterJNI jni = new FlutterJNI();
attach(jni, platformViewsController);
// Simulate create call from the framework.
createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true);
verify(viewFactory, times(1)).create(any(), eq(platformViewId), any());
}
@Test
@Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class})
public void createPlatformViewMessage__throwsIfViewIsNull() {
PlatformViewsController platformViewsController = new PlatformViewsController();
int platformViewId = 0;
assertNull(platformViewsController.getPlatformViewById(platformViewId));
PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
PlatformView platformView = mock(PlatformView.class);
when(platformView.getView()).thenReturn(null);
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
platformViewsController.getRegistry().registerViewFactory("testType", viewFactory);
FlutterJNI jni = new FlutterJNI();
attach(jni, platformViewsController);
// Simulate create call from the framework.
createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true);
assertEquals(ShadowFlutterJNI.getResponses().size(), 1);
assertThrows(
IllegalStateException.class,
() -> {
platformViewsController.initializePlatformViewIfNeeded(platformViewId);
});
}
@Test
@Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class})
public void onDetachedFromJNI_clearsPlatformViewContext() {
PlatformViewsController platformViewsController = new PlatformViewsController();
int platformViewId = 0;
assertNull(platformViewsController.getPlatformViewById(platformViewId));
PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
PlatformView platformView = mock(PlatformView.class);
View pv = mock(View.class);
when(pv.getLayoutParams()).thenReturn(new LayoutParams(1, 1));
when(platformView.getView()).thenReturn(pv);
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
platformViewsController.getRegistry().registerViewFactory("testType", viewFactory);
FlutterJNI jni = new FlutterJNI();
attach(jni, platformViewsController);
// Simulate create call from the framework.
createPlatformView(
jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ false);
assertFalse(platformViewsController.contextToPlatformView.isEmpty());
platformViewsController.onDetachedFromJNI();
assertTrue(platformViewsController.contextToPlatformView.isEmpty());
}
@Test
@Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class})
public void onPreEngineRestart_clearsPlatformViewContext() {
PlatformViewsController platformViewsController = new PlatformViewsController();
int platformViewId = 0;
assertNull(platformViewsController.getPlatformViewById(platformViewId));
PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
PlatformView platformView = mock(PlatformView.class);
View pv = mock(View.class);
when(pv.getLayoutParams()).thenReturn(new LayoutParams(1, 1));
when(platformView.getView()).thenReturn(pv);
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
platformViewsController.getRegistry().registerViewFactory("testType", viewFactory);
FlutterJNI jni = new FlutterJNI();
attach(jni, platformViewsController);
// Simulate create call from the framework.
createPlatformView(
jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ false);
assertFalse(platformViewsController.contextToPlatformView.isEmpty());
platformViewsController.onDetachedFromJNI();
assertTrue(platformViewsController.contextToPlatformView.isEmpty());
}
@Test
@Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class})
public void createPlatformViewMessage__throwsIfViewHasParent() {
PlatformViewsController platformViewsController = new PlatformViewsController();
int platformViewId = 0;
assertNull(platformViewsController.getPlatformViewById(platformViewId));
PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
PlatformView platformView = mock(PlatformView.class);
View androidView = mock(View.class);
when(androidView.getParent()).thenReturn(mock(ViewParent.class));
when(platformView.getView()).thenReturn(androidView);
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
platformViewsController.getRegistry().registerViewFactory("testType", viewFactory);
FlutterJNI jni = new FlutterJNI();
attach(jni, platformViewsController);
// Simulate create call from the framework.
createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true);
assertEquals(ShadowFlutterJNI.getResponses().size(), 1);
assertThrows(
IllegalStateException.class,
() -> {
platformViewsController.initializePlatformViewIfNeeded(platformViewId);
});
}
@Test
@Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class})
public void disposeAndroidView__hybridComposition() {
PlatformViewsController platformViewsController = new PlatformViewsController();
int platformViewId = 0;
assertNull(platformViewsController.getPlatformViewById(platformViewId));
PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
PlatformView platformView = mock(PlatformView.class);
Context context = RuntimeEnvironment.application.getApplicationContext();
View androidView = new View(context);
when(platformView.getView()).thenReturn(androidView);
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
platformViewsController.getRegistry().registerViewFactory("testType", viewFactory);
FlutterJNI jni = new FlutterJNI();
attach(jni, platformViewsController);
// Simulate create call from the framework.
createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true);
platformViewsController.initializePlatformViewIfNeeded(platformViewId);
assertNotNull(androidView.getParent());
assertTrue(androidView.getParent() instanceof FlutterMutatorView);
// Simulate dispose call from the framework.
disposePlatformView(jni, platformViewsController, platformViewId);
assertNull(androidView.getParent());
// Simulate create call from the framework.
createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true);
platformViewsController.initializePlatformViewIfNeeded(platformViewId);
assertNotNull(androidView.getParent());
assertTrue(androidView.getParent() instanceof FlutterMutatorView);
verify(platformView, times(1)).dispose();
}
@Test
@Config(
shadows = {
ShadowFlutterSurfaceView.class,
ShadowFlutterJNI.class,
ShadowPlatformTaskQueue.class
})
public void onEndFrame__destroysOverlaySurfaceAfterFrameOnFlutterSurfaceView() {
final PlatformViewsController platformViewsController = new PlatformViewsController();
final int platformViewId = 0;
assertNull(platformViewsController.getPlatformViewById(platformViewId));
final PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
final PlatformView platformView = mock(PlatformView.class);
when(platformView.getView()).thenReturn(mock(View.class));
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
platformViewsController.getRegistry().registerViewFactory("testType", viewFactory);
final FlutterJNI jni = new FlutterJNI();
jni.attachToNative();
attach(jni, platformViewsController);
jni.onFirstFrame();
// Simulate create call from the framework.
createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true);
// Produce a frame that displays a platform view and an overlay surface.
platformViewsController.onBeginFrame();
platformViewsController.onDisplayPlatformView(
platformViewId,
/* x=*/ 0,
/* y=*/ 0,
/* width=*/ 10,
/* height=*/ 10,
/* viewWidth=*/ 10,
/* viewHeight=*/ 10,
/* mutatorsStack=*/ new FlutterMutatorsStack());
final FlutterImageView overlayImageView = mock(FlutterImageView.class);
when(overlayImageView.acquireLatestImage()).thenReturn(true);
final FlutterOverlaySurface overlaySurface =
platformViewsController.createOverlaySurface(overlayImageView);
platformViewsController.onDisplayOverlaySurface(
overlaySurface.getId(), /* x=*/ 0, /* y=*/ 0, /* width=*/ 10, /* height=*/ 10);
platformViewsController.onEndFrame();
// Simulate first frame from the framework.
jni.onFirstFrame();
verify(overlayImageView, never()).detachFromRenderer();
// Produce a frame that doesn't display platform views.
platformViewsController.onBeginFrame();
platformViewsController.onEndFrame();
verify(overlayImageView, never()).detachFromRenderer();
// Simulate first frame from the framework.
jni.onFirstFrame();
verify(overlayImageView, times(1)).detachFromRenderer();
}
@Test
@Config(shadows = {ShadowFlutterSurfaceView.class, ShadowFlutterJNI.class})
public void onEndFrame__removesPlatformView() {
final PlatformViewsController platformViewsController = new PlatformViewsController();
final int platformViewId = 0;
assertNull(platformViewsController.getPlatformViewById(platformViewId));
final PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
final PlatformView platformView = mock(PlatformView.class);
final View androidView = mock(View.class);
when(platformView.getView()).thenReturn(androidView);
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
platformViewsController.getRegistry().registerViewFactory("testType", viewFactory);
final FlutterJNI jni = new FlutterJNI();
jni.attachToNative();
attach(jni, platformViewsController);
jni.onFirstFrame();
// Simulate create call from the framework.
createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true);
// Simulate first frame from the framework.
jni.onFirstFrame();
platformViewsController.onBeginFrame();
platformViewsController.onEndFrame();
verify(androidView, never()).setVisibility(View.GONE);
final ViewParent parentView = mock(ViewParent.class);
when(androidView.getParent()).thenReturn(parentView);
}
@Test
@Config(
shadows = {
ShadowFlutterSurfaceView.class,
ShadowFlutterJNI.class,
ShadowPlatformTaskQueue.class
})
public void onEndFrame__removesPlatformViewParent() {
final PlatformViewsController platformViewsController = new PlatformViewsController();
final int platformViewId = 0;
assertNull(platformViewsController.getPlatformViewById(platformViewId));
final PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
final PlatformView platformView = mock(PlatformView.class);
final View androidView = mock(View.class);
when(platformView.getView()).thenReturn(androidView);
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
platformViewsController.getRegistry().registerViewFactory("testType", viewFactory);
final FlutterJNI jni = new FlutterJNI();
jni.attachToNative();
final FlutterView flutterView = attach(jni, platformViewsController);
jni.onFirstFrame();
// Simulate create call from the framework.
createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true);
platformViewsController.initializePlatformViewIfNeeded(platformViewId);
assertEquals(flutterView.getChildCount(), 2);
// Simulate first frame from the framework.
jni.onFirstFrame();
platformViewsController.onBeginFrame();
platformViewsController.onEndFrame();
// Simulate dispose call from the framework.
disposePlatformView(jni, platformViewsController, platformViewId);
assertEquals(flutterView.getChildCount(), 1);
}
@Test
@Config(
shadows = {
ShadowFlutterSurfaceView.class,
ShadowFlutterJNI.class,
ShadowPlatformTaskQueue.class
})
public void detach__destroysOverlaySurfaces() {
final PlatformViewsController platformViewsController = new PlatformViewsController();
final int platformViewId = 0;
assertNull(platformViewsController.getPlatformViewById(platformViewId));
final PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
final PlatformView platformView = mock(PlatformView.class);
when(platformView.getView()).thenReturn(mock(View.class));
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
platformViewsController.getRegistry().registerViewFactory("testType", viewFactory);
final FlutterJNI jni = new FlutterJNI();
jni.attachToNative();
attach(jni, platformViewsController);
jni.onFirstFrame();
// Simulate create call from the framework.
createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true);
// Produce a frame that displays a platform view and an overlay surface.
platformViewsController.onBeginFrame();
platformViewsController.onDisplayPlatformView(
platformViewId,
/* x=*/ 0,
/* y=*/ 0,
/* width=*/ 10,
/* height=*/ 10,
/* viewWidth=*/ 10,
/* viewHeight=*/ 10,
/* mutatorsStack=*/ new FlutterMutatorsStack());
final FlutterImageView overlayImageView = mock(FlutterImageView.class);
when(overlayImageView.acquireLatestImage()).thenReturn(true);
final FlutterOverlaySurface overlaySurface =
platformViewsController.createOverlaySurface(overlayImageView);
// This is OK.
platformViewsController.onDisplayOverlaySurface(
overlaySurface.getId(), /* x=*/ 0, /* y=*/ 0, /* width=*/ 10, /* height=*/ 10);
platformViewsController.detach();
assertThrows(
IllegalStateException.class,
() -> {
platformViewsController.onDisplayOverlaySurface(
overlaySurface.getId(), /* x=*/ 0, /* y=*/ 0, /* width=*/ 10, /* height=*/ 10);
});
}
@Test
@Config(shadows = {ShadowFlutterSurfaceView.class, ShadowFlutterJNI.class})
public void detachFromView__removesOverlaySurfaces() {
final PlatformViewsController platformViewsController = new PlatformViewsController();
final int platformViewId = 0;
assertNull(platformViewsController.getPlatformViewById(platformViewId));
final PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
final PlatformView platformView = mock(PlatformView.class);
when(platformView.getView()).thenReturn(mock(View.class));
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
platformViewsController.getRegistry().registerViewFactory("testType", viewFactory);
final FlutterJNI jni = new FlutterJNI();
jni.attachToNative();
attach(jni, platformViewsController);
final FlutterImageView overlayImageView = mock(FlutterImageView.class);
when(overlayImageView.acquireLatestImage()).thenReturn(true);
final FlutterOverlaySurface overlaySurface =
platformViewsController.createOverlaySurface(overlayImageView);
platformViewsController.onDisplayOverlaySurface(
overlaySurface.getId(), /* x=*/ 0, /* y=*/ 0, /* width=*/ 10, /* height=*/ 10);
platformViewsController.detachFromView();
assertThrows(
IllegalStateException.class,
() -> {
platformViewsController.onDisplayOverlaySurface(
overlaySurface.getId(), /* x=*/ 0, /* y=*/ 0, /* width=*/ 10, /* height=*/ 10);
});
}
@Test
@Config(shadows = {ShadowFlutterSurfaceView.class, ShadowFlutterJNI.class})
public void destroyOverlaySurfaces__doesNotThrowIfControllerIsDetached() {
final PlatformViewsController platformViewsController = new PlatformViewsController();
final int platformViewId = 0;
assertNull(platformViewsController.getPlatformViewById(platformViewId));
final PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
final PlatformView platformView = mock(PlatformView.class);
when(platformView.getView()).thenReturn(mock(View.class));
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
platformViewsController.getRegistry().registerViewFactory("testType", viewFactory);
final FlutterJNI jni = new FlutterJNI();
jni.attachToNative();
attach(jni, platformViewsController);
final FlutterImageView overlayImageView = mock(FlutterImageView.class);
when(overlayImageView.acquireLatestImage()).thenReturn(true);
final FlutterOverlaySurface overlaySurface =
platformViewsController.createOverlaySurface(overlayImageView);
platformViewsController.onDisplayOverlaySurface(
overlaySurface.getId(), /* x=*/ 0, /* y=*/ 0, /* width=*/ 10, /* height=*/ 10);
platformViewsController.detachFromView();
platformViewsController.destroyOverlaySurfaces();
}
@Test
public void checkInputConnectionProxy__falseIfViewIsNull() {
final PlatformViewsController platformViewsController = new PlatformViewsController();
boolean shouldProxying = platformViewsController.checkInputConnectionProxy(null);
assertFalse(shouldProxying);
}
@Test
@Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class})
public void convertPlatformViewRenderSurfaceAsDefault() {
final PlatformViewsController platformViewsController = new PlatformViewsController();
final int platformViewId = 0;
assertNull(platformViewsController.getPlatformViewById(platformViewId));
final PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
final PlatformView platformView = mock(PlatformView.class);
final View androidView = mock(View.class);
when(platformView.getView()).thenReturn(androidView);
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
platformViewsController.getRegistry().registerViewFactory("testType", viewFactory);
final FlutterJNI jni = new FlutterJNI();
jni.attachToNative();
final FlutterView flutterView = attach(jni, platformViewsController);
jni.onFirstFrame();
// Simulate create call from the framework.
createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true);
// Produce a frame that displays a platform view and an overlay surface.
platformViewsController.onBeginFrame();
platformViewsController.onDisplayPlatformView(
platformViewId,
/* x=*/ 0,
/* y=*/ 0,
/* width=*/ 10,
/* height=*/ 10,
/* viewWidth=*/ 10,
/* viewHeight=*/ 10,
/* mutatorsStack=*/ new FlutterMutatorsStack());
assertEquals(flutterView.getChildCount(), 3);
final View view = flutterView.getChildAt(1);
assertTrue(view instanceof FlutterImageView);
// Simulate dispose call from the framework.
disposePlatformView(jni, platformViewsController, platformViewId);
}
@Test
@Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class})
public void dontConverRenderSurfaceWhenFlagIsTrue() {
final PlatformViewsController platformViewsController = new PlatformViewsController();
final int platformViewId = 0;
assertNull(platformViewsController.getPlatformViewById(platformViewId));
final PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
final PlatformView platformView = mock(PlatformView.class);
final View androidView = mock(View.class);
when(platformView.getView()).thenReturn(androidView);
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
platformViewsController.getRegistry().registerViewFactory("testType", viewFactory);
final FlutterJNI jni = new FlutterJNI();
jni.attachToNative();
final FlutterView flutterView = attach(jni, platformViewsController);
jni.onFirstFrame();
// Simulate setting render surface conversion flag.
synchronizeToNativeViewHierarchy(jni, platformViewsController, false);
// Simulate create call from the framework.
createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true);
// Produce a frame that displays a platform view and an overlay surface.
platformViewsController.onBeginFrame();
platformViewsController.onDisplayPlatformView(
platformViewId,
/* x=*/ 0,
/* y=*/ 0,
/* width=*/ 10,
/* height=*/ 10,
/* viewWidth=*/ 10,
/* viewHeight=*/ 10,
/* mutatorsStack=*/ new FlutterMutatorsStack());
assertEquals(flutterView.getChildCount(), 2);
assertTrue(!(flutterView.getChildAt(0) instanceof FlutterImageView));
assertTrue(flutterView.getChildAt(1) instanceof FlutterMutatorView);
// Simulate dispose call from the framework.
disposePlatformView(jni, platformViewsController, platformViewId);
}
private static ByteBuffer encodeMethodCall(MethodCall call) {
final ByteBuffer buffer = StandardMethodCodec.INSTANCE.encodeMethodCall(call);
buffer.rewind();
return buffer;
}
private static void createPlatformView(
FlutterJNI jni,
PlatformViewsController platformViewsController,
int platformViewId,
String viewType,
boolean hybrid) {
final Map<String, Object> platformViewCreateArguments = new HashMap<>();
platformViewCreateArguments.put("hybrid", hybrid);
platformViewCreateArguments.put("id", platformViewId);
platformViewCreateArguments.put("viewType", viewType);
platformViewCreateArguments.put("direction", 0);
platformViewCreateArguments.put("width", 1.0);
platformViewCreateArguments.put("height", 1.0);
final MethodCall platformCreateMethodCall =
new MethodCall("create", platformViewCreateArguments);
jni.handlePlatformMessage(
"flutter/platform_views",
encodeMethodCall(platformCreateMethodCall),
/*replyId=*/ 0,
/*messageData=*/ 0);
}
private static void disposePlatformView(
FlutterJNI jni, PlatformViewsController platformViewsController, int platformViewId) {
final Map<String, Object> platformViewDisposeArguments = new HashMap<>();
platformViewDisposeArguments.put("hybrid", true);
platformViewDisposeArguments.put("id", platformViewId);
final MethodCall platformDisposeMethodCall =
new MethodCall("dispose", platformViewDisposeArguments);
jni.handlePlatformMessage(
"flutter/platform_views",
encodeMethodCall(platformDisposeMethodCall),
/*replyId=*/ 0,
/*messageData=*/ 0);
}
private static void synchronizeToNativeViewHierarchy(
FlutterJNI jni, PlatformViewsController platformViewsController, boolean yes) {
final MethodCall convertMethodCall = new MethodCall("synchronizeToNativeViewHierarchy", yes);
jni.handlePlatformMessage(
"flutter/platform_views",
encodeMethodCall(convertMethodCall),
/*replyId=*/ 0,
/*messageData=*/ 0);
}
private static FlutterView attach(
FlutterJNI jni, PlatformViewsController platformViewsController) {
final DartExecutor executor = new DartExecutor(jni, mock(AssetManager.class));
executor.onAttachedToJNI();
final Context context = RuntimeEnvironment.application.getApplicationContext();
final TextureRegistry registry =
new TextureRegistry() {
public void TextureRegistry() {}
@Override
public SurfaceTextureEntry createSurfaceTexture() {
return registerSurfaceTexture(mock(SurfaceTexture.class));
}
@Override
public SurfaceTextureEntry registerSurfaceTexture(SurfaceTexture surfaceTexture) {
return new SurfaceTextureEntry() {
@Override
public SurfaceTexture surfaceTexture() {
return mock(SurfaceTexture.class);
}
@Override
public long id() {
return 0;
}
@Override
public void release() {}
};
}
};
platformViewsController.attach(context, registry, executor);
final FlutterView view =
new FlutterView(context, RenderMode.surface) {
@Override
public FlutterImageView createImageView() {
final FlutterImageView view = mock(FlutterImageView.class);
when(view.acquireLatestImage()).thenReturn(true);
return mock(FlutterImageView.class);
}
};
view.layout(0, 0, 100, 100);
final FlutterEngine engine = mock(FlutterEngine.class);
when(engine.getRenderer()).thenReturn(new FlutterRenderer(jni));
when(engine.getMouseCursorChannel()).thenReturn(mock(MouseCursorChannel.class));
when(engine.getTextInputChannel()).thenReturn(mock(TextInputChannel.class));
when(engine.getSettingsChannel()).thenReturn(new SettingsChannel(executor));
when(engine.getPlatformViewsController()).thenReturn(platformViewsController);
when(engine.getLocalizationPlugin()).thenReturn(mock(LocalizationPlugin.class));
when(engine.getKeyEventChannel()).thenReturn(mock(KeyEventChannel.class));
when(engine.getAccessibilityChannel()).thenReturn(mock(AccessibilityChannel.class));
view.attachToFlutterEngine(engine);
platformViewsController.attachToView(view);
return view;
}
/**
* For convenience when writing tests, this allows us to make fake messages from Flutter via
* Platform Channels. Typically those calls happen on the ui thread which dispatches to the
* platform thread. Since tests run on the platform thread it makes it difficult to test without
* this, but isn't technically required.
*/
@Implements(io.flutter.embedding.engine.dart.PlatformTaskQueue.class)
public static class ShadowPlatformTaskQueue {
@Implementation
public void dispatch(Runnable runnable) {
runnable.run();
}
}
@Implements(FlutterJNI.class)
public static class ShadowFlutterJNI {
private static SparseArray<ByteBuffer> replies = new SparseArray<>();
public ShadowFlutterJNI() {}
@Implementation
public boolean getIsSoftwareRenderingEnabled() {
return false;
}
@Implementation
public long performNativeAttach(FlutterJNI flutterJNI) {
return 1;
}
@Implementation
public void dispatchPlatformMessage(
String channel, ByteBuffer message, int position, int responseId) {}
@Implementation
public void onSurfaceCreated(Surface surface) {}
@Implementation
public void onSurfaceDestroyed() {}
@Implementation
public void onSurfaceWindowChanged(Surface surface) {}
@Implementation
public void setViewportMetrics(
float devicePixelRatio,
int physicalWidth,
int physicalHeight,
int physicalPaddingTop,
int physicalPaddingRight,
int physicalPaddingBottom,
int physicalPaddingLeft,
int physicalViewInsetTop,
int physicalViewInsetRight,
int physicalViewInsetBottom,
int physicalViewInsetLeft,
int systemGestureInsetTop,
int systemGestureInsetRight,
int systemGestureInsetBottom,
int systemGestureInsetLeft,
int physicalTouchSlop) {}
@Implementation
public void invokePlatformMessageResponseCallback(
int responseId, ByteBuffer message, int position) {
replies.put(responseId, message);
}
public static SparseArray<ByteBuffer> getResponses() {
return replies;
}
}
@Implements(SurfaceView.class)
public static class ShadowFlutterSurfaceView extends ShadowSurfaceView {
private final FakeSurfaceHolder holder = new FakeSurfaceHolder();
public static class FakeSurfaceHolder extends ShadowSurfaceView.FakeSurfaceHolder {
private final Surface surface = mock(Surface.class);
public Surface getSurface() {
return surface;
}
@Implementation
public void addCallback(SurfaceHolder.Callback callback) {
callback.surfaceCreated(this);
}
}
public ShadowFlutterSurfaceView() {}
@Implementation
public SurfaceHolder getHolder() {
return holder;
}
}
}