| // 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(); |
| } |