Test dynamic surface switch (#61918)
This commit is contained in:
parent
b8df8a8368
commit
ddb8e6e3bf
@ -12,9 +12,14 @@ import android.view.MotionEvent;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import java.lang.StringBuilder;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
import io.flutter.embedding.android.FlutterActivity;
|
import io.flutter.embedding.android.FlutterActivity;
|
||||||
|
import io.flutter.embedding.android.FlutterImageView;
|
||||||
|
import io.flutter.embedding.android.FlutterSurfaceView;
|
||||||
|
import io.flutter.embedding.android.FlutterTextureView;
|
||||||
|
import io.flutter.embedding.android.FlutterView;
|
||||||
import io.flutter.embedding.engine.dart.DartExecutor;
|
import io.flutter.embedding.engine.dart.DartExecutor;
|
||||||
import io.flutter.embedding.engine.FlutterEngine;
|
import io.flutter.embedding.engine.FlutterEngine;
|
||||||
import io.flutter.plugin.common.MethodCall;
|
import io.flutter.plugin.common.MethodCall;
|
||||||
@ -36,6 +41,63 @@ public class MainActivity extends FlutterActivity implements MethodChannel.Metho
|
|||||||
return ((ViewGroup)root.getChildAt(0)).getChildAt(0);
|
return ((ViewGroup)root.getChildAt(0)).getChildAt(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getViewName(View view) {
|
||||||
|
if (view instanceof FlutterImageView) {
|
||||||
|
return "FlutterImageView";
|
||||||
|
}
|
||||||
|
if (view instanceof FlutterSurfaceView) {
|
||||||
|
return "FlutterSurfaceView";
|
||||||
|
}
|
||||||
|
if (view instanceof FlutterTextureView) {
|
||||||
|
return "FlutterTextureView";
|
||||||
|
}
|
||||||
|
if (view instanceof FlutterView) {
|
||||||
|
return "FlutterView";
|
||||||
|
}
|
||||||
|
if (view instanceof ViewGroup) {
|
||||||
|
return "ViewGroup";
|
||||||
|
}
|
||||||
|
return "View";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recurseViewHierarchy(View current, String padding, StringBuilder builder) {
|
||||||
|
if (current.getVisibility() != View.VISIBLE || current.getAlpha() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String name = getViewName(current);
|
||||||
|
builder.append(padding);
|
||||||
|
builder.append("|-");
|
||||||
|
builder.append(name);
|
||||||
|
builder.append("\n");
|
||||||
|
|
||||||
|
if (current instanceof ViewGroup) {
|
||||||
|
ViewGroup viewGroup = (ViewGroup) current;
|
||||||
|
for (int index = 0; index < viewGroup.getChildCount(); index++) {
|
||||||
|
recurseViewHierarchy(viewGroup.getChildAt(index), padding + " ", builder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes the view hierarchy, so it can be sent to Dart over the method channel.
|
||||||
|
*
|
||||||
|
* Notation:
|
||||||
|
* |- <view-name>
|
||||||
|
* |- ... child view ordered by z order.
|
||||||
|
*
|
||||||
|
* Example output:
|
||||||
|
* |- FlutterView
|
||||||
|
* |- FlutterImageView
|
||||||
|
* |- ViewGroup
|
||||||
|
* |- View
|
||||||
|
*/
|
||||||
|
private String getSerializedViewHierarchy() {
|
||||||
|
View root = getFlutterView();
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
recurseViewHierarchy(root, "", builder);
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@ -65,19 +127,23 @@ public class MainActivity extends FlutterActivity implements MethodChannel.Metho
|
|||||||
getExternalStoragePermissions();
|
getExternalStoragePermissions();
|
||||||
return;
|
return;
|
||||||
case "synthesizeEvent":
|
case "synthesizeEvent":
|
||||||
synthesizeEvent(methodCall, result);
|
synthesizeEvent(methodCall);
|
||||||
|
result.success(null);
|
||||||
|
return;
|
||||||
|
case "getViewHierarchy":
|
||||||
|
String viewHierarchy = getSerializedViewHierarchy();
|
||||||
|
result.success(viewHierarchy);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
result.notImplemented();
|
result.notImplemented();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void synthesizeEvent(MethodCall methodCall, MethodChannel.Result result) {
|
public void synthesizeEvent(MethodCall methodCall) {
|
||||||
MotionEvent event = MotionEventCodec.decode((HashMap<String, Object>) methodCall.arguments());
|
MotionEvent event = MotionEventCodec.decode((HashMap<String, Object>) methodCall.arguments());
|
||||||
getFlutterView().dispatchTouchEvent(event);
|
getFlutterView().dispatchTouchEvent(event);
|
||||||
// TODO(egarciad): This can be cleaned up.
|
// TODO(egarciad): This can be cleaned up.
|
||||||
mMethodChannel.invokeMethod("onTouch", MotionEventCodec.encode(event));
|
mMethodChannel.invokeMethod("onTouch", MotionEventCodec.encode(event));
|
||||||
result.success(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -8,6 +8,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
MethodChannel channel = const MethodChannel('android_views_integration');
|
||||||
|
|
||||||
class AndroidPlatformView extends StatelessWidget {
|
class AndroidPlatformView extends StatelessWidget {
|
||||||
/// Creates a platform view for Android, which is rendered as a
|
/// Creates a platform view for Android, which is rendered as a
|
||||||
/// native view.
|
/// native view.
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter_driver/driver_extension.dart';
|
||||||
|
|
||||||
|
typedef DriverHandler = Future<String> Function();
|
||||||
|
|
||||||
|
/// Wraps a flutter driver [DataHandler] with one that waits until a delegate is set.
|
||||||
|
///
|
||||||
|
/// This allows the driver test to call [FlutterDriver.requestData] before the handler was
|
||||||
|
/// set by the app in which case the requestData call will only complete once the app is ready
|
||||||
|
/// for it.
|
||||||
|
class FutureDataHandler {
|
||||||
|
final Map<String, Completer<DriverHandler>> _handlers = <String, Completer<DriverHandler>>{};
|
||||||
|
|
||||||
|
/// Registers a lazy handler that will be invoked on the next message from the driver.
|
||||||
|
Completer<DriverHandler> registerHandler(String key) {
|
||||||
|
_handlers[key] = Completer<DriverHandler>();
|
||||||
|
return _handlers[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> handleMessage(String message) async {
|
||||||
|
if (_handlers[message] == null) {
|
||||||
|
return 'Unsupported driver message: $message.\n'
|
||||||
|
'Supported messages are: ${_handlers.keys}.';
|
||||||
|
}
|
||||||
|
final DriverHandler handler = await _handlers[message].future;
|
||||||
|
return handler();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FutureDataHandler driverDataHandler = FutureDataHandler();
|
@ -5,6 +5,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_driver/driver_extension.dart';
|
import 'package:flutter_driver/driver_extension.dart';
|
||||||
|
|
||||||
|
import 'future_data_handler.dart';
|
||||||
import 'motion_events_page.dart';
|
import 'motion_events_page.dart';
|
||||||
import 'nested_view_event_page.dart';
|
import 'nested_view_event_page.dart';
|
||||||
import 'page.dart';
|
import 'page.dart';
|
||||||
|
@ -8,15 +8,13 @@ import 'dart:typed_data';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_driver/driver_extension.dart';
|
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
import 'android_platform_view.dart';
|
import 'android_platform_view.dart';
|
||||||
|
import 'future_data_handler.dart';
|
||||||
import 'motion_event_diff.dart';
|
import 'motion_event_diff.dart';
|
||||||
import 'page.dart';
|
import 'page.dart';
|
||||||
|
|
||||||
MethodChannel channel = const MethodChannel('android_views_integration');
|
|
||||||
|
|
||||||
const String kEventsFileName = 'touchEvents';
|
const String kEventsFileName = 'touchEvents';
|
||||||
|
|
||||||
class MotionEventsPage extends PageWidget {
|
class MotionEventsPage extends PageWidget {
|
||||||
@ -29,22 +27,6 @@ class MotionEventsPage extends PageWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wraps a flutter driver [DataHandler] with one that waits until a delegate is set.
|
|
||||||
///
|
|
||||||
/// This allows the driver test to call [FlutterDriver.requestData] before the handler was
|
|
||||||
/// set by the app in which case the requestData call will only complete once the app is ready
|
|
||||||
/// for it.
|
|
||||||
class FutureDataHandler {
|
|
||||||
final Completer<DataHandler> handlerCompleter = Completer<DataHandler>();
|
|
||||||
|
|
||||||
Future<String> handleMessage(String message) async {
|
|
||||||
final DataHandler handler = await handlerCompleter.future;
|
|
||||||
return handler(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FutureDataHandler driverDataHandler = FutureDataHandler();
|
|
||||||
|
|
||||||
class MotionEventsBody extends StatefulWidget {
|
class MotionEventsBody extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
State createState() => MotionEventsBodyState();
|
State createState() => MotionEventsBodyState();
|
||||||
@ -196,7 +178,7 @@ class MotionEventsBodyState extends State<MotionEventsBody> {
|
|||||||
void onPlatformViewCreated(int id) {
|
void onPlatformViewCreated(int id) {
|
||||||
viewChannel = MethodChannel('simple_view/$id');
|
viewChannel = MethodChannel('simple_view/$id');
|
||||||
viewChannel.setMethodCallHandler(onViewMethodChannelCall);
|
viewChannel.setMethodCallHandler(onViewMethodChannelCall);
|
||||||
driverDataHandler.handlerCompleter.complete(handleDriverMessage);
|
driverDataHandler.registerHandler('run test').complete(playEventsFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
void listenToFlutterViewEvents() {
|
void listenToFlutterViewEvents() {
|
||||||
@ -206,14 +188,6 @@ class MotionEventsBodyState extends State<MotionEventsBody> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> handleDriverMessage(String message) async {
|
|
||||||
switch (message) {
|
|
||||||
case 'run test':
|
|
||||||
return playEventsFile();
|
|
||||||
}
|
|
||||||
return 'unknown message: "$message"';
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<dynamic> onMethodChannelCall(MethodCall call) {
|
Future<dynamic> onMethodChannelCall(MethodCall call) {
|
||||||
switch (call.method) {
|
switch (call.method) {
|
||||||
case 'onTouch':
|
case 'onTouch':
|
||||||
|
@ -10,6 +10,7 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
import 'android_platform_view.dart';
|
import 'android_platform_view.dart';
|
||||||
|
import 'future_data_handler.dart';
|
||||||
import 'page.dart';
|
import 'page.dart';
|
||||||
|
|
||||||
class NestedViewEventPage extends PageWidget {
|
class NestedViewEventPage extends PageWidget {
|
||||||
@ -38,6 +39,7 @@ class NestedViewEventBodyState extends State<NestedViewEventBody> {
|
|||||||
String lastError;
|
String lastError;
|
||||||
int id;
|
int id;
|
||||||
int nestedViewClickCount = 0;
|
int nestedViewClickCount = 0;
|
||||||
|
bool showPlatformView = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -49,10 +51,12 @@ class NestedViewEventBodyState extends State<NestedViewEventBody> {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 300,
|
height: 300,
|
||||||
child: AndroidPlatformView(
|
child: showPlatformView ?
|
||||||
viewType: 'simple_view',
|
AndroidPlatformView(
|
||||||
onPlatformViewCreated: onPlatformViewCreated,
|
key: const ValueKey<String>('PlatformView'),
|
||||||
),
|
viewType: 'simple_view',
|
||||||
|
onPlatformViewCreated: onPlatformViewCreated,
|
||||||
|
) : null,
|
||||||
),
|
),
|
||||||
if (lastTestStatus != _LastTestStatus.pending) _statusWidget(),
|
if (lastTestStatus != _LastTestStatus.pending) _statusWidget(),
|
||||||
if (viewChannel != null) ... <Widget>[
|
if (viewChannel != null) ... <Widget>[
|
||||||
@ -61,6 +65,11 @@ class NestedViewEventBodyState extends State<NestedViewEventBody> {
|
|||||||
child: const Text('SHOW ALERT DIALOG'),
|
child: const Text('SHOW ALERT DIALOG'),
|
||||||
onPressed: onShowAlertDialogPressed,
|
onPressed: onShowAlertDialogPressed,
|
||||||
),
|
),
|
||||||
|
RaisedButton(
|
||||||
|
key: const ValueKey<String>('TogglePlatformView'),
|
||||||
|
child: const Text('TOGGLE PLATFORM VIEW'),
|
||||||
|
onPressed: onTogglePlatformView,
|
||||||
|
),
|
||||||
Row(
|
Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
RaisedButton(
|
RaisedButton(
|
||||||
@ -120,6 +129,12 @@ class NestedViewEventBodyState extends State<NestedViewEventBody> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> onTogglePlatformView() async {
|
||||||
|
setState(() {
|
||||||
|
showPlatformView = !showPlatformView;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> onChildViewPressed() async {
|
Future<void> onChildViewPressed() async {
|
||||||
try {
|
try {
|
||||||
await viewChannel.invokeMethod<void>('addChildViewAndWaitForClick');
|
await viewChannel.invokeMethod<void>('addChildViewAndWaitForClick');
|
||||||
@ -152,5 +167,7 @@ class NestedViewEventBodyState extends State<NestedViewEventBody> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
viewChannel = MethodChannel('simple_view/$id');
|
viewChannel = MethodChannel('simple_view/$id');
|
||||||
});
|
});
|
||||||
|
driverDataHandler.registerHandler('hierarchy')
|
||||||
|
.complete(() => channel.invokeMethod<String>('getViewHierarchy'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,10 +18,8 @@ Future<void> main() async {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Each test below must return back to the home page after finishing.
|
// Each test below must return back to the home page after finishing.
|
||||||
|
|
||||||
test('MotionEvent recomposition', () async {
|
test('MotionEvent recomposition', () async {
|
||||||
final SerializableFinder motionEventsListTile =
|
final SerializableFinder motionEventsListTile = find.byValueKey('MotionEventsListTile');
|
||||||
find.byValueKey('MotionEventsListTile');
|
|
||||||
await driver.tap(motionEventsListTile);
|
await driver.tap(motionEventsListTile);
|
||||||
await driver.waitFor(find.byValueKey('PlatformView'));
|
await driver.waitFor(find.byValueKey('PlatformView'));
|
||||||
final String errorMessage = await driver.requestData('run test');
|
final String errorMessage = await driver.requestData('run test');
|
||||||
@ -30,8 +28,7 @@ Future<void> main() async {
|
|||||||
await driver.tap(backButton);
|
await driver.tap(backButton);
|
||||||
});
|
});
|
||||||
|
|
||||||
group('Nested View Event', ()
|
group('Nested View Event', () {
|
||||||
{
|
|
||||||
setUpAll(() async {
|
setUpAll(() async {
|
||||||
final SerializableFinder wmListTile =
|
final SerializableFinder wmListTile =
|
||||||
find.byValueKey('NestedViewEventTile');
|
find.byValueKey('NestedViewEventTile');
|
||||||
@ -44,8 +41,7 @@ Future<void> main() async {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('AlertDialog from platform view context', () async {
|
test('AlertDialog from platform view context', () async {
|
||||||
final SerializableFinder showAlertDialog = find.byValueKey(
|
final SerializableFinder showAlertDialog = find.byValueKey('ShowAlertDialog');
|
||||||
'ShowAlertDialog');
|
|
||||||
await driver.waitFor(showAlertDialog);
|
await driver.waitFor(showAlertDialog);
|
||||||
await driver.tap(showAlertDialog);
|
await driver.tap(showAlertDialog);
|
||||||
final String status = await driver.getText(find.byValueKey('Status'));
|
final String status = await driver.getText(find.byValueKey('Status'));
|
||||||
@ -58,8 +54,60 @@ Future<void> main() async {
|
|||||||
await driver.tap(addChildView);
|
await driver.tap(addChildView);
|
||||||
final SerializableFinder tapChildView = find.byValueKey('TapChildView');
|
final SerializableFinder tapChildView = find.byValueKey('TapChildView');
|
||||||
await driver.tap(tapChildView);
|
await driver.tap(tapChildView);
|
||||||
final String nestedViewClickCount = await driver.getText(find.byValueKey('NestedViewClickCount'));
|
final String nestedViewClickCount =
|
||||||
|
await driver.getText(find.byValueKey('NestedViewClickCount'));
|
||||||
expect(nestedViewClickCount, 'Click count: 1');
|
expect(nestedViewClickCount, 'Click count: 1');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('Flutter surface switch', () {
|
||||||
|
setUpAll(() async {
|
||||||
|
final SerializableFinder wmListTile = find.byValueKey('NestedViewEventTile');
|
||||||
|
await driver.tap(wmListTile);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDownAll(() async {
|
||||||
|
await driver.waitFor(find.pageBack());
|
||||||
|
await driver.tap(find.pageBack());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Uses FlutterImageView when Android view is on the screen', () async {
|
||||||
|
await driver.waitFor(find.byValueKey('PlatformView'));
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await driver.requestData('hierarchy'),
|
||||||
|
'|-FlutterView\n'
|
||||||
|
' |-FlutterSurfaceView\n' // Flutter UI (hidden)
|
||||||
|
' |-FlutterImageView\n' // Flutter UI (background surface)
|
||||||
|
' |-ViewGroup\n' // Platform View
|
||||||
|
' |-ViewGroup\n'
|
||||||
|
' |-FlutterImageView\n' // Flutter UI (overlay surface)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Hide platform view.
|
||||||
|
final SerializableFinder togglePlatformView = find.byValueKey('TogglePlatformView');
|
||||||
|
await driver.tap(togglePlatformView);
|
||||||
|
await driver.waitForAbsent(find.byValueKey('PlatformView'));
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await driver.requestData('hierarchy'),
|
||||||
|
'|-FlutterView\n'
|
||||||
|
' |-FlutterSurfaceView\n' // Just the Flutter UI
|
||||||
|
);
|
||||||
|
|
||||||
|
// Show platform view again.
|
||||||
|
await driver.tap(togglePlatformView);
|
||||||
|
await driver.waitFor(find.byValueKey('PlatformView'));
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await driver.requestData('hierarchy'),
|
||||||
|
'|-FlutterView\n'
|
||||||
|
' |-FlutterSurfaceView\n' // Flutter UI (hidden)
|
||||||
|
' |-FlutterImageView\n' // Flutter UI (background surface)
|
||||||
|
' |-ViewGroup\n' // Platform View
|
||||||
|
' |-ViewGroup\n'
|
||||||
|
' |-FlutterImageView\n' // Flutter UI (overlay surface)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user