blob: e24c9d872b87ad17413dadd54325a54edb5fc789 [file] [log] [blame]
// Copyright 2013 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 'dart:io' as io;
import 'dart:typed_data' show ByteData, Endian, Uint8List;
import 'dart:ui' as ui;
import 'dart:convert';
// Signals a waiting latch in the native test.
@pragma('vm:external-name', 'Signal')
external void signal();
// Signals a waiting latch in the native test, passing a boolean value.
@pragma('vm:external-name', 'SignalBoolValue')
external void signalBoolValue(bool value);
// Signals a waiting latch in the native test, passing a string value.
@pragma('vm:external-name', 'SignalStringValue')
external void signalStringValue(String value);
// Signals a waiting latch in the native test, which returns a value to the fixture.
@pragma('vm:external-name', 'SignalBoolReturn')
external bool signalBoolReturn();
// Notify the native test that the first frame has been scheduled.
@pragma('vm:external-name', 'NotifyFirstFrameScheduled')
external void notifyFirstFrameScheduled();
void main() {}
@pragma('vm:entry-point')
void hiPlatformChannels() {
ui.channelBuffers.setListener('hi',
(ByteData? data, ui.PlatformMessageResponseCallback callback) async {
ui.PlatformDispatcher.instance.sendPlatformMessage('hi', data,
(ByteData? reply) {
ui.PlatformDispatcher.instance
.sendPlatformMessage('hi', reply, (ByteData? reply) {});
});
callback(data);
});
}
/// Returns a future that completes when
/// `PlatformDispatcher.instance.onSemanticsEnabledChanged` fires.
Future<void> get semanticsChanged {
final Completer<void> semanticsChanged = Completer<void>();
ui.PlatformDispatcher.instance.onSemanticsEnabledChanged =
semanticsChanged.complete;
return semanticsChanged.future;
}
@pragma('vm:entry-point')
void sendAccessibilityAnnouncement() async {
// Wait until semantics are enabled.
if (!ui.PlatformDispatcher.instance.semanticsEnabled) {
await semanticsChanged;
}
// Standard message codec magic number identifiers.
// See: https://github.com/flutter/flutter/blob/ee94fe262b63b0761e8e1f889ae52322fef068d2/packages/flutter/lib/src/services/message_codecs.dart#L262
const int valueMap = 13, valueString = 7;
// Corresponds to: {"type": "announce", "data": {"message": "hello"}}
// See: https://github.com/flutter/flutter/blob/b781da9b5822de1461a769c3b245075359f5464d/packages/flutter/lib/src/semantics/semantics_event.dart#L86
final Uint8List data = Uint8List.fromList([
// Map with 2 entries
valueMap, 2,
// Map key: "type"
valueString, 'type'.length, ...'type'.codeUnits,
// Map value: "announce"
valueString, 'announce'.length, ...'announce'.codeUnits,
// Map key: "data"
valueString, 'data'.length, ...'data'.codeUnits,
// Map value: map with 1 entry
valueMap, 1,
// Map key: "message"
valueString, 'message'.length, ...'message'.codeUnits,
// Map value: "hello"
valueString, 'hello'.length, ...'hello'.codeUnits,
]);
final ByteData byteData = data.buffer.asByteData();
ui.PlatformDispatcher.instance.sendPlatformMessage(
'flutter/accessibility',
byteData,
(ByteData? _) => signal(),
);
}
@pragma('vm:entry-point')
void sendAccessibilityTooltipEvent() async {
// Wait until semantics are enabled.
if (!ui.PlatformDispatcher.instance.semanticsEnabled) {
await semanticsChanged;
}
// Standard message codec magic number identifiers.
// See: https://github.com/flutter/flutter/blob/ee94fe262b63b0761e8e1f889ae52322fef068d2/packages/flutter/lib/src/services/message_codecs.dart#L262
const int valueMap = 13, valueString = 7;
// Corresponds to: {"type": "tooltip", "data": {"message": "hello"}}
// See: https://github.com/flutter/flutter/blob/b781da9b5822de1461a769c3b245075359f5464d/packages/flutter/lib/src/semantics/semantics_event.dart#L120
final Uint8List data = Uint8List.fromList([
// Map with 2 entries
valueMap, 2,
// Map key: "type"
valueString, 'type'.length, ...'type'.codeUnits,
// Map value: "tooltip"
valueString, 'tooltip'.length, ...'tooltip'.codeUnits,
// Map key: "data"
valueString, 'data'.length, ...'data'.codeUnits,
// Map value: map with 1 entry
valueMap, 1,
// Map key: "message"
valueString, 'message'.length, ...'message'.codeUnits,
// Map value: "hello"
valueString, 'hello'.length, ...'hello'.codeUnits,
]);
final ByteData byteData = data.buffer.asByteData();
ui.PlatformDispatcher.instance.sendPlatformMessage(
'flutter/accessibility',
byteData,
(ByteData? _) => signal(),
);
}
@pragma('vm:entry-point')
void exitTestExit() async {
final Completer<ByteData?> closed = Completer<ByteData?>();
ui.channelBuffers.setListener('flutter/platform', (ByteData? data, ui.PlatformMessageResponseCallback callback) async {
final String jsonString = json.encode(<Map<String, String>>[{'response': 'exit'}]);
final ByteData responseData = ByteData.sublistView(utf8.encode(jsonString));
callback(responseData);
closed.complete(data);
});
await closed.future;
}
@pragma('vm:entry-point')
void exitTestCancel() async {
final Completer<ByteData?> closed = Completer<ByteData?>();
ui.channelBuffers.setListener('flutter/platform', (ByteData? data, ui.PlatformMessageResponseCallback callback) async {
final String jsonString = json.encode(<Map<String, String>>[{'response': 'cancel'}]);
final ByteData responseData = ByteData.sublistView(utf8.encode(jsonString));
callback(responseData);
closed.complete(data);
});
await closed.future;
// Because the request was canceled, the below shall execute.
final Completer<ByteData?> exited = Completer<ByteData?>();
final String jsonString = json.encode(<String, dynamic>{
'method': 'System.exitApplication',
'args': <String, dynamic>{
'type': 'required', 'exitCode': 0
}
});
ui.PlatformDispatcher.instance.sendPlatformMessage(
'flutter/platform',
ByteData.sublistView(utf8.encode(jsonString)),
(ByteData? reply) {
exited.complete(reply);
});
await exited.future;
}
@pragma('vm:entry-point')
void enableLifecycleTest() async {
final Completer<ByteData?> finished = Completer<ByteData?>();
ui.channelBuffers.setListener('flutter/lifecycle', (ByteData? data, ui.PlatformMessageResponseCallback callback) async {
if (data != null) {
ui.PlatformDispatcher.instance.sendPlatformMessage(
'flutter/unittest',
data,
(ByteData? reply) {
finished.complete();
});
}
});
await finished.future;
}
@pragma('vm:entry-point')
void enableLifecycleToFrom() async {
ui.channelBuffers.setListener('flutter/lifecycle', (ByteData? data, ui.PlatformMessageResponseCallback callback) async {
if (data != null) {
ui.PlatformDispatcher.instance.sendPlatformMessage(
'flutter/unittest',
data,
(ByteData? reply) {});
}
});
final Completer<ByteData?> enabledLifecycle = Completer<ByteData?>();
ui.PlatformDispatcher.instance.sendPlatformMessage('flutter/platform', ByteData.sublistView(utf8.encode('{"method":"System.initializationComplete"}')), (ByteData? data) {
enabledLifecycle.complete(data);
});
}
@pragma('vm:entry-point')
void sendCreatePlatformViewMethod() async {
// The platform view method channel uses the standard method codec.
// See https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/services/message_codecs.dart#L262
// for the implementation of the encoding and magic number identifiers.
const int valueString = 7;
const int valueMap = 13;
const int valueInt32 = 3;
const String method = 'create';
const String typeKey = 'viewType';
const String typeValue = 'type';
const String idKey = 'id';
final List<int> data = <int>[
// Method name
valueString, method.length, ...utf8.encode(method),
// Method arguments: {'type': 'type':, 'id': 0}
valueMap, 2,
valueString, typeKey.length, ...utf8.encode(typeKey),
valueString, typeValue.length, ...utf8.encode(typeValue),
valueString, idKey.length, ...utf8.encode(idKey),
valueInt32, 0, 0, 0, 0,
];
final Completer<ByteData?> completed = Completer<ByteData?>();
final ByteData bytes = ByteData.sublistView(Uint8List.fromList(data));
ui.PlatformDispatcher.instance.sendPlatformMessage('flutter/platform_views', bytes, (ByteData? response) {
completed.complete(response);
});
await completed.future;
}
@pragma('vm:entry-point')
void sendGetKeyboardState() async {
// The keyboard method channel uses the standard method codec.
// See https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/services/message_codecs.dart#L262
// for the implementation of the encoding and magic number identifiers.
const int valueNull = 0;
const int valueString = 7;
const int valueMap = 13;
const String method = 'getKeyboardState';
final List<int> data = <int>[
// Method name
valueString, method.length, ...utf8.encode(method),
// Method arguments: null
valueNull, 2,
];
final Completer<void> completer = Completer<void>();
final ByteData bytes = ByteData.sublistView(Uint8List.fromList(data));
ui.PlatformDispatcher.instance.sendPlatformMessage('flutter/keyboard', bytes, (ByteData? response) {
// For magic numbers for decoding a reply envelope, see:
// https://github.com/flutter/flutter/blob/67271f69f7f88a4edba6d8023099e3bd27a072d2/packages/flutter/lib/src/services/message_codecs.dart#L577-L587
const int replyEnvelopeSuccess = 0;
// Ensure the response is a success containing a map of keyboard states.
if (response == null) {
signalStringValue('Unexpected null response');
} else if (response.lengthInBytes < 2) {
signalStringValue('Unexpected response length of ${response.lengthInBytes} bytes');
} else if (response.getUint8(0) != replyEnvelopeSuccess) {
signalStringValue('Unexpected response envelope status: ${response.getUint8(0)}');
} else if (response.getUint8(1) != valueMap) {
signalStringValue('Unexpected response value magic number: ${response.getUint8(1)}');
} else {
signalStringValue('Success');
}
completer.complete();
});
await completer.future;
}
@pragma('vm:entry-point')
void customEntrypoint() {}
@pragma('vm:entry-point')
void verifyNativeFunction() {
signal();
}
@pragma('vm:entry-point')
void verifyNativeFunctionWithParameters() {
signalBoolValue(true);
}
@pragma('vm:entry-point')
void verifyNativeFunctionWithReturn() {
bool value = signalBoolReturn();
signalBoolValue(value);
}
@pragma('vm:entry-point')
void readPlatformExecutable() {
signalStringValue(io.Platform.executable);
}
@pragma('vm:entry-point')
void drawHelloWorld() {
ui.PlatformDispatcher.instance.onBeginFrame = (Duration duration) {
final ui.ParagraphBuilder paragraphBuilder =
ui.ParagraphBuilder(ui.ParagraphStyle())..addText('Hello world');
final ui.Paragraph paragraph = paragraphBuilder.build();
paragraph.layout(const ui.ParagraphConstraints(width: 800.0));
final ui.PictureRecorder recorder = ui.PictureRecorder();
final ui.Canvas canvas = ui.Canvas(recorder);
canvas.drawParagraph(paragraph, ui.Offset.zero);
final ui.Picture picture = recorder.endRecording();
final ui.SceneBuilder sceneBuilder = ui.SceneBuilder()
..addPicture(ui.Offset.zero, picture)
..pop();
ui.PlatformDispatcher.instance.implicitView?.render(sceneBuilder.build());
};
ui.PlatformDispatcher.instance.scheduleFrame();
notifyFirstFrameScheduled();
}
ui.Picture _createColoredBox(ui.Color color, ui.Size size) {
final ui.Paint paint = ui.Paint();
paint.color = color;
final ui.PictureRecorder baseRecorder = ui.PictureRecorder();
final ui.Canvas canvas = ui.Canvas(baseRecorder);
canvas.drawRect(ui.Rect.fromLTRB(0.0, 0.0, size.width, size.height), paint);
return baseRecorder.endRecording();
}
@pragma('vm:entry-point')
void renderImplicitView() {
ui.PlatformDispatcher.instance.onBeginFrame = (Duration duration) {
final ui.Size size = ui.Size(800.0, 600.0);
final ui.Color red = ui.Color.fromARGB(127, 255, 0, 0);
final ui.SceneBuilder builder = ui.SceneBuilder();
builder.pushOffset(0.0, 0.0);
builder.addPicture(
ui.Offset(0.0, 0.0), _createColoredBox(red, size));
builder.pop();
ui.PlatformDispatcher.instance.implicitView?.render(builder.build());
};
ui.PlatformDispatcher.instance.scheduleFrame();
}
@pragma('vm:entry-point')
void signalViewIds() {
final Iterable<ui.FlutterView> views = ui.PlatformDispatcher.instance.views;
final List<int> viewIds =
views.map((ui.FlutterView view) => view.viewId).toList();
viewIds.sort();
signalStringValue('View IDs: [${viewIds.join(', ')}]');
}
@pragma('vm:entry-point')
void onMetricsChangedSignalViewIds() {
ui.PlatformDispatcher.instance.onMetricsChanged = () {
signalViewIds();
};
signal();
}