From 70ff50f92947ee14d288a42e9dd1842fb1d9c3e6 Mon Sep 17 00:00:00 2001 From: Mikkel Nygaard Ravn Date: Thu, 27 Apr 2017 08:44:28 +0200 Subject: [PATCH] Integration test for channel communication (#9621) --- bin/internal/engine.version | 2 +- ...st.dart => channels_integration_test.dart} | 4 +- ...art => channels_integration_test_ios.dart} | 4 +- .../tasks/channels_integration_test_win.dart | 14 + .../tasks/platform_channel_sample_test.dart | 14 + .../platform_channel_sample_test_ios.dart | 14 + .../lib/tasks/integration_tests.dart | 57 ++ dev/devicelab/lib/tasks/perf_tests.dart | 42 -- dev/devicelab/manifest.yaml | 23 +- dev/integration_tests/README.md | 4 + dev/integration_tests/channels/.gitignore | 10 + dev/integration_tests/channels/README.md | 3 + .../channels/android/.gitignore | 12 + .../channels/android/app/build.gradle | 46 ++ .../android/app/src/main/AndroidManifest.xml | 32 ++ .../yourcompany/channels/MainActivity.java | 142 +++++ .../io/flutter/plugins/PluginRegistry.java | 14 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../channels/android/build.gradle | 29 + .../channels/android/gradle.properties | 1 + .../channels/android/settings.gradle | 15 + dev/integration_tests/channels/ios/.gitignore | 39 ++ .../ios/Flutter/AppFrameworkInfo.plist | 30 ++ .../channels/ios/Flutter/Debug.xcconfig | 4 + .../channels/ios/Flutter/Release.xcconfig | 4 + .../channels/ios/Flutter/TestConfig.xcconfig | 5 + dev/integration_tests/channels/ios/Podfile | 38 ++ .../channels/ios/Podfile.lock | 16 + .../ios/Runner.xcodeproj/project.pbxproj | 505 ++++++++++++++++++ .../contents.xcworkspacedata | 10 + .../xcshareddata/xcschemes/Runner.xcscheme | 91 ++++ .../contents.xcworkspacedata | 10 + .../channels/ios/Runner/AppDelegate.h | 10 + .../channels/ios/Runner/AppDelegate.m | 99 ++++ .../AppIcon.appiconset/Contents.json | 116 ++++ .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 564 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 1283 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 1588 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 1025 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 1716 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 1920 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 1283 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 1895 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 2665 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 2665 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 3831 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 1888 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 3294 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 3612 bytes .../Runner/Base.lproj/LaunchScreen.storyboard | 27 + .../ios/Runner/Base.lproj/Main.storyboard | 26 + .../channels/ios/Runner/Info.plist | 49 ++ .../channels/ios/Runner/PluginRegistry.h | 18 + .../channels/ios/Runner/PluginRegistry.m | 15 + .../channels/ios/Runner/main.m | 14 + dev/integration_tests/channels/lib/main.dart | 193 +++++++ .../channels/lib/src/basic_messaging.dart | 144 +++++ .../channels/lib/src/method_calls.dart | 133 +++++ .../channels/lib/src/test_step.dart | 168 ++++++ dev/integration_tests/channels/pubspec.yaml | 10 + .../channels/test_driver/main_test.dart | 34 ++ .../lib/src/services/message_codec.dart | 25 +- .../lib/src/services/platform_channel.dart | 203 +++---- .../lib/src/services/platform_messages.dart | 15 +- .../test/services/platform_channel_test.dart | 73 ++- 69 files changed, 2420 insertions(+), 186 deletions(-) rename dev/devicelab/bin/tasks/{platform_channel_test.dart => channels_integration_test.dart} (77%) rename dev/devicelab/bin/tasks/{platform_channel_test_ios.dart => channels_integration_test_ios.dart} (77%) create mode 100644 dev/devicelab/bin/tasks/channels_integration_test_win.dart create mode 100644 dev/devicelab/bin/tasks/platform_channel_sample_test.dart create mode 100644 dev/devicelab/bin/tasks/platform_channel_sample_test_ios.dart create mode 100644 dev/devicelab/lib/tasks/integration_tests.dart create mode 100644 dev/integration_tests/README.md create mode 100644 dev/integration_tests/channels/.gitignore create mode 100644 dev/integration_tests/channels/README.md create mode 100644 dev/integration_tests/channels/android/.gitignore create mode 100644 dev/integration_tests/channels/android/app/build.gradle create mode 100644 dev/integration_tests/channels/android/app/src/main/AndroidManifest.xml create mode 100644 dev/integration_tests/channels/android/app/src/main/java/com/yourcompany/channels/MainActivity.java create mode 100644 dev/integration_tests/channels/android/app/src/main/java/io/flutter/plugins/PluginRegistry.java create mode 100644 dev/integration_tests/channels/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 dev/integration_tests/channels/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 dev/integration_tests/channels/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 dev/integration_tests/channels/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 dev/integration_tests/channels/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 dev/integration_tests/channels/android/build.gradle create mode 100644 dev/integration_tests/channels/android/gradle.properties create mode 100644 dev/integration_tests/channels/android/settings.gradle create mode 100644 dev/integration_tests/channels/ios/.gitignore create mode 100644 dev/integration_tests/channels/ios/Flutter/AppFrameworkInfo.plist create mode 100644 dev/integration_tests/channels/ios/Flutter/Debug.xcconfig create mode 100644 dev/integration_tests/channels/ios/Flutter/Release.xcconfig create mode 100644 dev/integration_tests/channels/ios/Flutter/TestConfig.xcconfig create mode 100644 dev/integration_tests/channels/ios/Podfile create mode 100644 dev/integration_tests/channels/ios/Podfile.lock create mode 100644 dev/integration_tests/channels/ios/Runner.xcodeproj/project.pbxproj create mode 100644 dev/integration_tests/channels/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 dev/integration_tests/channels/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 dev/integration_tests/channels/ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 dev/integration_tests/channels/ios/Runner/AppDelegate.h create mode 100644 dev/integration_tests/channels/ios/Runner/AppDelegate.m create mode 100644 dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 dev/integration_tests/channels/ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 dev/integration_tests/channels/ios/Runner/Base.lproj/Main.storyboard create mode 100644 dev/integration_tests/channels/ios/Runner/Info.plist create mode 100644 dev/integration_tests/channels/ios/Runner/PluginRegistry.h create mode 100644 dev/integration_tests/channels/ios/Runner/PluginRegistry.m create mode 100644 dev/integration_tests/channels/ios/Runner/main.m create mode 100644 dev/integration_tests/channels/lib/main.dart create mode 100644 dev/integration_tests/channels/lib/src/basic_messaging.dart create mode 100644 dev/integration_tests/channels/lib/src/method_calls.dart create mode 100644 dev/integration_tests/channels/lib/src/test_step.dart create mode 100644 dev/integration_tests/channels/pubspec.yaml create mode 100644 dev/integration_tests/channels/test_driver/main_test.dart diff --git a/bin/internal/engine.version b/bin/internal/engine.version index 63bdadf487..2482519321 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -a5b64899c9392183b4b5df7e89fa8f0950a8e509 +3211d2fca262ba348a0bbcecefeb3e1b0b832faf diff --git a/dev/devicelab/bin/tasks/platform_channel_test.dart b/dev/devicelab/bin/tasks/channels_integration_test.dart similarity index 77% rename from dev/devicelab/bin/tasks/platform_channel_test.dart rename to dev/devicelab/bin/tasks/channels_integration_test.dart index d1afc673c8..8b60289dfe 100644 --- a/dev/devicelab/bin/tasks/platform_channel_test.dart +++ b/dev/devicelab/bin/tasks/channels_integration_test.dart @@ -4,11 +4,11 @@ import 'dart:async'; -import 'package:flutter_devicelab/tasks/perf_tests.dart'; import 'package:flutter_devicelab/framework/adb.dart'; import 'package:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/tasks/integration_tests.dart'; Future main() async { deviceOperatingSystem = DeviceOperatingSystem.android; - await task(createPlatformServiceDriverTest()); + await task(createChannelsIntegrationTest()); } diff --git a/dev/devicelab/bin/tasks/platform_channel_test_ios.dart b/dev/devicelab/bin/tasks/channels_integration_test_ios.dart similarity index 77% rename from dev/devicelab/bin/tasks/platform_channel_test_ios.dart rename to dev/devicelab/bin/tasks/channels_integration_test_ios.dart index 07760fb43b..9ac1a3ace6 100644 --- a/dev/devicelab/bin/tasks/platform_channel_test_ios.dart +++ b/dev/devicelab/bin/tasks/channels_integration_test_ios.dart @@ -4,11 +4,11 @@ import 'dart:async'; -import 'package:flutter_devicelab/tasks/perf_tests.dart'; import 'package:flutter_devicelab/framework/adb.dart'; import 'package:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/tasks/integration_tests.dart'; Future main() async { deviceOperatingSystem = DeviceOperatingSystem.ios; - await task(createPlatformServiceDriverTest()); + await task(createChannelsIntegrationTest()); } diff --git a/dev/devicelab/bin/tasks/channels_integration_test_win.dart b/dev/devicelab/bin/tasks/channels_integration_test_win.dart new file mode 100644 index 0000000000..8b60289dfe --- /dev/null +++ b/dev/devicelab/bin/tasks/channels_integration_test_win.dart @@ -0,0 +1,14 @@ +// Copyright 2017 The Chromium 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_devicelab/framework/adb.dart'; +import 'package:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/tasks/integration_tests.dart'; + +Future main() async { + deviceOperatingSystem = DeviceOperatingSystem.android; + await task(createChannelsIntegrationTest()); +} diff --git a/dev/devicelab/bin/tasks/platform_channel_sample_test.dart b/dev/devicelab/bin/tasks/platform_channel_sample_test.dart new file mode 100644 index 0000000000..38b10b375f --- /dev/null +++ b/dev/devicelab/bin/tasks/platform_channel_sample_test.dart @@ -0,0 +1,14 @@ +// Copyright 2017 The Chromium 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_devicelab/framework/adb.dart'; +import 'package:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/tasks/integration_tests.dart'; + +Future main() async { + deviceOperatingSystem = DeviceOperatingSystem.android; + await task(createPlatformChannelSampleTest()); +} diff --git a/dev/devicelab/bin/tasks/platform_channel_sample_test_ios.dart b/dev/devicelab/bin/tasks/platform_channel_sample_test_ios.dart new file mode 100644 index 0000000000..9feeaa5f75 --- /dev/null +++ b/dev/devicelab/bin/tasks/platform_channel_sample_test_ios.dart @@ -0,0 +1,14 @@ +// Copyright 2017 The Chromium 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_devicelab/framework/adb.dart'; +import 'package:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/tasks/integration_tests.dart'; + +Future main() async { + deviceOperatingSystem = DeviceOperatingSystem.ios; + await task(createPlatformChannelSampleTest()); +} diff --git a/dev/devicelab/lib/tasks/integration_tests.dart b/dev/devicelab/lib/tasks/integration_tests.dart new file mode 100644 index 0000000000..004e7e5939 --- /dev/null +++ b/dev/devicelab/lib/tasks/integration_tests.dart @@ -0,0 +1,57 @@ +// Copyright (c) 2017 The Chromium 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 '../framework/adb.dart'; +import '../framework/framework.dart'; +import '../framework/ios.dart'; +import '../framework/utils.dart'; + +TaskFunction createChannelsIntegrationTest() { + return new DriverTest( + '${flutterDirectory.path}/dev/integration_tests/channels', + 'lib/main.dart', + ); +} + +TaskFunction createPlatformChannelSampleTest() { + return new DriverTest( + '${flutterDirectory.path}/examples/platform_channel', + 'test_driver/button_tap.dart', + ); +} + +class DriverTest { + + DriverTest(this.testDirectory, this.testTarget); + + final String testDirectory; + final String testTarget; + + Future call() { + return inDirectory(testDirectory, () async { + final Device device = await devices.workingDevice; + await device.unlock(); + final String deviceId = device.deviceId; + await flutter('packages', options: ['get']); + + if (deviceOperatingSystem == DeviceOperatingSystem.ios) { + await prepareProvisioningCertificates(testDirectory); + // This causes an Xcode project to be created. + await flutter('build', options: ['ios', '--profile']); + } + + await flutter('drive', options: [ + '-v', + '-t', + testTarget, + '-d', + deviceId, + ]); + + return new TaskResult.success(null); + }); + } +} diff --git a/dev/devicelab/lib/tasks/perf_tests.dart b/dev/devicelab/lib/tasks/perf_tests.dart index 87da30c8f6..c59f216ceb 100644 --- a/dev/devicelab/lib/tasks/perf_tests.dart +++ b/dev/devicelab/lib/tasks/perf_tests.dart @@ -10,14 +10,6 @@ import '../framework/framework.dart'; import '../framework/ios.dart'; import '../framework/utils.dart'; - -TaskFunction createPlatformServiceDriverTest() { - return new DriverTest( - '${flutterDirectory.path}/examples/platform_channel', - 'test_driver/button_tap.dart', - ); -} - TaskFunction createComplexLayoutScrollPerfTest() { return new PerfTest( '${flutterDirectory.path}/dev/benchmarks/complex_layout', @@ -175,40 +167,6 @@ class PerfTest { } } - -class DriverTest { - - DriverTest(this.testDirectory, this.testTarget); - - final String testDirectory; - final String testTarget; - - Future call() { - return inDirectory(testDirectory, () async { - final Device device = await devices.workingDevice; - await device.unlock(); - final String deviceId = device.deviceId; - await flutter('packages', options: ['get']); - - if (deviceOperatingSystem == DeviceOperatingSystem.ios) { - await prepareProvisioningCertificates(testDirectory); - // This causes an Xcode project to be created. - await flutter('build', options: ['ios', '--profile']); - } - - await flutter('drive', options: [ - '-v', - '-t', - testTarget, - '-d', - deviceId, - ]); - - return new TaskResult.success(null); - }); - } -} - class BuildTest { BuildTest(this.testDirectory); diff --git a/dev/devicelab/manifest.yaml b/dev/devicelab/manifest.yaml index 2aeb98d6b9..b527fd3f94 100644 --- a/dev/devicelab/manifest.yaml +++ b/dev/devicelab/manifest.yaml @@ -83,7 +83,13 @@ tasks: required_agent_capabilities: ["has-android-device"] flaky: true - platform_channel_test: + channels_integration_test: + description: > + Checks that platform channels work on Android. + stage: devicelab + required_agent_capabilities: ["has-android-device"] + + platform_channel_sample_test: description: > Runs a driver test on the Platform Channel sample app on Android. stage: devicelab @@ -154,7 +160,13 @@ tasks: # iOS on-device tests - platform_channel_test_ios: + channels_integration_test_ios: + description: > + Checks that platform channels work on iOS. + stage: devicelab_ios + required_agent_capabilities: ["has-ios-device"] + + platform_channel_sample_test_ios: description: > Runs a driver test on the Platform Channel sample app on iOS. stage: devicelab_ios @@ -207,6 +219,13 @@ tasks: # Tests running on Windows host + channels_integration_test_win: + description: > + Checks that platform channels work when app is launched from Windows. + stage: devicelab_win + required_agent_capabilities: ["windows"] + flaky: true + flutter_gallery_win__build: description: > Collects various performance metrics from AOT builds of the Flutter diff --git a/dev/integration_tests/README.md b/dev/integration_tests/README.md new file mode 100644 index 0000000000..b385c938fb --- /dev/null +++ b/dev/integration_tests/README.md @@ -0,0 +1,4 @@ +Automated Flutter integration test suites. Each suite consists of a complete +Flutter app and a `flutter_driver` specification that drives tests from the UI. + +Intended for use with devicelab. diff --git a/dev/integration_tests/channels/.gitignore b/dev/integration_tests/channels/.gitignore new file mode 100644 index 0000000000..eb15c3d27c --- /dev/null +++ b/dev/integration_tests/channels/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +.atom/ +.idea +.packages +.pub/ +build/ +ios/.generated/ +packages +pubspec.lock +.flutter-plugins diff --git a/dev/integration_tests/channels/README.md b/dev/integration_tests/channels/README.md new file mode 100644 index 0000000000..600469887c --- /dev/null +++ b/dev/integration_tests/channels/README.md @@ -0,0 +1,3 @@ +# channels + +Integration test of platform channels. diff --git a/dev/integration_tests/channels/android/.gitignore b/dev/integration_tests/channels/android/.gitignore new file mode 100644 index 0000000000..5c4ef82869 --- /dev/null +++ b/dev/integration_tests/channels/android/.gitignore @@ -0,0 +1,12 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures + +/gradle +/gradlew +/gradlew.bat diff --git a/dev/integration_tests/channels/android/app/build.gradle b/dev/integration_tests/channels/android/app/build.gradle new file mode 100644 index 0000000000..4e2e46b56c --- /dev/null +++ b/dev/integration_tests/channels/android/app/build.gradle @@ -0,0 +1,46 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withInputStream { stream -> + localProperties.load(stream) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +apply plugin: 'com.android.application' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 25 + buildToolsVersion '25.0.2' + + lintOptions { + disable 'InvalidPackage' + } + + defaultConfig { + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + androidTestCompile 'com.android.support:support-annotations:25.0.0' + androidTestCompile 'com.android.support.test:runner:0.5' + androidTestCompile 'com.android.support.test:rules:0.5' +} diff --git a/dev/integration_tests/channels/android/app/src/main/AndroidManifest.xml b/dev/integration_tests/channels/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..b63cc17387 --- /dev/null +++ b/dev/integration_tests/channels/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + diff --git a/dev/integration_tests/channels/android/app/src/main/java/com/yourcompany/channels/MainActivity.java b/dev/integration_tests/channels/android/app/src/main/java/com/yourcompany/channels/MainActivity.java new file mode 100644 index 0000000000..ebd9f18c8c --- /dev/null +++ b/dev/integration_tests/channels/android/app/src/main/java/com/yourcompany/channels/MainActivity.java @@ -0,0 +1,142 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package com.yourcompany.channels; + +import java.nio.ByteBuffer; + +import android.os.Bundle; + +import io.flutter.app.FlutterActivity; +import io.flutter.plugin.common.*; +import io.flutter.plugins.PluginRegistry; + +public class MainActivity extends FlutterActivity { + PluginRegistry pluginRegistry; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + pluginRegistry = new PluginRegistry(); + pluginRegistry.registerAll(this); + + setupMessageHandshake(new BasicMessageChannel<>(getFlutterView(), "binary-msg", BinaryCodec.INSTANCE)); + setupMessageHandshake(new BasicMessageChannel<>(getFlutterView(), "string-msg", StringCodec.INSTANCE)); + setupMessageHandshake(new BasicMessageChannel<>(getFlutterView(), "json-msg", JSONMessageCodec.INSTANCE)); + setupMessageHandshake(new BasicMessageChannel<>(getFlutterView(), "std-msg", StandardMessageCodec.INSTANCE)); + setupMethodHandshake(new MethodChannel(getFlutterView(), "json-method", JSONMethodCodec.INSTANCE)); + setupMethodHandshake(new MethodChannel(getFlutterView(), "std-method", StandardMethodCodec.INSTANCE)); + } + + private void setupMessageHandshake(final BasicMessageChannel channel) { + // On message receipt, do a send/reply/send round-trip in the other direction, + // then reply to the first message. + channel.setMessageHandler(new BasicMessageChannel.MessageHandler() { + @Override + public void onMessage(final T message, final BasicMessageChannel.Reply reply) { + final T messageEcho = echo(message); + channel.send(messageEcho, new BasicMessageChannel.Reply() { + @Override + public void reply(T replyMessage) { + channel.send(echo(replyMessage)); + reply.reply(messageEcho); + } + }); + } + }); + } + + // Outgoing ByteBuffer messages must be direct-allocated and payload placed between + // positon 0 and current position. + @SuppressWarnings("unchecked") + private T echo(T message) { + if (message instanceof ByteBuffer) { + final ByteBuffer buffer = (ByteBuffer) message; + final ByteBuffer echo = ByteBuffer.allocateDirect(buffer.remaining()); + echo.put(buffer); + return (T) echo; + } + return message; + } + + private void setupMethodHandshake(final MethodChannel channel) { + channel.setMethodCallHandler(new MethodChannel.MethodCallHandler() { + @Override + public void onMethodCall(final MethodCall methodCall, final MethodChannel.Result result) { + switch (methodCall.method) { + case "success": + doSuccessHandshake(channel, methodCall, result); + break; + case "error": + doErrorHandshake(channel, methodCall, result); + break; + default: + doNotImplementedHandshake(channel, methodCall, result); + break; + + } + } + }); + } + + private void doSuccessHandshake(final MethodChannel channel, final MethodCall methodCall, final MethodChannel.Result result) { + channel.invokeMethod(methodCall.method, methodCall.arguments, new MethodChannel.Result() { + @Override + public void success(Object o) { + channel.invokeMethod(methodCall.method, o); + result.success(methodCall.arguments); + } + + @Override + public void error(String code, String message, Object details) { + throw new AssertionError("Should not be called"); + } + + @Override + public void notImplemented() { + throw new AssertionError("Should not be called"); + } + }); + } + + private void doErrorHandshake(final MethodChannel channel, final MethodCall methodCall, final MethodChannel.Result result) { + channel.invokeMethod(methodCall.method, methodCall.arguments, new MethodChannel.Result() { + @Override + public void success(Object o) { + throw new AssertionError("Should not be called"); + } + + @Override + public void error(String code, String message, Object details) { + channel.invokeMethod(methodCall.method, details); + result.error(code, message, methodCall.arguments); + } + + @Override + public void notImplemented() { + throw new AssertionError("Should not be called"); + } + }); + } + + private void doNotImplementedHandshake(final MethodChannel channel, final MethodCall methodCall, final MethodChannel.Result result) { + channel.invokeMethod(methodCall.method, methodCall.arguments, new MethodChannel.Result() { + @Override + public void success(Object o) { + throw new AssertionError("Should not be called"); + } + + @Override + public void error(String code, String message, Object details) { + throw new AssertionError("Should not be called"); + } + + @Override + public void notImplemented() { + channel.invokeMethod(methodCall.method, null); + result.notImplemented(); + } + }); + } +} diff --git a/dev/integration_tests/channels/android/app/src/main/java/io/flutter/plugins/PluginRegistry.java b/dev/integration_tests/channels/android/app/src/main/java/io/flutter/plugins/PluginRegistry.java new file mode 100644 index 0000000000..92f7a749c4 --- /dev/null +++ b/dev/integration_tests/channels/android/app/src/main/java/io/flutter/plugins/PluginRegistry.java @@ -0,0 +1,14 @@ +package io.flutter.plugins; + +import io.flutter.app.FlutterActivity; + + +/** + * Generated file. Do not edit. + */ + +public class PluginRegistry { + + public void registerAll(FlutterActivity activity) { + } +} diff --git a/dev/integration_tests/channels/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/dev/integration_tests/channels/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/dev/integration_tests/channels/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/dev/integration_tests/channels/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/dev/integration_tests/channels/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/dev/integration_tests/channels/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/dev/integration_tests/channels/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/dev/integration_tests/channels/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/dev/integration_tests/channels/android/build.gradle b/dev/integration_tests/channels/android/build.gradle new file mode 100644 index 0000000000..ee5325df80 --- /dev/null +++ b/dev/integration_tests/channels/android/build.gradle @@ -0,0 +1,29 @@ +buildscript { + repositories { + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:2.2.3' + } +} + +allprojects { + repositories { + jcenter() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} + +task wrapper(type: Wrapper) { + gradleVersion = '2.14.1' +} diff --git a/dev/integration_tests/channels/android/gradle.properties b/dev/integration_tests/channels/android/gradle.properties new file mode 100644 index 0000000000..8bd86f6805 --- /dev/null +++ b/dev/integration_tests/channels/android/gradle.properties @@ -0,0 +1 @@ +org.gradle.jvmargs=-Xmx1536M diff --git a/dev/integration_tests/channels/android/settings.gradle b/dev/integration_tests/channels/android/settings.gradle new file mode 100644 index 0000000000..115da6cb4f --- /dev/null +++ b/dev/integration_tests/channels/android/settings.gradle @@ -0,0 +1,15 @@ +include ':app' + +def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() + +def plugins = new Properties() +def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') +if (pluginsFile.exists()) { + pluginsFile.withInputStream { stream -> plugins.load(stream) } +} + +plugins.each { name, path -> + def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() + include ":$name" + project(":$name").projectDir = pluginDirectory +} diff --git a/dev/integration_tests/channels/ios/.gitignore b/dev/integration_tests/channels/ios/.gitignore new file mode 100644 index 0000000000..797c015957 --- /dev/null +++ b/dev/integration_tests/channels/ios/.gitignore @@ -0,0 +1,39 @@ +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +.DS_Store +*.swp +profile + +DerivedData/ +build/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +/Flutter/app.flx +/Flutter/app.zip +/Flutter/App.framework +/Flutter/Flutter.framework +/Flutter/Generated.xcconfig +/ServiceDefinitions.json + +Pods/ diff --git a/dev/integration_tests/channels/ios/Flutter/AppFrameworkInfo.plist b/dev/integration_tests/channels/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000000..6c2de8086b --- /dev/null +++ b/dev/integration_tests/channels/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,30 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + UIRequiredDeviceCapabilities + + arm64 + + MinimumOSVersion + 8.0 + + diff --git a/dev/integration_tests/channels/ios/Flutter/Debug.xcconfig b/dev/integration_tests/channels/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000000..72c15fd3e1 --- /dev/null +++ b/dev/integration_tests/channels/ios/Flutter/Debug.xcconfig @@ -0,0 +1,4 @@ +#include "Generated.xcconfig" +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" + +#include "TestConfig.xcconfig" diff --git a/dev/integration_tests/channels/ios/Flutter/Release.xcconfig b/dev/integration_tests/channels/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000000..8d3ed2b34f --- /dev/null +++ b/dev/integration_tests/channels/ios/Flutter/Release.xcconfig @@ -0,0 +1,4 @@ +#include "Generated.xcconfig" +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" + +#include "TestConfig.xcconfig" diff --git a/dev/integration_tests/channels/ios/Flutter/TestConfig.xcconfig b/dev/integration_tests/channels/ios/Flutter/TestConfig.xcconfig new file mode 100644 index 0000000000..6c52ef8d54 --- /dev/null +++ b/dev/integration_tests/channels/ios/Flutter/TestConfig.xcconfig @@ -0,0 +1,5 @@ +ProvisioningStyle=Manual +CODE_SIGN_IDENTITY=iPhone Developer +PROVISIONING_PROFILE=Xcode Managed Profile +DEVELOPMENT_TEAM=... +PROVISIONING_PROFILE_SPECIFIER=... diff --git a/dev/integration_tests/channels/ios/Podfile b/dev/integration_tests/channels/ios/Podfile new file mode 100644 index 0000000000..74b3de0649 --- /dev/null +++ b/dev/integration_tests/channels/ios/Podfile @@ -0,0 +1,38 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '9.0' + +if ENV['FLUTTER_FRAMEWORK_DIR'] == nil + abort('Please set FLUTTER_FRAMEWORK_DIR to the directory containing Flutter.framework') +end + +target 'Runner' do + use_frameworks! + + # Pods for Runner + + # Flutter Pods + pod 'Flutter', :path => ENV['FLUTTER_FRAMEWORK_DIR'] + + if File.exists? '../.flutter-plugins' + flutter_root = File.expand_path('..') + File.foreach('../.flutter-plugins') { |line| + plugin = line.split(pattern='=') + if plugin.length == 2 + name = plugin[0].strip() + path = plugin[1].strip() + resolved_path = File.expand_path("#{path}/ios", flutter_root) + pod name, :path => resolved_path + else + puts "Invalid plugin specification: #{line}" + end + } + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['ENABLE_BITCODE'] = 'NO' + end + end +end diff --git a/dev/integration_tests/channels/ios/Podfile.lock b/dev/integration_tests/channels/ios/Podfile.lock new file mode 100644 index 0000000000..8e25faa0a3 --- /dev/null +++ b/dev/integration_tests/channels/ios/Podfile.lock @@ -0,0 +1,16 @@ +PODS: + - Flutter (1.0.0) + +DEPENDENCIES: + - Flutter (from `/Users/mravn/github/engine/src/out/ios_debug_unopt`) + +EXTERNAL SOURCES: + Flutter: + :path: "/Users/mravn/github/engine/src/out/ios_debug_unopt" + +SPEC CHECKSUMS: + Flutter: d674e78c937094a75ac71dd77e921e840bea3dbf + +PODFILE CHECKSUM: cc70c01bca487bebd110b87397f017f3b76a89f1 + +COCOAPODS: 1.2.1 diff --git a/dev/integration_tests/channels/ios/Runner.xcodeproj/project.pbxproj b/dev/integration_tests/channels/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..b61088c306 --- /dev/null +++ b/dev/integration_tests/channels/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,505 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* PluginRegistry.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* PluginRegistry.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; + 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 913134F849F6C8DEAC837F96 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C803412E9584DEAC0259A174 /* Pods_Runner.framework */; }; + 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; + 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; + 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; }; + 9740EEBB1CF902C7004384FC /* app.flx in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB71CF902C7004384FC /* app.flx */; }; + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; + 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, + 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* PluginRegistry.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PluginRegistry.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* PluginRegistry.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PluginRegistry.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 9740EEB71CF902C7004384FC /* app.flx */ = {isa = PBXFileReference; lastKnownFileType = file; name = app.flx; path = Flutter/app.flx; sourceTree = ""; }; + 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C803412E9584DEAC0259A174 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, + 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, + 913134F849F6C8DEAC837F96 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 840012C8B5EDBCF56B0E4AC1 /* Pods */ = { + isa = PBXGroup; + children = ( + ); + name = Pods; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 9740EEB71CF902C7004384FC /* app.flx */, + 3B80C3931E831B6300D905FE /* App.framework */, + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEBA1CF902C7004384FC /* Flutter.framework */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 840012C8B5EDBCF56B0E4AC1 /* Pods */, + CF3B75C9A7D2FA2A4C99F110 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, + 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 97C146F11CF9000F007C117D /* Supporting Files */, + 1498D2321E8E86230040F4C2 /* PluginRegistry.h */, + 1498D2331E8E89220040F4C2 /* PluginRegistry.m */, + ); + path = Runner; + sourceTree = ""; + }; + 97C146F11CF9000F007C117D /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 97C146F21CF9000F007C117D /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + CF3B75C9A7D2FA2A4C99F110 /* Frameworks */ = { + isa = PBXGroup; + children = ( + C803412E9584DEAC0259A174 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 95BB15E9E1769C0D146AA592 /* [CP] Embed Pods Frameworks */, + 532EA9D341340B1DCD08293D /* [CP] Copy Pods Resources */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0830; + ORGANIZATIONNAME = "The Chromium Authors"; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + DevelopmentTeam = AQ7UHDBEXJ; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9740EEBB1CF902C7004384FC /* app.flx in Resources */, + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + }; + 532EA9D341340B1DCD08293D /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 95BB15E9E1769C0D146AA592 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, + 97C146F31CF9000F007C117D /* main.m in Sources */, + 1498D2341E8E89220040F4C2 /* PluginRegistry.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ARCHS = arm64; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + DEVELOPMENT_TEAM = AQ7UHDBEXJ; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.yourcompany.channels; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = ""; + ProvisioningStyle = Automatic; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ARCHS = arm64; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + DEVELOPMENT_TEAM = AQ7UHDBEXJ; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.yourcompany.channels; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = ""; + ProvisioningStyle = Automatic; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/dev/integration_tests/channels/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/dev/integration_tests/channels/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..21a3cc14c7 --- /dev/null +++ b/dev/integration_tests/channels/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/dev/integration_tests/channels/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/dev/integration_tests/channels/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000000..1c95807881 --- /dev/null +++ b/dev/integration_tests/channels/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/integration_tests/channels/ios/Runner.xcworkspace/contents.xcworkspacedata b/dev/integration_tests/channels/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..21a3cc14c7 --- /dev/null +++ b/dev/integration_tests/channels/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/dev/integration_tests/channels/ios/Runner/AppDelegate.h b/dev/integration_tests/channels/ios/Runner/AppDelegate.h new file mode 100644 index 0000000000..138b680780 --- /dev/null +++ b/dev/integration_tests/channels/ios/Runner/AppDelegate.h @@ -0,0 +1,10 @@ +// Copyright 2017 The Chromium 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 +#import + +@interface AppDelegate : FlutterAppDelegate + +@end diff --git a/dev/integration_tests/channels/ios/Runner/AppDelegate.m b/dev/integration_tests/channels/ios/Runner/AppDelegate.m new file mode 100644 index 0000000000..94c06d8def --- /dev/null +++ b/dev/integration_tests/channels/ios/Runner/AppDelegate.m @@ -0,0 +1,99 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "AppDelegate.h" +#include "PluginRegistry.h" + +@implementation AppDelegate { + PluginRegistry *plugins; +} + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + // Override point for customization after application launch. + FlutterViewController *flutterController = + (FlutterViewController *)self.window.rootViewController; + plugins = [[PluginRegistry alloc] initWithController:flutterController]; + + [self setupMessagingHandshakeOnChannel: + [FlutterBasicMessageChannel messageChannelWithName:@"binary-msg" + binaryMessenger:flutterController + codec:[FlutterBinaryCodec sharedInstance]]]; + [self setupMessagingHandshakeOnChannel: + [FlutterBasicMessageChannel messageChannelWithName:@"string-msg" + binaryMessenger:flutterController + codec:[FlutterStringCodec sharedInstance]]]; + [self setupMessagingHandshakeOnChannel: + [FlutterBasicMessageChannel messageChannelWithName:@"json-msg" + binaryMessenger:flutterController + codec:[FlutterJSONMessageCodec sharedInstance]]]; + [self setupMessagingHandshakeOnChannel: + [FlutterBasicMessageChannel messageChannelWithName:@"std-msg" + binaryMessenger:flutterController + codec:[FlutterStandardMessageCodec sharedInstance]]]; + [self setupMethodCallSuccessHandshakeOnChannel: + [FlutterMethodChannel methodChannelWithName:@"json-method" + binaryMessenger:flutterController + codec:[FlutterJSONMethodCodec sharedInstance]]]; + [self setupMethodCallSuccessHandshakeOnChannel: + [FlutterMethodChannel methodChannelWithName:@"std-method" + binaryMessenger:flutterController + codec:[FlutterStandardMethodCodec sharedInstance]]]; + return YES; +} + +- (void)setupMessagingHandshakeOnChannel:(FlutterBasicMessageChannel*)channel { + [channel setMessageHandler:^(id message, FlutterReply reply) { + [channel sendMessage:message reply:^(id messageReply) { + [channel sendMessage:messageReply]; + reply(message); + }]; + }]; +} + +- (void)setupMethodCallSuccessHandshakeOnChannel:(FlutterMethodChannel*)channel { + [channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { + if ([call.method isEqual:@"success"]) { + [channel invokeMethod:call.method arguments:call.arguments result:^(id value) { + [channel invokeMethod:call.method arguments:value]; + result(call.arguments); + }]; + } else if ([call.method isEqual:@"error"]) { + [channel invokeMethod:call.method arguments:call.arguments result:^(id value) { + FlutterError* error = (FlutterError*) value; + [channel invokeMethod:call.method arguments:error.details]; + result(error); + }]; + } else { + [channel invokeMethod:call.method arguments:call.arguments result:^(id value) { + NSAssert(value == FlutterMethodNotImplemented, @"Result must be not implemented"); + [channel invokeMethod:call.method arguments:nil]; + result(FlutterMethodNotImplemented); + }]; + } + }]; +} + +- (void)applicationWillResignActive:(UIApplication *)application { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. +} + +- (void)applicationDidEnterBackground:(UIApplication *)application { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. +} + +- (void)applicationWillEnterForeground:(UIApplication *)application { + // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. +} + +- (void)applicationDidBecomeActive:(UIApplication *)application { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. +} + +- (void)applicationWillTerminate:(UIApplication *)application { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. +} + +@end diff --git a/dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..d22f10b2ab --- /dev/null +++ b/dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..28c6bf03016f6c994b70f38d1b7346e5831b531f GIT binary patch literal 564 zcmV-40?Yl0P)Px$?ny*JR5%f>l)FnDQ543{x%ZCiu33$Wg!pQFfT_}?5Q|_VSlIbLC`dpoMXL}9 zHfd9&47Mo(7D231gb+kjFxZHS4-m~7WurTH&doVX2KI5sU4v(sJ1@T9eCIKPjsqSr z)C01LsCxk=72-vXmX}CQD#BD;Cthymh&~=f$Q8nn0J<}ZrusBy4PvRNE}+1ceuj8u z0mW5k8fmgeLnTbWHGwfKA3@PdZxhn|PypR&^p?weGftrtCbjF#+zk_5BJh7;0`#Wr zgDpM_;Ax{jO##IrT`Oz;MvfwGfV$zD#c2xckpcXC6oou4ML~ezCc2EtnsQTB4tWNg z?4bkf;hG7IMfhgNI(FV5Gs4|*GyMTIY0$B=_*mso9Ityq$m^S>15>-?0(zQ<8Qy<_TjHE33(?_M8oaM zyc;NxzRVK@DL6RJnX%U^xW0Gpg(lXp(!uK1v0YgHjs^ZXSQ|m#lV7ip7{`C_J2TxPmfw%h$|%acrYHt)Re^PB%O&&=~a zhS(%I#+V>J-vjIib^<+s%ludY7y^C(P8nmqn9fp!i+?vr`bziDE=bx`%2W#Xyrj|i z!XQ4v1%L`m{7KT7q+LZNB^h8Ha2e=`Wp65^0;J00)_^G=au=8Yo;1b`CV&@#=jIBo zjN^JNVfYSs)+kDdGe7`1&8!?MQYKS?DuHZf3iogk_%#9E|5S zWeHrmAo>P;ejX7mwq#*}W25m^ZI+{(Z8fI?4jM_fffY0nok=+88^|*_DwcW>mR#e+ zX$F_KMdb6sRz!~7KkyN0G(3XQ+;z3X%PZ4gh;n-%62U<*VUKNv(D&Q->Na@Xb&u5Q3`3DGf+a8O5x7c#7+R+EAYl@R5us)CIw z7sT@_y~Ao@uL#&^LIh&QceqiT^+lb0YbFZt_SHOtWA%mgPEKVNvVgCsXy{5+zl*X8 zCJe)Q@y>wH^>l4;h1l^Y*9%-23TSmE>q5nI@?mt%n;Sj4Qq`Z+ib)a*a^cJc%E9^J zB;4s+K@rARbcBLT5P=@r;IVnBMKvT*)ew*R;&8vu%?Z&S>s?8?)3*YawM0P4!q$Kv zMmKh3lgE~&w&v%wVzH3Oe=jeNT=n@Y6J6TdHWTjXfX~-=1A1Bw`EW8rn}MqeI34nh zexFeA?&C3B2(E?0{drE@DA2pu(A#ElY&6el60Rn|Qpn-FkfQ8M93AfWIr)drgDFEU zghdWK)^71EWCP(@(=c4kfH1Y(4iugD4fve6;nSUpLT%!)MUHs1!zJYy4y||C+SwQ! z)KM&$7_tyM`sljP2fz6&Z;jxRn{Wup8IOUx8D4uh&(=O zx-7$a;U><*5L^!%xRlw)vAbh;sdlR||& ze}8_8%)c2Fwy=F&H|LM+p{pZB5DKTx>Y?F1N%BlZkXf!}JeGuMZk~LPi7{cidvUGB zAJ4LVeNV%XO>LTrklB#^-;8nb;}6l;1oW&WS=Mz*Az!4cqqQzbOSFq`$Q%PfD7srM zpKgP-D_0XPTRX*hAqeq0TDkJ;5HB1%$3Np)99#16c{ zJImlNL(npL!W|Gr_kxl1GVmF5&^$^YherS7+~q$p zt}{a=*RiD2Ikv6o=IM1kgc7zqpaZ;OB)P!1zz*i3{U()Dq#jG)egvK}@uFLa`oyWZ zf~=MV)|yJn`M^$N%ul5);JuQvaU1r2wt(}J_Qgyy`qWQI`hEeRX0uC@c1(dQ2}=U$ tNIIaX+dr)NRWXcxoR{>fqI{SF_dm1Ylv~=3YHI)h002ovPDHLkV1g(pWS;;4 literal 0 HcmV?d00001 diff --git a/dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..f091b6b0bca859a3f474b03065bef75ba58a9e4c GIT binary patch literal 1588 zcmV-42Fv-0P)C1SqPt}wig>|5Crh^=oyX$BK<}M8eLU3e2hGT;=G|!_SP)7zNI6fqUMB=)y zRAZ>eDe#*r`yDAVgB_R*LB*MAc)8(b{g{9McCXW!lq7r(btRoB9!8B-#AI6JMb~YFBEvdsV)`mEQO^&#eRKx@b&x- z5lZm*!WfD8oCLzfHGz#u7sT0^VLMI1MqGxF^v+`4YYnVYgk*=kU?HsSz{v({E3lb9 z>+xILjBN)t6`=g~IBOelGQ(O990@BfXf(DRI5I$qN$0Gkz-FSc$3a+2fX$AedL4u{ z4V+5Ong(9LiGcIKW?_352sR;LtDPmPJXI{YtT=O8=76o9;*n%_m|xo!i>7$IrZ-{l z-x3`7M}qzHsPV@$v#>H-TpjDh2UE$9g6sysUREDy_R(a)>=eHw-WAyfIN z*qb!_hW>G)Tu8nSw9yn#3wFMiLcfc4pY0ek1}8(NqkBR@t4{~oC>ryc-h_ByH(Cg5 z>ao-}771+xE3um9lWAY1FeQFxowa1(!J(;Jg*wrg!=6FdRX+t_<%z&d&?|Bn){>zm zZQj(aA_HeBY&OC^jj*)N`8fa^ePOU72VpInJoI1?`ty#lvlNzs(&MZX+R%2xS~5Kh zX*|AU4QE#~SgPzOXe9>tRj>hjU@c1k5Y_mW*Jp3fI;)1&g3j|zDgC+}2Q_v%YfDax z!?umcN^n}KYQ|a$Lr+51Nf9dkkYFSjZZjkma$0KOj+;aQ&721~t7QUKx61J3(P4P1 zstI~7-wOACnWP4=8oGOwz%vNDqD8w&Q`qcNGGrbbf&0s9L0De{4{mRS?o0MU+nR_! zrvshUau0G^DeMhM_v{5BuLjb#Hh@r23lDAk8oF(C+P0rsBpv85EP>4CVMx#04MOfG z;P%vktHcXwTj~+IE(~px)3*MY77e}p#|c>TD?sMatC0Tu4iKKJ0(X8jxQY*gYtxsC z(zYC$g|@+I+kY;dg_dE>scBf&bP1Nc@Hz<3R)V`=AGkc;8CXqdi=B4l2k|g;2%#m& z*jfX^%b!A8#bI!j9-0Fi0bOXl(-c^AB9|nQaE`*)Hw+o&jS9@7&Gov#HbD~#d{twV zXd^Tr^mWLfFh$@Dr$e;PBEz4(-2q1FF0}c;~B5sA}+Q>TOoP+t>wf)V9Iy=5ruQa;z)y zI9C9*oUga6=hxw6QasLPnee@3^Rr*M{CdaL5=R41nLs(AHk_=Y+A9$2&H(B7!_pURs&8aNw7?`&Z&xY_Ye z)~D5Bog^td-^QbUtkTirdyK^mTHAOuptDflut!#^lnKqU md>ggs(5nOWAqO?umG&QVYK#ibz}*4>0000U6E9hRK9^#O7(mu>ETqrXGsduA8$)?`v2seloOCza43C{NQ$$gAOH**MCn0Q?+L7dl7qnbRdqZ8LSVp1ItDxhxD?t@5_yHg6A8yI zC*%Wgg22K|8E#!~cTNYR~@Y9KepMPrrB8cABapAFa=`H+UGhkXUZV1GnwR1*lPyZ;*K(i~2gp|@bzp8}og7e*#% zEnr|^CWdVV!-4*Y_7rFvlww2Ze+>j*!Z!pQ?2l->4q#nqRu9`ELo6RMS5=br47g_X zRw}P9a7RRYQ%2Vsd0Me{_(EggTnuN6j=-?uFS6j^u69elMypu?t>op*wBx<=Wx8?( ztpe^(fwM6jJX7M-l*k3kEpWOl_Vk3@(_w4oc}4YF4|Rt=2V^XU?#Yz`8(e?aZ@#li0n*=g^qOcVpd-Wbok=@b#Yw zqn8u9a)z>l(1kEaPYZ6hwubN6i<8QHgsu0oE) ziJ(p;Wxm>sf!K+cw>R-(^Y2_bahB+&KI9y^);#0qt}t-$C|Bo71lHi{_+lg#f%RFy z0um=e3$K3i6K{U_4K!EX?F&rExl^W|G8Z8;`5z-k}OGNZ0#WVb$WCpQu-_YsiqKP?BB# vzVHS-CTUF4Ozn5G+mq_~Qqto~ahA+K`|lyv3(-e}00000NkvXXu0mjfd`9t{ literal 0 HcmV?d00001 diff --git a/dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d0ef06e7edb86cdfe0d15b4b0d98334a86163658 GIT binary patch literal 1716 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 literal 0 HcmV?d00001 diff --git a/dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c8f9ed8f5cee1c98386d13b17e89f719e83555b2 GIT binary patch literal 1895 zcmV-t2blPYP)FQtfgmafE#=YDCq`qUBt#QpG%*H6QHY765~R=q zZ6iudfM}q!Pz#~9JgOi8QJ|DSu?1-*(kSi1K4#~5?#|rh?sS)(-JQqX*}ciXJ56_H zdw=^s_srbAdqxlvGyrgGet#6T7_|j;95sL%MtM;q86vOxKM$f#puR)Bjv9Zvz9-di zXOTSsZkM83)E9PYBXC<$6(|>lNLVBb&&6y{NByFCp%6+^ALR@NCTse_wqvNmSWI-m z!$%KlHFH2omF!>#%1l3LTZg(s7eof$7*xB)ZQ0h?ejh?Ta9fDv59+u#MokW+1t8Zb zgHv%K(u9G^Lv`lh#f3<6!JVTL3(dCpxHbnbA;kKqQyd1~^Xe0VIaYBSWm6nsr;dFj z4;G-RyL?cYgsN1{L4ZFFNa;8)Rv0fM0C(~Tkit94 zz#~A)59?QjD&pAPSEQ)p8gP|DS{ng)j=2ux)_EzzJ773GmQ_Cic%3JJhC0t2cx>|v zJcVusIB!%F90{+}8hG3QU4KNeKmK%T>mN57NnCZ^56=0?&3@!j>a>B43pi{!u z7JyDj7`6d)qVp^R=%j>UIY6f+3`+qzIc!Y_=+uN^3BYV|o+$vGo-j-Wm<10%A=(Yk^beI{t%ld@yhKjq0iNjqN4XMGgQtbKubPM$JWBz}YA65k%dm*awtC^+f;a-x4+ddbH^7iDWGg&N0n#MW{kA|=8iMUiFYvMoDY@sPC#t$55gn6ykUTPAr`a@!(;np824>2xJthS z*ZdmT`g5-`BuJs`0LVhz+D9NNa3<=6m;cQLaF?tCv8)zcRSh66*Z|vXhG@$I%U~2l z?`Q zykI#*+rQ=z6Jm=Bui-SfpDYLA=|vzGE(dYm=OC8XM&MDo7ux4UF1~0J1+i%aCUpRe zt3L_uNyQ*cE(38Uy03H%I*)*Bh=Lb^Xj3?I^Hnbeq72(EOK^Y93CNp*uAA{5Lc=ky zx=~RKa4{iTm{_>_vSCm?$Ej=i6@=m%@VvAITnigVg{&@!7CDgs908761meDK5azA} z4?=NOH|PdvabgJ&fW2{Mo$Q0CcD8Qc84%{JPYt5EiG{MdLIAeX%T=D7NIP4%Hw}p9 zg)==!2Lbp#j{u_}hMiao9=!VSyx0gHbeCS`;q&vzeq|fs`y&^X-lso(Ls@-706qmA z7u*T5PMo_w3{se1t2`zWeO^hOvTsohG_;>J0wVqVe+n)AbQCx)yh9;w+J6?NF5Lmo zecS@ieAKL8%bVd@+-KT{yI|S}O>pYckUFs;ry9Ow$CD@ztz5K-*D$^{i(_1llhSh^ zEkL$}tsQt5>QA^;QgjgIfBDmcOgi5YDyu?t6vSnbp=1+@6D& z5MJ}B8q;bRlVoxasyhcUF1+)o`&3r0colr}QJ3hcSdLu;9;td>kf@Tcn<@9sIx&=m z;AD;SCh95=&p;$r{Xz3iWCO^MX83AGJ(yH&eTXgv|0=34#-&WAmw{)U7OU9!Wz^!7 zZ%jZFi@JR;>Mhi7S>V7wQ176|FdW2m?&`qa(ScO^CFPR80HucLHOTy%5s*HR0^8)i h0WYBP*#0Ks^FNSabJA*5${_#%002ovPDHLkV1oKhTl@e3 literal 0 HcmV?d00001 diff --git a/dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..a6d6b8609df07bf62e5100a53a01510388bd2b22 GIT binary patch literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ literal 0 HcmV?d00001 diff --git a/dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a6d6b8609df07bf62e5100a53a01510388bd2b22 GIT binary patch literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ literal 0 HcmV?d00001 diff --git a/dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..75b2d164a5a98e212cca15ea7bf2ab5de5108680 GIT binary patch literal 3831 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 literal 0 HcmV?d00001 diff --git a/dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/dev/integration_tests/channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..c4df70d39da7941ef3f6dcb7f06a192d8dcb308d GIT binary patch literal 1888 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 iW8 + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/integration_tests/channels/ios/Runner/Base.lproj/Main.storyboard b/dev/integration_tests/channels/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000000..f3c28516fb --- /dev/null +++ b/dev/integration_tests/channels/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/integration_tests/channels/ios/Runner/Info.plist b/dev/integration_tests/channels/ios/Runner/Info.plist new file mode 100644 index 0000000000..04cb09d42c --- /dev/null +++ b/dev/integration_tests/channels/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + channels + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/dev/integration_tests/channels/ios/Runner/PluginRegistry.h b/dev/integration_tests/channels/ios/Runner/PluginRegistry.h new file mode 100644 index 0000000000..df039db515 --- /dev/null +++ b/dev/integration_tests/channels/ios/Runner/PluginRegistry.h @@ -0,0 +1,18 @@ +// +// Generated file. Do not edit. +// + +#ifndef PluginRegistry_h +#define PluginRegistry_h + +#import + + +@interface PluginRegistry : NSObject + + +- (instancetype)initWithController:(FlutterViewController *)controller; + +@end + +#endif /* PluginRegistry_h */ diff --git a/dev/integration_tests/channels/ios/Runner/PluginRegistry.m b/dev/integration_tests/channels/ios/Runner/PluginRegistry.m new file mode 100644 index 0000000000..0a34729946 --- /dev/null +++ b/dev/integration_tests/channels/ios/Runner/PluginRegistry.m @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +#import "PluginRegistry.h" + +@implementation PluginRegistry + +- (instancetype)initWithController:(FlutterViewController *)controller { + if (self = [super init]) { + } + return self; +} + +@end diff --git a/dev/integration_tests/channels/ios/Runner/main.m b/dev/integration_tests/channels/ios/Runner/main.m new file mode 100644 index 0000000000..945818bd51 --- /dev/null +++ b/dev/integration_tests/channels/ios/Runner/main.m @@ -0,0 +1,14 @@ +// Copyright 2017 The Chromium 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 +#import +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, + NSStringFromClass([AppDelegate class])); + } +} diff --git a/dev/integration_tests/channels/lib/main.dart b/dev/integration_tests/channels/lib/main.dart new file mode 100644 index 0000000000..025f3e1e7f --- /dev/null +++ b/dev/integration_tests/channels/lib/main.dart @@ -0,0 +1,193 @@ +// Copyright 2017 The Chromium 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:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:flutter_driver/driver_extension.dart'; + +import 'src/basic_messaging.dart'; +import 'src/method_calls.dart'; +import 'src/test_step.dart'; + +void main() { + enableFlutterDriverExtension(); + runApp(new TestApp()); +} + +class TestApp extends StatefulWidget { + @override + _TestAppState createState() => new _TestAppState(); +} + +class _TestAppState extends State { + static final List aList = [ + false, + 0, + 0.0, + 'hello', + [ + {'key': 42} + ], + ]; + static final Map aMap = { + 'a': false, + 'b': 0, + 'c': 0.0, + 'd': 'hello', + 'e': [ + {'key': 42} + ] + }; + static final Uint8List someUint8s = new Uint8List.fromList([ + 0xBA, + 0x5E, + 0xBA, + 0x11, + ]); + static final Int32List someInt32s = new Int32List.fromList([ + -0x7fffffff - 1, + 0, + 0x7fffffff, + ]); + static final Int64List someInt64s = new Int64List.fromList([ + -0x7fffffffffffffff - 1, + 0, + 0x7fffffffffffffff, + ]); + static final Float64List someFloat64s = + new Float64List.fromList([ + double.NAN, + double.NEGATIVE_INFINITY, + -double.MAX_FINITE, + -double.MIN_POSITIVE, + -0.0, + 0.0, + double.MIN_POSITIVE, + double.MAX_FINITE, + double.INFINITY, + ]); + static final List steps = [ + () => methodCallJsonSuccessHandshake(null), + () => methodCallJsonSuccessHandshake(true), + () => methodCallJsonSuccessHandshake(7), + () => methodCallJsonSuccessHandshake('world'), + () => methodCallJsonSuccessHandshake(aList), + () => methodCallJsonSuccessHandshake(aMap), + () => methodCallJsonNotImplementedHandshake(), + () => methodCallStandardSuccessHandshake(null), + () => methodCallStandardSuccessHandshake(true), + () => methodCallStandardSuccessHandshake(7), + () => methodCallStandardSuccessHandshake('world'), + () => methodCallStandardSuccessHandshake(aList), + () => methodCallStandardSuccessHandshake(aMap), + () => methodCallJsonErrorHandshake(null), + () => methodCallJsonErrorHandshake('world'), + () => methodCallStandardErrorHandshake(null), + () => methodCallStandardErrorHandshake('world'), + () => methodCallStandardNotImplementedHandshake(), + () => basicBinaryHandshake(null), + () => basicBinaryHandshake(new ByteData(0)), + () => basicBinaryHandshake(new ByteData(4)..setUint32(0, 0x12345678)), + () => basicStringHandshake('hello, world'), + () => basicStringHandshake('hello \u263A \u{1f602} unicode'), + () => basicStringHandshake(''), + () => basicStringHandshake(null), + () => basicJsonHandshake(null), + () => basicJsonHandshake(true), + () => basicJsonHandshake(false), + () => basicJsonHandshake(0), + () => basicJsonHandshake(-7), + () => basicJsonHandshake(7), + () => basicJsonHandshake(1 << 32), + () => basicJsonHandshake(1 << 56), + () => basicJsonHandshake(0.0), + () => basicJsonHandshake(-7.0), + () => basicJsonHandshake(7.0), + () => basicJsonHandshake(''), + () => basicJsonHandshake('hello, world'), + () => basicJsonHandshake('hello, "world"'), + () => basicJsonHandshake('hello \u263A \u{1f602} unicode'), + () => basicJsonHandshake([]), + () => basicJsonHandshake(aList), + () => basicJsonHandshake({}), + () => basicJsonHandshake(aMap), + () => basicStandardHandshake(null), + () => basicStandardHandshake(true), + () => basicStandardHandshake(false), + () => basicStandardHandshake(0), + () => basicStandardHandshake(-7), + () => basicStandardHandshake(7), + () => basicStandardHandshake(1 << 32), + () => basicStandardHandshake(1 << 64), + () => basicStandardHandshake(1 << 128), + () => basicStandardHandshake(0.0), + () => basicStandardHandshake(-7.0), + () => basicStandardHandshake(7.0), + () => basicStandardHandshake(''), + () => basicStandardHandshake('hello, world'), + () => basicStandardHandshake('hello \u263A \u{1f602} unicode'), + () => basicStandardHandshake(someUint8s), + () => basicStandardHandshake(someInt32s), + () => basicStandardHandshake(someInt64s), + () => basicStandardHandshake(someFloat64s), + () => basicStandardHandshake([]), + () => basicStandardHandshake(aList), + () => basicStandardHandshake({}), + () => basicStandardHandshake({7: true, false: -7}), + () => basicStandardHandshake(aMap), + () => basicBinaryMessageToUnknownChannel(), + () => basicStringMessageToUnknownChannel(), + () => basicJsonMessageToUnknownChannel(), + () => basicStandardMessageToUnknownChannel(), + ]; + Future _result; + int _step = 0; + + @override + void initState() { + super.initState(); + } + + void _executeNextStep() { + setState(() { + if (_step < steps.length) + _result = steps[_step++](); + else + _result = new Future.value(TestStepResult.complete); + }); + } + + Widget _buildTestResultWidget( + BuildContext context, + AsyncSnapshot snapshot, + ) { + return new TestStepResult.fromSnapshot(snapshot).asWidget(context); + } + + @override + Widget build(BuildContext context) { + return new MaterialApp( + title: 'Channels Test', + home: new Scaffold( + appBar: new AppBar( + title: const Text('Channels Test'), + ), + body: new Padding( + padding: const EdgeInsets.all(20.0), + child: new FutureBuilder( + future: _result, + builder: _buildTestResultWidget, + ), + ), + floatingActionButton: new FloatingActionButton( + key: const ValueKey('step'), + onPressed: _executeNextStep, + child: new Icon(Icons.navigate_next), + ), + ), + ); + } +} diff --git a/dev/integration_tests/channels/lib/src/basic_messaging.dart b/dev/integration_tests/channels/lib/src/basic_messaging.dart new file mode 100644 index 0000000000..f2105a8364 --- /dev/null +++ b/dev/integration_tests/channels/lib/src/basic_messaging.dart @@ -0,0 +1,144 @@ +// Copyright 2017 The Chromium 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:typed_data'; +import 'package:flutter/services.dart'; +import 'test_step.dart'; + +Future basicBinaryHandshake(ByteData message) async { + const BasicMessageChannel channel = + const BasicMessageChannel( + 'binary-msg', + const BinaryCodec(), + ); + return _basicMessageHandshake( + 'Binary >${toString(message)}<', channel, message); +} + +Future basicStringHandshake(String message) async { + const BasicMessageChannel channel = const BasicMessageChannel( + 'string-msg', + const StringCodec(), + ); + return _basicMessageHandshake('String >$message<', channel, message); +} + +Future basicJsonHandshake(dynamic message) async { + const BasicMessageChannel channel = + const BasicMessageChannel( + 'json-msg', + const JSONMessageCodec(), + ); + return _basicMessageHandshake('JSON >$message<', channel, message); +} + +Future basicStandardHandshake(dynamic message) async { + const BasicMessageChannel channel = + const BasicMessageChannel( + 'std-msg', + const StandardMessageCodec(), + ); + return _basicMessageHandshake( + 'Standard >${toString(message)}<', channel, message); +} + +Future basicBinaryMessageToUnknownChannel() async { + const BasicMessageChannel channel = + const BasicMessageChannel( + 'binary-unknown', + const BinaryCodec(), + ); + return _basicMessageToUnknownChannel('Binary', channel); +} + +Future basicStringMessageToUnknownChannel() async { + const BasicMessageChannel channel = const BasicMessageChannel( + 'string-unknown', + const StringCodec(), + ); + return _basicMessageToUnknownChannel('String', channel); +} + +Future basicJsonMessageToUnknownChannel() async { + const BasicMessageChannel channel = + const BasicMessageChannel( + 'json-unknown', + const JSONMessageCodec(), + ); + return _basicMessageToUnknownChannel('JSON', channel); +} + +Future basicStandardMessageToUnknownChannel() async { + const BasicMessageChannel channel = + const BasicMessageChannel( + 'std-unknown', + const StandardMessageCodec(), + ); + return _basicMessageToUnknownChannel('Standard', channel); +} + +/// Sends the specified message to the platform, doing a +/// receive message/send reply/receive reply echo handshake initiated by the +/// platform, then expecting a reply echo to the original message. +/// +/// Fails, if an error occurs, or if any message seen is not deeply equal to +/// the original message. +Future _basicMessageHandshake( + String description, + BasicMessageChannel channel, + T message, +) async { + final List received = []; + channel.setMessageHandler((T message) async { + received.add(message); + return message; + }); + dynamic messageEcho = nothing; + dynamic error = nothing; + try { + messageEcho = await channel.send(message); + } catch (e) { + error = e; + } + return resultOfHandshake( + 'Basic message handshake', + description, + message, + received, + messageEcho, + error, + ); +} + +/// Sends a message on a channel that no one listens on. +Future _basicMessageToUnknownChannel( + String description, + BasicMessageChannel channel, +) async { + dynamic messageEcho = nothing; + dynamic error = nothing; + try { + messageEcho = await channel.send(null); + } catch (e) { + error = e; + } + return resultOfHandshake( + 'Message on unknown channel', + description, + null, + [null, null], + messageEcho, + error, + ); +} + +String toString(dynamic message) { + if (message is ByteData) + return message.buffer + .asUint8List(message.offsetInBytes, message.lengthInBytes) + .toString(); + else + return '$message'; +} diff --git a/dev/integration_tests/channels/lib/src/method_calls.dart b/dev/integration_tests/channels/lib/src/method_calls.dart new file mode 100644 index 0000000000..7db46cd6c5 --- /dev/null +++ b/dev/integration_tests/channels/lib/src/method_calls.dart @@ -0,0 +1,133 @@ +// Copyright 2017 The Chromium 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/services.dart'; +import 'test_step.dart'; + +Future methodCallJsonSuccessHandshake(dynamic payload) async { + const MethodChannel channel = + const MethodChannel('json-method', const JSONMethodCodec()); + return _methodCallSuccessHandshake( + 'JSON success($payload)', channel, payload); +} + +Future methodCallJsonErrorHandshake(dynamic payload) async { + const MethodChannel channel = + const MethodChannel('json-method', const JSONMethodCodec()); + return _methodCallErrorHandshake('JSON error($payload)', channel, payload); +} + +Future methodCallJsonNotImplementedHandshake() async { + const MethodChannel channel = + const MethodChannel('json-method', const JSONMethodCodec()); + return _methodCallNotImplementedHandshake('JSON notImplemented()', channel); +} + +Future methodCallStandardSuccessHandshake( + dynamic payload) async { + const MethodChannel channel = + const MethodChannel('std-method', const StandardMethodCodec()); + return _methodCallSuccessHandshake( + 'Standard success($payload)', channel, payload); +} + +Future methodCallStandardErrorHandshake(dynamic payload) async { + const MethodChannel channel = + const MethodChannel('std-method', const StandardMethodCodec()); + return _methodCallErrorHandshake( + 'Standard error($payload)', channel, payload); +} + +Future methodCallStandardNotImplementedHandshake() async { + const MethodChannel channel = + const MethodChannel('std-method', const StandardMethodCodec()); + return _methodCallNotImplementedHandshake( + 'Standard notImplemented()', channel); +} + +Future _methodCallSuccessHandshake( + String description, + MethodChannel channel, + dynamic arguments, +) async { + final List received = []; + channel.setMethodCallHandler((MethodCall call) async { + received.add(call.arguments); + return call.arguments; + }); + dynamic result = nothing; + dynamic error = nothing; + try { + result = await channel.invokeMethod('success', arguments); + } catch (e) { + error = e; + } + return resultOfHandshake( + 'Method call success handshake', + description, + arguments, + received, + result, + error, + ); +} + +Future _methodCallErrorHandshake( + String description, + MethodChannel channel, + dynamic arguments, +) async { + final List received = []; + channel.setMethodCallHandler((MethodCall call) async { + received.add(call.arguments); + throw new PlatformException( + code: 'error', message: null, details: arguments); + }); + dynamic errorDetails = nothing; + dynamic error = nothing; + try { + error = await channel.invokeMethod('error', arguments); + } on PlatformException catch (e) { + errorDetails = e.details; + } catch (e) { + error = e; + } + return resultOfHandshake( + 'Method call error handshake', + description, + arguments, + received, + errorDetails, + error, + ); +} + +Future _methodCallNotImplementedHandshake( + String description, + MethodChannel channel, +) async { + final List received = []; + channel.setMethodCallHandler((MethodCall call) async { + received.add(call.arguments); + throw new MissingPluginException(); + }); + dynamic result = nothing; + dynamic error = nothing; + try { + error = await channel.invokeMethod('notImplemented'); + } on MissingPluginException { + result = null; + } catch (e) { + error = e; + } + return resultOfHandshake( + 'Method call not implemented handshake', + description, + null, + received, + result, + error, + ); +} diff --git a/dev/integration_tests/channels/lib/src/test_step.dart b/dev/integration_tests/channels/lib/src/test_step.dart new file mode 100644 index 0000000000..1f65880580 --- /dev/null +++ b/dev/integration_tests/channels/lib/src/test_step.dart @@ -0,0 +1,168 @@ +// Copyright 2017 The Chromium 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:typed_data'; + +import 'package:flutter/material.dart'; + +enum TestStatus { ok, pending, failed, complete } + +typedef Future TestStep(); + +const String nothing = '-'; + +/// Result of a test step checking a nested communication handshake +/// between the Flutter app and the platform: +/// +/// - The Flutter app sends a message to the platform. +/// - The platform, on receipt, echos the message back to Flutter in a separate message. +/// - The Flutter app records the incoming message echo and replies. +/// - The platform, on receipt of reply, echos the reply back to Flutter in a separate message. +/// - The Flutter app records the incoming reply echo. +/// - The platform finally replies to the original message with another echo. +class TestStepResult { + static const TextStyle bold = const TextStyle(fontWeight: FontWeight.bold); + static const TestStepResult complete = const TestStepResult( + 'Test complete', + nothing, + TestStatus.complete, + ); + + const TestStepResult( + this.name, + this.description, + this.status, { + this.messageSent = nothing, + this.messageEcho = nothing, + this.messageReceived = nothing, + this.replyEcho = nothing, + this.error = nothing, + }); + + factory TestStepResult.fromSnapshot(AsyncSnapshot snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.none: + return const TestStepResult('Not started', nothing, TestStatus.ok); + case ConnectionState.waiting: + return const TestStepResult('Executing', nothing, TestStatus.pending); + case ConnectionState.done: + if (snapshot.hasData) { + return snapshot.data; + } else { + final TestStepResult result = snapshot.error; + return result; + } + break; + default: + throw 'Unsupported state ${snapshot.connectionState}'; + } + } + + final String name; + final String description; + final TestStatus status; + final dynamic messageSent; + final dynamic messageEcho; + final dynamic messageReceived; + final dynamic replyEcho; + final dynamic error; + + Widget asWidget(BuildContext context) { + return new Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + new Text('Step: $name', style: bold), + new Text(description), + const Text(' '), + new Text('Msg sent: ${_toString(messageSent)}'), + new Text('Msg rvcd: ${_toString(messageReceived)}'), + new Text('Reply echo: ${_toString(replyEcho)}'), + new Text('Msg echo: ${_toString(messageEcho)}'), + new Text('Error: ${_toString(error)}'), + const Text(' '), + new Text( + status.toString().substring('TestStatus.'.length), + key: new ValueKey( + status == TestStatus.pending ? 'nostatus' : 'status'), + style: bold, + ), + ], + ); + } +} + +Future resultOfHandshake( + String name, + String description, + dynamic message, + List received, + dynamic messageEcho, + dynamic error, +) async { + assert(message != nothing); + while (received.length < 2) received.add(nothing); + TestStatus status; + if (!_deepEquals(messageEcho, message) || + received.length != 2 || + !_deepEquals(received[0], message) || + !_deepEquals(received[1], message)) { + status = TestStatus.failed; + } else if (error != nothing) { + status = TestStatus.failed; + } else { + status = TestStatus.ok; + } + return new TestStepResult( + name, + description, + status, + messageSent: message, + messageEcho: messageEcho, + messageReceived: received[0], + replyEcho: received[1], + error: error, + ); +} + +String _toString(dynamic message) { + if (message is ByteData) + return message.buffer + .asUint8List(message.offsetInBytes, message.lengthInBytes) + .toString(); + else + return '$message'; +} + +bool _deepEquals(dynamic a, dynamic b) { + if (a == b) return true; + if (a is double && a.isNaN) return b is double && b.isNaN; + if (a is ByteData) return b is ByteData && _deepEqualsByteData(a, b); + if (a is List) return b is List && _deepEqualsList(a, b); + if (a is Map) return b is Map && _deepEqualsMap(a, b); + return false; +} + +bool _deepEqualsByteData(ByteData a, ByteData b) { + return _deepEqualsList( + a.buffer.asUint8List(a.offsetInBytes, a.lengthInBytes), + b.buffer.asUint8List(b.offsetInBytes, b.lengthInBytes), + ); +} + +bool _deepEqualsList(List a, List b) { + if (a.length != b.length) return false; + for (int i = 0; i < a.length; i++) { + if (!_deepEquals(a[i], b[i])) return false; + } + return true; +} + +bool _deepEqualsMap(Map a, Map b) { + if (a.length != b.length) return false; + for (dynamic key in a.keys) { + if (!b.containsKey(key) || !_deepEquals(a[key], b[key])) return false; + } + return true; +} diff --git a/dev/integration_tests/channels/pubspec.yaml b/dev/integration_tests/channels/pubspec.yaml new file mode 100644 index 0000000000..3c26fdd9d8 --- /dev/null +++ b/dev/integration_tests/channels/pubspec.yaml @@ -0,0 +1,10 @@ +name: channels +description: Integration test for platform channels. + +dependencies: + flutter: + sdk: flutter + flutter_driver: + sdk: flutter +flutter: + uses-material-design: true diff --git a/dev/integration_tests/channels/test_driver/main_test.dart b/dev/integration_tests/channels/test_driver/main_test.dart new file mode 100644 index 0000000000..1048c69461 --- /dev/null +++ b/dev/integration_tests/channels/test_driver/main_test.dart @@ -0,0 +1,34 @@ +// Copyright 2017 The Chromium 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 'package:flutter_driver/flutter_driver.dart'; +import 'package:test/test.dart'; + +void main() { + group('channel suite', () { + FlutterDriver driver; + + setUpAll(() async { + driver = await FlutterDriver.connect(); + }); + + test('step through', () async { + final SerializableFinder stepButton = find.byValueKey('step'); + final SerializableFinder statusField = find.byValueKey('status'); + int step = 0; + while (await driver.getText(statusField) == 'ok') { + await driver.tap(stepButton); + step++; + } + final String status = await driver.getText(statusField); + if (status != 'complete') { + fail('Failed at step $step with status $status'); + } + }); + + tearDownAll(() async { + driver?.close(); + }); + }); +} diff --git a/packages/flutter/lib/src/services/message_codec.dart b/packages/flutter/lib/src/services/message_codec.dart index 5b1124a6fb..7cf3620b59 100644 --- a/packages/flutter/lib/src/services/message_codec.dart +++ b/packages/flutter/lib/src/services/message_codec.dart @@ -7,9 +7,12 @@ import 'dart:ui' show hashValues; import 'package:flutter/foundation.dart'; +import 'platform_channel.dart'; + /// A message encoding/decoding mechanism. /// -/// Both operations throw [FormatException], if conversion fails. +/// Both operations throw an exception, if conversion fails. Such situations +/// should be treated as programming errors. /// /// See also: /// @@ -92,15 +95,7 @@ class MethodCall { /// A codec for method calls and enveloped results. /// -/// Result envelopes are binary messages with enough structure that the codec can -/// distinguish between a successful result and an error. In the former case, -/// the codec must be able to extract the result payload, possibly `null`. In -/// the latter case, the codec must be able to extract an error code string, -/// a (human-readable) error message string, and a value providing any -/// additional error details, possibly `null`. These data items are used to -/// populate a [PlatformException]. -/// -/// All operations throw [FormatException], if conversion fails. +/// All operations throw an exception, if conversion fails. /// /// See also: /// @@ -109,7 +104,7 @@ class MethodCall { /// * [PlatformEventChannel], which use [MethodCodec]s for communication /// between Flutter and platform plugins. abstract class MethodCodec { - /// Encodes the specified [methodCall] in binary. + /// Encodes the specified [methodCall] into binary. ByteData encodeMethodCall(MethodCall methodCall); /// Decodes the specified [methodCall] from binary. @@ -139,10 +134,10 @@ abstract class MethodCodec { /// /// * [MethodCodec], which throws a [PlatformException], if a received result /// envelope represents an error. -/// * [PlatformMethodChannel.invokeMethod], which completes the returned future +/// * [MethodChannel.invokeMethod], which completes the returned future /// with a [PlatformException], if invoking the platform plugin method /// results in an error envelope. -/// * [PlatformEventChannel.receiveBroadcastStream], which emits +/// * [EventChannel.receiveBroadcastStream], which emits /// [PlatformException]s as error events, whenever an event received from the /// platform plugin is wrapped in an error envelope. class PlatformException implements Exception { @@ -175,9 +170,11 @@ class PlatformException implements Exception { /// /// See also: /// -/// * [PlatformMethodChannel.invokeMethod], which completes the returned future +/// * [MethodChannel.invokeMethod], which completes the returned future /// with a [MissingPluginException], if no plugin handler for the method call /// was found. +/// * [OptionalMethodChannel.invokeMethod], which completes the returned future +/// with `null`, if no plugin handler for the method call was found. class MissingPluginException implements Exception { /// Creates a [MissingPluginException] with an optional human-readable /// error message. diff --git a/packages/flutter/lib/src/services/platform_channel.dart b/packages/flutter/lib/src/services/platform_channel.dart index c064c28326..a766ff9af8 100644 --- a/packages/flutter/lib/src/services/platform_channel.dart +++ b/packages/flutter/lib/src/services/platform_channel.dart @@ -17,14 +17,15 @@ import 'platform_messages.dart'; /// Messages are encoded into binary before being sent, and binary messages /// received are decoded into Dart values. The [MessageCodec] used must be /// compatible with the one used by the platform plugin. This can be achieved -/// by creating a FlutterMessageChannel counterpart of this channel on the +/// by creating a `BasicMessageChannel` counterpart of this channel on the /// platform side. The Dart type of messages sent and received is [T], /// but only the values supported by the specified [MessageCodec] can be used. +/// The use of unsupported values should be considered programming errors, and +/// will result in exceptions being thrown. The `null` message is supported +/// for all codecs. /// -/// The identity of the channel is given by its name, so other uses of that name -/// with may interfere with this channel's communication. Specifically, at most -/// one message handler can be registered with the channel name at any given -/// time. +/// The logical identity of the channel is given by its name. Identically named +/// channels will interfere with each other's communication. /// /// See: class BasicMessageChannel { @@ -41,23 +42,21 @@ class BasicMessageChannel { /// Sends the specified [message] to the platform plugins on this channel. /// - /// Returns a [Future] which completes to the received and decoded response, - /// or to a [FormatException], if encoding or decoding fails. + /// Returns a [Future] which completes to the received response, which may + /// be `null`. Future send(T message) async { - return codec.decodeMessage( - await BinaryMessages.send(name, codec.encodeMessage(message)) - ); + return codec.decodeMessage(await BinaryMessages.send(name, codec.encodeMessage(message))); } /// Sets a callback for receiving messages from the platform plugins on this - /// channel. + /// channel. Messages may be `null`. /// /// The given callback will replace the currently registered callback for this /// channel, if any. To remove the handler, pass `null` as the `handler` /// argument. /// - /// The handler's return value, if non-null, is sent back to the platform - /// plugins as a response. + /// The handler's return value is sent back to the platform plugins as a + /// message reply. It may be `null`. void setMessageHandler(Future handler(T message)) { if (handler == null) { BinaryMessages.setMessageHandler(name, null); @@ -69,12 +68,13 @@ class BasicMessageChannel { } /// Sets a mock callback for intercepting messages sent on this channel. + /// Messages may be `null`. /// /// The given callback will replace the currently registered mock callback for /// this channel, if any. To remove the mock handler, pass `null` as the /// `handler` argument. /// - /// The handler's return value, if non-null, is used as a response. + /// The handler's return value is used as a message reply. It may be `null`. /// /// This is intended for testing. Messages intercepted in this manner are not /// sent to platform plugins. @@ -95,12 +95,15 @@ class BasicMessageChannel { /// Method calls are encoded into binary before being sent, and binary results /// received are decoded into Dart values. The [MethodCodec] used must be /// compatible with the one used by the platform plugin. This can be achieved -/// by creating a FlutterMethodChannel counterpart of this channel on the -/// platform side. The Dart type of messages sent and received is `dynamic`, +/// by creating a `MethodChannel` counterpart of this channel on the +/// platform side. The Dart type of arguments and results is `dynamic`, /// but only values supported by the specified [MethodCodec] can be used. +/// The use of unsupported values should be considered programming errors, and +/// will result in exceptions being thrown. The `null` value is supported +/// for all codecs. /// -/// The identity of the channel is given by its name, so other uses of that name -/// with may interfere with this channel's communication. +/// The logical identity of the channel is given by its name. Identically named +/// channels will interfere with each other's communication. /// /// See: class MethodChannel { @@ -124,8 +127,8 @@ class MethodChannel { /// /// * a result (possibly `null`), on successful invocation; /// * a [PlatformException], if the invocation failed in the platform plugin; - /// * a [FormatException], if encoding or decoding failed. - /// * a [MissingPluginException], if the method has not been implemented. + /// * a [MissingPluginException], if the method has not been implemented by a + /// platform plugin. Future invokeMethod(String method, [dynamic arguments]) async { assert(method != null); final dynamic result = await BinaryMessages.send( @@ -150,26 +153,12 @@ class MethodChannel { /// populate an error envelope which is sent back instead. If the future /// completes with a [MissingPluginException], an empty reply is sent /// similarly to what happens if no method call handler has been set. + /// Any other exception results in an error envelope being sent. void setMethodCallHandler(Future handler(MethodCall call)) { - if (handler == null) { - BinaryMessages.setMessageHandler(name, null); - } else { - BinaryMessages.setMessageHandler( - name, - (ByteData message) async { - final MethodCall call = codec.decodeMethodCall(message); - try { - final dynamic result = await handler(call); - return codec.encodeSuccessEnvelope(result); - } on PlatformException catch (e) { - return codec.encodeErrorEnvelope( - code: e.code, message: e.message, details: e.details); - } on MissingPluginException { - return null; - } - }, - ); - } + BinaryMessages.setMessageHandler( + name, + handler == null ? null : (ByteData message) => _handleAsMethodCall(message, handler), + ); } /// Sets a mock callback for intercepting method invocations on this channel. @@ -186,24 +175,26 @@ class MethodChannel { /// This is intended for testing. Method calls intercepted in this manner are /// not sent to platform plugins. void setMockMethodCallHandler(Future handler(MethodCall call)) { - if (handler == null) { - BinaryMessages.setMockMessageHandler(name, null); - } else { - BinaryMessages.setMockMessageHandler( - name, - (ByteData message) async { - final MethodCall call = codec.decodeMethodCall(message); - try { - final dynamic result = await handler(call); - return codec.encodeSuccessEnvelope(result); - } on PlatformException catch (e) { - return codec.encodeErrorEnvelope( - code: e.code, message: e.message, details: e.details); - } on MissingPluginException { - return null; - } - }, + BinaryMessages.setMockMessageHandler( + name, + handler == null ? null : (ByteData message) => _handleAsMethodCall(message, handler), + ); + } + + Future _handleAsMethodCall(ByteData message, Future handler(MethodCall call)) async { + final MethodCall call = codec.decodeMethodCall(message); + try { + return codec.encodeSuccessEnvelope(await handler(call)); + } on PlatformException catch (e) { + return codec.encodeErrorEnvelope( + code: e.code, + message: e.message, + details: e.details, ); + } on MissingPluginException { + return null; + } catch (e) { + return codec.encodeErrorEnvelope(code: 'error', message: e.toString(), details: null); } } } @@ -230,18 +221,19 @@ class OptionalMethodChannel extends MethodChannel { /// A named channel for communicating with platform plugins using event streams. /// /// Stream setup requests are encoded into binary before being sent, -/// and binary events received are decoded into Dart values. The [MethodCodec] -/// used must be compatible with the one used by the platform plugin. This can -/// be achieved by creating a FlutterEventChannel counterpart of this channel on -/// the platform side. The Dart type of events sent and received is `dynamic`, -/// but only values supported by the specified [MethodCodec] can be used. +/// and binary events and errors received are decoded into Dart values. +/// The [MethodCodec] used must be compatible with the one used by the platform +/// plugin. This can be achieved by creating an `EventChannel` counterpart of +/// this channel on the platform side. The Dart type of events sent and received +/// is `dynamic`, but only values supported by the specified [MethodCodec] can +/// be used. /// -/// The identity of the channel is given by its name, so other uses of that name -/// with may interfere with this channel's communication. +/// The logical identity of the channel is given by its name. Identically named +/// channels will interfere with each other's communication. /// /// See: class EventChannel { - /// Creates a [EventChannel] with the specified [name]. + /// Creates an [EventChannel] with the specified [name]. /// /// The [codec] used will be [StandardMethodCodec], unless otherwise /// specified. @@ -263,61 +255,44 @@ class EventChannel { /// received from the platform plugin; /// * an error event containing a [PlatformException] for each error event /// received from the platform plugin; - /// * an error event containing a [FormatException] for each event received - /// where decoding fails; - /// * an error event containing a [PlatformException], - /// [MissingPluginException], or [FormatException] whenever stream setup fails - /// (stream setup is done only when listener count changes from 0 to 1). /// - /// Notes for platform plugin implementers: - /// - /// Plugins must expose methods named `listen` and `cancel` suitable for - /// invocations by [MethodChannel.invokeMethod]. Both methods are - /// invoked with the specified [arguments]. - /// - /// Following the semantics of broadcast streams, `listen` will be called as - /// the first listener registers with the returned stream, and `cancel` when - /// the last listener cancels its registration. This pattern may repeat - /// indefinitely. Platform plugins should consume no stream-related resources - /// while listener count is zero. + /// Errors occurring during stream activation or deactivation are reported + /// through the [FlutterError] facility. Stream activation happens only when + /// stream listener count changes from 0 to 1. Stream deactivation happens + /// only when stream listener count changes from 1 to 0. Stream receiveBroadcastStream([dynamic arguments]) { final MethodChannel methodChannel = new MethodChannel(name, codec); StreamController controller; - controller = new StreamController.broadcast( - onListen: () async { - BinaryMessages.setMessageHandler( - name, (ByteData reply) async { - if (reply == null) { - controller.close(); - } else { - try { - controller.add(codec.decodeEnvelope(reply)); - } catch (e) { - controller.addError(e); - } - } - } - ); - try { - await methodChannel.invokeMethod('listen', arguments); - } catch (e) { - BinaryMessages.setMessageHandler(name, null); - controller.addError(e); - } - }, onCancel: () async { - BinaryMessages.setMessageHandler(name, null); - try { - await methodChannel.invokeMethod('cancel', arguments); - } catch (exception, stack) { - FlutterError.reportError(new FlutterErrorDetails( - exception: exception, - stack: stack, - library: 'services library', - context: 'while de-activating platform stream on channel $name', - )); - } + controller = new StreamController.broadcast(onListen: () async { + BinaryMessages.setMessageHandler(name, (ByteData reply) async { + if (reply == null) + controller.close(); + else + controller.add(codec.decodeEnvelope(reply)); + }); + try { + await methodChannel.invokeMethod('listen', arguments); + } catch (exception, stack) { + FlutterError.reportError(new FlutterErrorDetails( + exception: exception, + stack: stack, + library: 'services library', + context: 'while activating platform stream on channel $name', + )); } - ); + }, onCancel: () async { + BinaryMessages.setMessageHandler(name, null); + try { + await methodChannel.invokeMethod('cancel', arguments); + } catch (exception, stack) { + FlutterError.reportError(new FlutterErrorDetails( + exception: exception, + stack: stack, + library: 'services library', + context: 'while de-activating platform stream on channel $name', + )); + } + }); return controller.stream; } } diff --git a/packages/flutter/lib/src/services/platform_messages.dart b/packages/flutter/lib/src/services/platform_messages.dart index 972c3149bf..8cb40ba483 100644 --- a/packages/flutter/lib/src/services/platform_messages.dart +++ b/packages/flutter/lib/src/services/platform_messages.dart @@ -8,17 +8,20 @@ import 'dart:ui' as ui; import 'package:flutter/foundation.dart'; +import 'platform_channel.dart'; + typedef Future _MessageHandler(ByteData message); /// Sends binary messages to and receives binary messages from platform plugins. /// /// See also: /// -/// * [BasicMessageChannel], which provides messaging services similar to -/// [BinaryMessages], but with pluggable message codecs in support of sending -/// strings or semi-structured messages. -/// * [MethodChannel], which provides higher-level platform -/// communication such as method invocations and event streams. +/// * [BasicMessageChannel], which provides basic messaging services similar to +/// `BinaryMessages`, but with pluggable message codecs in support of sending +/// strings or semi-structured messages. +/// * [MethodChannel], which provides platform communication using asynchronous +/// method calls. +/// * [EventChannel], which provides platform communication using event streams. /// /// See: class BinaryMessages { @@ -54,7 +57,7 @@ class BinaryMessages { /// Typically called by [ServicesBinding] to handle platform messages received /// from [ui.window.onPlatformMessage]. /// - /// To register a handler for a given message channel, see [PlatformChannel]. + /// To register a handler for a given message channel, see [setMessageHandler]. static Future handlePlatformMessage( String channel, ByteData data, ui.PlatformMessageResponseCallback callback) async { ByteData response; diff --git a/packages/flutter/test/services/platform_channel_test.dart b/packages/flutter/test/services/platform_channel_test.dart index 6478146c03..d141c4f797 100644 --- a/packages/flutter/test/services/platform_channel_test.dart +++ b/packages/flutter/test/services/platform_channel_test.dart @@ -9,7 +9,7 @@ import 'package:flutter/services.dart'; import 'package:test/test.dart'; void main() { - group('PlatformMessageChannel', () { + group('BasicMessageChannel', () { const MessageCodec string = const StringCodec(); const BasicMessageChannel channel = const BasicMessageChannel('ch', string); test('can send string message and get reply', () async { @@ -34,7 +34,7 @@ void main() { }); }); - group('PlatformMethodChannel', () { + group('MethodChannel', () { const MessageCodec jsonMessage = const JSONMessageCodec(); const MethodCodec jsonMethod = const JSONMethodCodec(); const MethodChannel channel = const MethodChannel('ch7', jsonMethod); @@ -89,8 +89,75 @@ void main() { fail('MissingPluginException expected'); } }); + test('can handle method call with no registered plugin', () async { + channel.setMethodCallHandler(null); + final ByteData call = jsonMethod.encodeMethodCall(new MethodCall('sayHello', 'hello')); + ByteData envelope; + await BinaryMessages.handlePlatformMessage('ch7', call, (ByteData result) { + envelope = result; + }); + expect(envelope, isNull); + }); + test('can handle method call of unimplemented method', () async { + channel.setMethodCallHandler((MethodCall call) async { + throw new MissingPluginException(); + }); + final ByteData call = jsonMethod.encodeMethodCall(new MethodCall('sayHello', 'hello')); + ByteData envelope; + await BinaryMessages.handlePlatformMessage('ch7', call, (ByteData result) { + envelope = result; + }); + expect(envelope, isNull); + }); + test('can handle method call with successful result', () async { + channel.setMethodCallHandler((MethodCall call) async => '${call.arguments}, world'); + final ByteData call = jsonMethod.encodeMethodCall(new MethodCall('sayHello', 'hello')); + ByteData envelope; + await BinaryMessages.handlePlatformMessage('ch7', call, (ByteData result) { + envelope = result; + }); + expect(jsonMethod.decodeEnvelope(envelope), equals('hello, world')); + }); + test('can handle method call with expressive error result', () async { + channel.setMethodCallHandler((MethodCall call) async { + throw new PlatformException(code: 'bad', message: 'sayHello failed', details: null); + }); + final ByteData call = jsonMethod.encodeMethodCall(new MethodCall('sayHello', 'hello')); + ByteData envelope; + await BinaryMessages.handlePlatformMessage('ch7', call, (ByteData result) { + envelope = result; + }); + try { + jsonMethod.decodeEnvelope(envelope); + fail('Exception expected'); + } on PlatformException catch(e) { + expect(e.code, equals('bad')); + expect(e.message, equals('sayHello failed')); + } catch (e) { + fail('PlatformException expected'); + } + }); + test('can handle method call with other error result', () async { + channel.setMethodCallHandler((MethodCall call) async { + throw new ArgumentError('bad'); + }); + final ByteData call = jsonMethod.encodeMethodCall(new MethodCall('sayHello', 'hello')); + ByteData envelope; + await BinaryMessages.handlePlatformMessage('ch7', call, (ByteData result) { + envelope = result; + }); + try { + jsonMethod.decodeEnvelope(envelope); + fail('Exception expected'); + } on PlatformException catch(e) { + expect(e.code, equals('error')); + expect(e.message, equals('Invalid argument(s): bad')); + } catch (e) { + fail('PlatformException expected'); + } + }); }); - group('PlatformEventChannel', () { + group('EventChannel', () { const MessageCodec jsonMessage = const JSONMessageCodec(); const MethodCodec jsonMethod = const JSONMethodCodec(); const EventChannel channel = const EventChannel('ch', jsonMethod);