Unverified Commit ddb8e6e3 authored by Emmanuel Garcia's avatar Emmanuel Garcia Committed by GitHub

Test dynamic surface switch (#61918)

parent b8df8a83
...@@ -12,9 +12,14 @@ import android.view.MotionEvent; ...@@ -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 ...@@ -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 ...@@ -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'; ...@@ -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.
......
// 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 @@ ...@@ -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'; ...@@ -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 { ...@@ -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> { ...@@ -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> { ...@@ -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'; ...@@ -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> { ...@@ -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> { ...@@ -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> { ...@@ -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> { ...@@ -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> { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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)
);
});
});
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment