mirror of
https://github.com/flutter/flutter
synced 2024-09-20 00:32:02 +00:00
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.ViewGroup;
|
||||
|
||||
import java.lang.StringBuilder;
|
||||
import java.util.HashMap;
|
||||
|
||||
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.FlutterEngine;
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
@ -65,19 +127,23 @@ public class MainActivity extends FlutterActivity implements MethodChannel.Metho
|
|||
getExternalStoragePermissions();
|
||||
return;
|
||||
case "synthesizeEvent":
|
||||
synthesizeEvent(methodCall, result);
|
||||
synthesizeEvent(methodCall);
|
||||
result.success(null);
|
||||
return;
|
||||
case "getViewHierarchy":
|
||||
String viewHierarchy = getSerializedViewHierarchy();
|
||||
result.success(viewHierarchy);
|
||||
return;
|
||||
}
|
||||
result.notImplemented();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void synthesizeEvent(MethodCall methodCall, MethodChannel.Result result) {
|
||||
public void synthesizeEvent(MethodCall methodCall) {
|
||||
MotionEvent event = MotionEventCodec.decode((HashMap<String, Object>) methodCall.arguments());
|
||||
getFlutterView().dispatchTouchEvent(event);
|
||||
// TODO(egarciad): This can be cleaned up.
|
||||
mMethodChannel.invokeMethod("onTouch", MotionEventCodec.encode(event));
|
||||
result.success(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -8,6 +8,8 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
MethodChannel channel = const MethodChannel('android_views_integration');
|
||||
|
||||
class AndroidPlatformView extends StatelessWidget {
|
||||
/// Creates a platform view for Android, which is rendered as a
|
||||
/// 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_driver/driver_extension.dart';
|
||||
|
||||
import 'future_data_handler.dart';
|
||||
import 'motion_events_page.dart';
|
||||
import 'nested_view_event_page.dart';
|
||||
import 'page.dart';
|
||||
|
|
|
@ -8,15 +8,13 @@ import 'dart:typed_data';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_driver/driver_extension.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import 'android_platform_view.dart';
|
||||
import 'future_data_handler.dart';
|
||||
import 'motion_event_diff.dart';
|
||||
import 'page.dart';
|
||||
|
||||
MethodChannel channel = const MethodChannel('android_views_integration');
|
||||
|
||||
const String kEventsFileName = 'touchEvents';
|
||||
|
||||
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 {
|
||||
@override
|
||||
State createState() => MotionEventsBodyState();
|
||||
|
@ -196,7 +178,7 @@ class MotionEventsBodyState extends State<MotionEventsBody> {
|
|||
void onPlatformViewCreated(int id) {
|
||||
viewChannel = MethodChannel('simple_view/$id');
|
||||
viewChannel.setMethodCallHandler(onViewMethodChannelCall);
|
||||
driverDataHandler.handlerCompleter.complete(handleDriverMessage);
|
||||
driverDataHandler.registerHandler('run test').complete(playEventsFile);
|
||||
}
|
||||
|
||||
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) {
|
||||
switch (call.method) {
|
||||
case 'onTouch':
|
||||
|
|
|
@ -10,6 +10,7 @@ import 'package:flutter/services.dart';
|
|||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'android_platform_view.dart';
|
||||
import 'future_data_handler.dart';
|
||||
import 'page.dart';
|
||||
|
||||
class NestedViewEventPage extends PageWidget {
|
||||
|
@ -38,6 +39,7 @@ class NestedViewEventBodyState extends State<NestedViewEventBody> {
|
|||
String lastError;
|
||||
int id;
|
||||
int nestedViewClickCount = 0;
|
||||
bool showPlatformView = true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -49,10 +51,12 @@ class NestedViewEventBodyState extends State<NestedViewEventBody> {
|
|||
children: <Widget>[
|
||||
SizedBox(
|
||||
height: 300,
|
||||
child: AndroidPlatformView(
|
||||
viewType: 'simple_view',
|
||||
onPlatformViewCreated: onPlatformViewCreated,
|
||||
),
|
||||
child: showPlatformView ?
|
||||
AndroidPlatformView(
|
||||
key: const ValueKey<String>('PlatformView'),
|
||||
viewType: 'simple_view',
|
||||
onPlatformViewCreated: onPlatformViewCreated,
|
||||
) : null,
|
||||
),
|
||||
if (lastTestStatus != _LastTestStatus.pending) _statusWidget(),
|
||||
if (viewChannel != null) ... <Widget>[
|
||||
|
@ -61,6 +65,11 @@ class NestedViewEventBodyState extends State<NestedViewEventBody> {
|
|||
child: const Text('SHOW ALERT DIALOG'),
|
||||
onPressed: onShowAlertDialogPressed,
|
||||
),
|
||||
RaisedButton(
|
||||
key: const ValueKey<String>('TogglePlatformView'),
|
||||
child: const Text('TOGGLE PLATFORM VIEW'),
|
||||
onPressed: onTogglePlatformView,
|
||||
),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
RaisedButton(
|
||||
|
@ -120,6 +129,12 @@ class NestedViewEventBodyState extends State<NestedViewEventBody> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> onTogglePlatformView() async {
|
||||
setState(() {
|
||||
showPlatformView = !showPlatformView;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> onChildViewPressed() async {
|
||||
try {
|
||||
await viewChannel.invokeMethod<void>('addChildViewAndWaitForClick');
|
||||
|
@ -152,5 +167,7 @@ class NestedViewEventBodyState extends State<NestedViewEventBody> {
|
|||
setState(() {
|
||||
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.
|
||||
|
||||
test('MotionEvent recomposition', () async {
|
||||
final SerializableFinder motionEventsListTile =
|
||||
find.byValueKey('MotionEventsListTile');
|
||||
final SerializableFinder motionEventsListTile = find.byValueKey('MotionEventsListTile');
|
||||
await driver.tap(motionEventsListTile);
|
||||
await driver.waitFor(find.byValueKey('PlatformView'));
|
||||
final String errorMessage = await driver.requestData('run test');
|
||||
|
@ -30,8 +28,7 @@ Future<void> main() async {
|
|||
await driver.tap(backButton);
|
||||
});
|
||||
|
||||
group('Nested View Event', ()
|
||||
{
|
||||
group('Nested View Event', () {
|
||||
setUpAll(() async {
|
||||
final SerializableFinder wmListTile =
|
||||
find.byValueKey('NestedViewEventTile');
|
||||
|
@ -44,8 +41,7 @@ Future<void> main() async {
|
|||
});
|
||||
|
||||
test('AlertDialog from platform view context', () async {
|
||||
final SerializableFinder showAlertDialog = find.byValueKey(
|
||||
'ShowAlertDialog');
|
||||
final SerializableFinder showAlertDialog = find.byValueKey('ShowAlertDialog');
|
||||
await driver.waitFor(showAlertDialog);
|
||||
await driver.tap(showAlertDialog);
|
||||
final String status = await driver.getText(find.byValueKey('Status'));
|
||||
|
@ -58,8 +54,60 @@ Future<void> main() async {
|
|||
await driver.tap(addChildView);
|
||||
final SerializableFinder tapChildView = find.byValueKey('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');
|
||||
});
|
||||
});
|
||||
|
||||
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…
Reference in a new issue