[flutter_tools] Migrate to package:vm_service 4: trigonometric boogaloo (#54132)

diff --git a/packages/flutter_tools/lib/src/devfs.dart b/packages/flutter_tools/lib/src/devfs.dart
index 7af81d8..db15800 100644
--- a/packages/flutter_tools/lib/src/devfs.dart
+++ b/packages/flutter_tools/lib/src/devfs.dart
@@ -4,8 +4,8 @@
 
 import 'dart:async';
 
-import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
 import 'package:meta/meta.dart';
+import 'package:vm_service/vm_service.dart' as vmservice;
 
 import 'asset.dart';
 import 'base/context.dart';
@@ -439,7 +439,7 @@
     globals.printTrace('DevFS: Creating new filesystem on the device ($_baseUri)');
     try {
       _baseUri = await _operations.create(fsName);
-    } on rpc.RpcException catch (rpcException) {
+    } on vmservice.RPCError catch (rpcException) {
       // 1001 is kFileSystemAlreadyExists in //dart/runtime/vm/json_stream.h
       if (rpcException.code != 1001) {
         rethrow;
diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
index b540cdf..11c2d32 100644
--- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
+++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
@@ -620,7 +620,7 @@
         // loopback (::1).
         final Uri uri = Uri.parse('http://[$_ipv6Loopback]:$port');
         final VMService vmService = await VMService.connect(uri);
-        await vmService.getVM();
+        await vmService.getVMOld();
         await vmService.refreshViews();
         for (final FlutterView flutterView in vmService.vm.views) {
           if (flutterView.uiIsolate == null) {
@@ -717,7 +717,7 @@
           continue;
         }
       }
-      await service.getVM();
+      await service.getVMOld();
       await service.refreshViews();
       for (final FlutterView flutterView in service.vm.views) {
         if (flutterView.uiIsolate == null) {
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart
index f08c66a..5a34d94 100644
--- a/packages/flutter_tools/lib/src/ios/devices.dart
+++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -7,11 +7,11 @@
 
 import 'package:meta/meta.dart';
 import 'package:platform/platform.dart';
+import 'package:vm_service/vm_service.dart' as vm_service;
 import 'package:process/process.dart';
 
 import '../application_package.dart';
 import '../artifacts.dart';
-import '../base/common.dart';
 import '../base/file_system.dart';
 import '../base/io.dart';
 import '../base/logger.dart';
@@ -514,7 +514,7 @@
     // and "Flutter". The regex tries to strike a balance between not producing
     // false positives and not producing false negatives.
     _anyLineRegex = RegExp(r'\w+(\([^)]*\))?\[\d+\] <[A-Za-z]+>: ');
-    _loggingSubscriptions = <StreamSubscription<ServiceEvent>>[];
+    _loggingSubscriptions = <StreamSubscription<void>>[];
   }
 
   /// Create a new [IOSDeviceLogReader].
@@ -554,7 +554,7 @@
   RegExp _anyLineRegex;
 
   StreamController<String> _linesController;
-  List<StreamSubscription<ServiceEvent>> _loggingSubscriptions;
+  List<StreamSubscription<void>> _loggingSubscriptions;
 
   @override
   Stream<String> get logLines => _linesController.stream;
@@ -575,18 +575,20 @@
     if (_majorSdkVersion < _minimumUniversalLoggingSdkVersion) {
       return;
     }
-    // The VM service will not publish logging events unless the debug stream is being listened to.
-    // onDebugEvent listens to this stream as a side effect.
-    unawaited(connectedVmService.onDebugEvent);
-    _loggingSubscriptions.add((await connectedVmService.onStdoutEvent).listen((ServiceEvent event) {
-      final String logMessage = event.message;
-      if (logMessage.isNotEmpty) {
-        _linesController.add(logMessage);
+    try {
+      await connectedVmService.streamListen('Stdout');
+    } on vm_service.RPCError {
+      // Do nothing, since the tool is already subscribed.
+    }
+    _loggingSubscriptions.add(connectedVmService.onStdoutEvent.listen((vm_service.Event event) {
+      final String message = utf8.decode(base64.decode(event.bytes));
+      if (message.isNotEmpty) {
+        _linesController.add(message);
       }
     }));
   }
 
-  void _listenToSysLog () {
+  void _listenToSysLog() {
     // syslog is not written on iOS 13+.
     if (_majorSdkVersion >= _minimumUniversalLoggingSdkVersion) {
       return;
@@ -641,7 +643,7 @@
 
   @override
   void dispose() {
-    for (final StreamSubscription<ServiceEvent> loggingSubscription in _loggingSubscriptions) {
+    for (final StreamSubscription<void> loggingSubscription in _loggingSubscriptions) {
       loggingSubscription.cancel();
     }
     _idevicesyslogProcess?.kill();
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart
index 92409dd..0fb3842 100644
--- a/packages/flutter_tools/lib/src/resident_runner.dart
+++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -234,7 +234,7 @@
         : vmService.vm.views).toList();
   }
 
-  Future<void> getVMs() => vmService.getVM();
+  Future<void> getVMs() => vmService.getVMOld();
 
   Future<void> exitApps() async {
     if (!device.supportsFlutterExit) {
diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart
index 3df2c5b..64ac9c0 100644
--- a/packages/flutter_tools/lib/src/run_hot.dart
+++ b/packages/flutter_tools/lib/src/run_hot.dart
@@ -3,10 +3,8 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-
+import 'package:vm_service/vm_service.dart' as vm_service;
 import 'package:platform/platform.dart';
-import 'package:json_rpc_2/error_code.dart' as rpc_error_code;
-import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
 import 'package:meta/meta.dart';
 import 'package:pool/pool.dart';
 import 'base/async_guard.dart';
@@ -110,9 +108,10 @@
     // TODO(cbernaschina): check that isolateId is the id of the UI isolate.
     final OperationResult result = await restart(pause: pause);
     if (!result.isOk) {
-      throw rpc.RpcException(
-        rpc_error_code.INTERNAL_ERROR,
+      throw vm_service.RPCError(
         'Unable to reload sources',
+        RPCErrorCodes.kInternalError,
+        '',
       );
     }
   }
@@ -121,9 +120,10 @@
     final OperationResult result =
       await restart(fullRestart: true, pause: pause);
     if (!result.isOk) {
-      throw rpc.RpcException(
-        rpc_error_code.INTERNAL_ERROR,
+      throw vm_service.RPCError(
         'Unable to restart',
+        RPCErrorCodes.kInternalError,
+        '',
       );
     }
   }
@@ -564,16 +564,15 @@
     if (benchmarkMode) {
       final List<Future<void>> isolateNotifications = <Future<void>>[];
       for (final FlutterDevice device in flutterDevices) {
+        try {
+          await device.vmService.streamListen('Isolate');
+        } on vm_service.RPCError {
+          // Do nothing, we're already subcribed.
+        }
         for (final FlutterView view in device.views) {
           isolateNotifications.add(
-            view.owner.vm.vmService.onIsolateEvent
-              .then((Stream<ServiceEvent> serviceEvents) async {
-              await for (final ServiceEvent serviceEvent in serviceEvents) {
-                if (serviceEvent.owner.name.contains('_spawn')
-                  && serviceEvent.kind == ServiceEvent.kIsolateExit) {
-                  return;
-                }
-              }
+            view.owner.vm.vmService.onIsolateEvent.firstWhere((vm_service.Event event) {
+              return event.kind == vm_service.EventKind.kServiceExtensionAdded;
             }),
           );
         }
@@ -720,9 +719,12 @@
       if (!result.isOk) {
         restartEvent = 'restart-failed';
       }
-    } on rpc.RpcException {
+    } on vm_service.SentinelException catch (err, st) {
       restartEvent = 'exception';
-      return OperationResult(1, 'hot restart failed to complete', fatal: true);
+      return OperationResult(1, 'hot restart failed to complete: $err\n$st', fatal: true);
+    } on vm_service.RPCError  catch (err, st) {
+      restartEvent = 'exception';
+      return OperationResult(1, 'hot restart failed to complete: $err\n$st', fatal: true);
     } finally {
       HotEvent(restartEvent,
         targetPlatform: targetPlatform,
@@ -764,7 +766,7 @@
           );
         },
       );
-    } on rpc.RpcException {
+    } on vm_service.RPCError {
       HotEvent('exception',
         targetPlatform: targetPlatform,
         sdkName: sdkName,
diff --git a/packages/flutter_tools/lib/src/test/coverage_collector.dart b/packages/flutter_tools/lib/src/test/coverage_collector.dart
index e1fa2cf..9523d64 100644
--- a/packages/flutter_tools/lib/src/test/coverage_collector.dart
+++ b/packages/flutter_tools/lib/src/test/coverage_collector.dart
@@ -189,7 +189,7 @@
   Future<VMService> Function(Uri) connector = _defaultConnect,
 }) async {
   final VMService vmService = await connector(serviceUri);
-  await vmService.getVM();
+  await vmService.getVMOld();
   final Map<String, dynamic> result = await _getAllCoverage(
       vmService, libraryPredicate);
   await vmService.close();
@@ -197,7 +197,7 @@
 }
 
 Future<Map<String, dynamic>> _getAllCoverage(VMService service, bool Function(String) libraryPredicate) async {
-  await service.getVM();
+  await service.getVMOld();
   final List<Map<String, dynamic>> coverage = <Map<String, dynamic>>[];
   for (final Isolate isolateRef in service.vm.isolates) {
     await isolateRef.load();
diff --git a/packages/flutter_tools/lib/src/tracing.dart b/packages/flutter_tools/lib/src/tracing.dart
index 25402b8..5ea6cfb 100644
--- a/packages/flutter_tools/lib/src/tracing.dart
+++ b/packages/flutter_tools/lib/src/tracing.dart
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import 'dart:async';
+import 'package:vm_service/vm_service.dart' as vm_service;
 
 import 'base/file_system.dart';
 import 'base/logger.dart';
@@ -45,7 +46,8 @@
       );
       try {
         final Completer<void> whenFirstFrameRendered = Completer<void>();
-        (await vmService.onExtensionEvent).listen((ServiceEvent event) {
+        await vmService.streamListen('Extension');
+        vmService.onExtensionEvent.listen((vm_service.Event event) {
           if (event.extensionKind == 'Flutter.FirstFrame') {
             whenFirstFrameRendered.complete();
           }
diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart
index 3db48d0..7f05b7a 100644
--- a/packages/flutter_tools/lib/src/vmservice.dart
+++ b/packages/flutter_tools/lib/src/vmservice.dart
@@ -5,18 +5,14 @@
 import 'dart:async';
 import 'dart:math' as math;
 
-import 'package:json_rpc_2/error_code.dart' as rpc_error_code;
-import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
 import 'package:meta/meta.dart' show required;
-import 'package:stream_channel/stream_channel.dart';
-import 'package:web_socket_channel/io.dart';
-import 'package:web_socket_channel/web_socket_channel.dart';
+import 'package:vm_service/vm_service.dart' as vm_service;
 
 import 'base/common.dart';
 import 'base/context.dart';
 import 'base/io.dart' as io;
 import 'base/utils.dart';
-import 'convert.dart' show base64, utf8;
+import 'convert.dart' show base64, json, utf8;
 import 'device.dart';
 import 'globals.dart' as globals;
 import 'version.dart';
@@ -25,10 +21,24 @@
 /// for [WebSocket]s (used by tests).
 typedef WebSocketConnector = Future<io.WebSocket> Function(String url, {io.CompressionOptions compression});
 
-/// A function that opens a two-way communication channel to the specified [uri].
-typedef _OpenChannel = Future<StreamChannel<String>> Function(Uri uri, {io.CompressionOptions compression});
+WebSocketConnector _openChannel = _defaultOpenChannel;
 
-_OpenChannel _openChannel = _defaultOpenChannel;
+/// The error codes for the JSON-RPC standard.
+///
+/// See also: https://www.jsonrpc.org/specification#error_object
+abstract class RPCErrorCodes {
+  /// The method does not exist or is not available.
+  static const int kMethodNotFound = -32601;
+
+  /// Invalid method parameter(s), such as a mismatched type.
+  static const int kInvalidParams = -32602;
+
+  /// Internal JSON-RPC error.
+  static const int kInternalError = -32603;
+
+  /// Application specific error codes.s
+  static const int kServerError = -32000;
+}
 
 /// A function that reacts to the invocation of the 'reloadSources' service.
 ///
@@ -64,7 +74,9 @@
   String libraryId,
 });
 
-Future<StreamChannel<String>> _defaultOpenChannel(Uri uri, {io.CompressionOptions compression = io.CompressionOptions.compressionDefault}) async {
+Future<io.WebSocket> _defaultOpenChannel(String url, {
+  io.CompressionOptions compression = io.CompressionOptions.compressionDefault
+}) async {
   Duration delay = const Duration(milliseconds: 100);
   int attempts = 0;
   io.WebSocket socket;
@@ -90,14 +102,14 @@
   while (socket == null) {
     attempts += 1;
     try {
-      socket = await constructor(uri.toString(), compression: compression);
+      socket = await constructor(url, compression: compression);
     } on io.WebSocketException catch (e) {
       await handleError(e);
     } on io.SocketException catch (e) {
       await handleError(e);
     }
   }
-  return IOWebSocketChannel(socket).cast<String>();
+  return socket;
 }
 
 /// Override `VMServiceConnector` in [context] to return a different VMService
@@ -112,10 +124,10 @@
 });
 
 /// A connection to the Dart VM Service.
-// TODO(mklim): Test this, https://github.com/flutter/flutter/issues/23031
-class VMService {
+///
+/// This also implements the package:vm_service API to enable a gradual migration.
+class VMService implements vm_service.VmService {
   VMService(
-    this._peer,
     this.httpAddress,
     this.wsAddress,
     ReloadSources reloadSources,
@@ -123,38 +135,49 @@
     CompileExpression compileExpression,
     Device device,
     ReloadMethod reloadMethod,
+    this._delegateService,
+    this.streamClosedCompleter,
+    Stream<dynamic> secondary,
   ) {
     _vm = VM._empty(this);
-    _peer.listen().catchError(_connectionError.completeError);
 
-    _peer.registerMethod('streamNotify', (rpc.Parameters event) {
-      _handleStreamNotify(event.asMap.cast<String, dynamic>());
+    // TODO(jonahwilliams): this is temporary to support the current vm_service
+    // semantics of update-in-place.
+    secondary.listen((dynamic rawData) {
+      final String message = rawData as String;
+      final dynamic map = json.decode(message);
+      if (map != null && map['method'] == 'streamNotify') {
+        _handleStreamNotify(map['params'] as Map<String, dynamic>);
+      }
     });
 
     if (reloadSources != null) {
-      _peer.registerMethod('reloadSources', (rpc.Parameters params) async {
+      _delegateService.registerServiceCallback('reloadSources', (Map<String, dynamic> params) async {
         final String isolateId = params['isolateId'].value as String;
-        final bool force = params.asMap['force'] as bool ?? false;
-        final bool pause = params.asMap['pause'] as bool ?? false;
+        final bool force = params['force'] as bool ?? false;
+        final bool pause = params['pause'] as bool ?? false;
 
         if (isolateId.isEmpty) {
-          throw rpc.RpcException.invalidParams("Invalid 'isolateId': $isolateId");
+          throw vm_service.RPCError(
+            "Invalid 'isolateId': $isolateId",
+            RPCErrorCodes.kInvalidParams,
+            '',
+          );
         }
         try {
           await reloadSources(isolateId, force: force, pause: pause);
           return <String, String>{'type': 'Success'};
-        } on rpc.RpcException {
+        } on vm_service.RPCError {
           rethrow;
         } on Exception catch (e, st) {
-          throw rpc.RpcException(rpc_error_code.SERVER_ERROR,
-              'Error during Sources Reload: $e\n$st');
+          throw vm_service.RPCError(
+            'Error during Sources Reload: $e\n$st',
+            RPCErrorCodes.kServerError,
+            '',
+          );
         }
       });
-
-      _peer.sendNotification('registerService', <String, String>{
-        'service': 'reloadSources',
-        'alias': 'Flutter Tools',
-      });
+      _delegateService.registerService('reloadSources', 'Flutter Tools');
 
     }
 
@@ -168,15 +191,23 @@
       // if the build method of a StatelessWidget is updated, this is the name of class.
       // If the build method of a StatefulWidget is updated, then this is the name
       // of the Widget class that created the State object.
-      _peer.registerMethod('reloadMethod', (rpc.Parameters params) async {
-        final String libraryId = params['library'].value as String;
-        final String classId = params['class'].value as String;
+      _delegateService.registerServiceCallback('reloadMethod', (Map<String, dynamic> params) async {
+        final String libraryId = params['library'] as String;
+        final String classId = params['class'] as String;
 
         if (libraryId.isEmpty) {
-          throw rpc.RpcException.invalidParams("Invalid 'libraryId': $libraryId");
+          throw vm_service.RPCError(
+            "Invalid 'libraryId': $libraryId",
+            RPCErrorCodes.kInvalidParams,
+            '',
+          );
         }
         if (classId.isEmpty) {
-          throw rpc.RpcException.invalidParams("Invalid 'classId': $classId");
+          throw vm_service.RPCError(
+            "Invalid 'classId': $classId",
+            RPCErrorCodes.kInvalidParams,
+            '',
+          );
         }
 
         globals.printTrace('reloadMethod not yet supported, falling back to hot reload');
@@ -187,113 +218,97 @@
             classId: classId,
           );
           return <String, String>{'type': 'Success'};
-        } on rpc.RpcException {
+        } on vm_service.RPCError {
           rethrow;
         } on Exception catch (e, st) {
-          throw rpc.RpcException(rpc_error_code.SERVER_ERROR,
-              'Error during Sources Reload: $e\n$st');
+          throw vm_service.RPCError('Error during Sources Reload: $e\n$st', -32000, '');
         }
       });
-      _peer.sendNotification('registerService', <String, String>{
-        'service': 'reloadMethod',
-        'alias': 'Flutter Tools',
-      });
+      _delegateService.registerService('reloadMethod', 'Flutter Tools');
     }
 
     if (restart != null) {
-      _peer.registerMethod('hotRestart', (rpc.Parameters params) async {
-        final bool pause = params.asMap['pause'] as bool ?? false;
-
-        if (pause is! bool) {
-          throw rpc.RpcException.invalidParams("Invalid 'pause': $pause");
-        }
-
+      _delegateService.registerServiceCallback('hotRestart', (Map<String, dynamic> params) async {
+        final bool pause = params['pause'] as bool ?? false;
         try {
           await restart(pause: pause);
           return <String, String>{'type': 'Success'};
-        } on rpc.RpcException {
+        } on vm_service.RPCError {
           rethrow;
         } on Exception catch (e, st) {
-          throw rpc.RpcException(rpc_error_code.SERVER_ERROR,
-              'Error during Hot Restart: $e\n$st');
+          throw vm_service.RPCError(
+            'Error during Hot Restart: $e\n$st',
+            RPCErrorCodes.kServerError,
+            '',
+          );
         }
       });
-
-      _peer.sendNotification('registerService', <String, String>{
-        'service': 'hotRestart',
-        'alias': 'Flutter Tools',
-      });
+      _delegateService.registerService('hotRestart', 'Flutter Tools');
     }
 
-    _peer.registerMethod('flutterVersion', (rpc.Parameters params) async {
+    _delegateService.registerServiceCallback('flutterVersion', (Map<String, dynamic> params) async {
       final FlutterVersion version = context.get<FlutterVersion>() ?? FlutterVersion();
       final Map<String, Object> versionJson = version.toJson();
       versionJson['frameworkRevisionShort'] = version.frameworkRevisionShort;
       versionJson['engineRevisionShort'] = version.engineRevisionShort;
       return versionJson;
     });
-
-    _peer.sendNotification('registerService', <String, String>{
-      'service': 'flutterVersion',
-      'alias': 'Flutter Tools',
-    });
+    _delegateService.registerService('flutterVersion', 'Flutter Tools');
 
     if (compileExpression != null) {
-      _peer.registerMethod('compileExpression', (rpc.Parameters params) async {
-        final String isolateId = params['isolateId'].asString;
+      _delegateService.registerServiceCallback('compileExpression', (Map<String, dynamic> params) async {
+        final String isolateId = params['isolateId'] as String;
         if (isolateId is! String || isolateId.isEmpty) {
-          throw rpc.RpcException.invalidParams(
-              "Invalid 'isolateId': $isolateId");
+          throw throw vm_service.RPCError(
+            "Invalid 'isolateId': $isolateId",
+            RPCErrorCodes.kInvalidParams,
+            '',
+          );
         }
-        final String expression = params['expression'].asString;
+        final String expression = params['expression'] as String;
         if (expression is! String || expression.isEmpty) {
-          throw rpc.RpcException.invalidParams(
-              "Invalid 'expression': $expression");
+          throw throw vm_service.RPCError(
+            "Invalid 'expression': $expression",
+            RPCErrorCodes.kInvalidParams,
+            '',
+          );
         }
-        final List<String> definitions =
-            List<String>.from(params['definitions'].asList);
-        final List<String> typeDefinitions =
-            List<String>.from(params['typeDefinitions'].asList);
-        final String libraryUri = params['libraryUri'].asString;
-        final String klass = params['klass'].exists ? params['klass'].asString : null;
-        final bool isStatic = params['isStatic'].asBoolOr(false);
-
+        final List<String> definitions = List<String>.from(params['definitions'] as List<dynamic>);
+        final List<String> typeDefinitions = List<String>.from(params['typeDefinitions'] as List<dynamic>);
+        final String libraryUri = params['libraryUri'] as String;
+        final String klass = params['klass'] as String;
+        final bool isStatic = params['isStatic'] as bool ?? false;
         try {
           final String kernelBytesBase64 = await compileExpression(isolateId,
               expression, definitions, typeDefinitions, libraryUri, klass,
               isStatic);
-          return <String, dynamic>{'type': 'Success',
-            'result': <String, dynamic> {'kernelBytes': kernelBytesBase64}};
-        } on rpc.RpcException {
+          return <String, dynamic>{
+            'type': 'Success',
+            'result': <String, dynamic>{
+              'result': <String, dynamic>{'kernelBytes': kernelBytesBase64},
+            },
+          };
+        } on vm_service.RPCError {
           rethrow;
         } on Exception catch (e, st) {
-          throw rpc.RpcException(rpc_error_code.SERVER_ERROR,
-              'Error during expression compilation: $e\n$st');
+          throw vm_service.RPCError(
+            'Error during expression compilation: $e\n$st',
+            RPCErrorCodes.kServerError,
+            '',
+          );
         }
       });
-
-      _peer.sendNotification('registerService', <String, String>{
-        'service': 'compileExpression',
-        'alias': 'Flutter Tools',
-      });
+      _delegateService.registerService('compileExpression', 'Flutter Tools');
     }
     if (device != null) {
-      _peer.registerMethod('flutterMemoryInfo', (rpc.Parameters params) async {
+      _delegateService.registerServiceCallback('flutterMemoryInfo', (Map<String, dynamic> params) async {
         final MemoryInfo result = await device.queryMemoryInfo();
         return result.toJson();
       });
-      _peer.sendNotification('registerService', <String, String>{
-        'service': 'flutterMemoryInfo',
-        'alias': 'Flutter Tools',
-      });
+      _delegateService.registerService('flutterMemoryInfo', 'Flutter Tools');
     }
   }
 
-  static void _unhandledError(dynamic error, dynamic stack) {
-    globals.logger.printTrace('Error in internal implementation of JSON RPC.\n$error\n$stack');
-    assert(false);
-  }
-
   /// Connect to a Dart VM Service at [httpUri].
   ///
   /// If the [reloadSources] parameter is not null, the 'reloadSources' service
@@ -332,10 +347,38 @@
     Device device,
   }) async {
     final Uri wsUri = httpUri.replace(scheme: 'ws', path: globals.fs.path.join(httpUri.path, 'ws'));
-    final StreamChannel<String> channel = await _openChannel(wsUri, compression: compression);
-    final rpc.Peer peer = rpc.Peer.withoutJson(jsonDocument.bind(channel), onUnhandledError: _unhandledError);
+    final io.WebSocket channel = await _openChannel(wsUri.toString(), compression: compression);
+    final StreamController<dynamic> primary = StreamController<dynamic>();
+    final StreamController<dynamic> secondary = StreamController<dynamic>();
+
+    channel.listen((dynamic data) {
+      primary.add(data);
+      secondary.add(data);
+    }, onDone: ()  {
+      primary.close();
+      secondary.close();
+    }, onError: (dynamic error, StackTrace stackTrace) {
+      primary.addError(error, stackTrace);
+      secondary.addError(error, stackTrace);
+    });
+
+    // Create an instance of the package:vm_service API in addition to the flutter
+    // tool's to allow gradual migration.
+    final Completer<void> streamClosedCompleter = Completer<void>();
+
+    final vm_service.VmService delegateService = vm_service.VmService(
+      primary.stream,
+      channel.add,
+      log: null,
+      disposeHandler: () async {
+        if (!streamClosedCompleter.isCompleted) {
+          streamClosedCompleter.complete();
+        }
+        await channel.close();
+      },
+    );
+
     final VMService service = VMService(
-      peer,
       httpUri,
       wsUri,
       reloadSources,
@@ -343,17 +386,21 @@
       compileExpression,
       device,
       reloadMethod,
+      delegateService,
+      streamClosedCompleter,
+      secondary.stream,
     );
+
     // This call is to ensure we are able to establish a connection instead of
     // keeping on trucking and failing farther down the process.
-    await service._sendRequest('getVersion', const <String, dynamic>{});
+    await delegateService.getVersion();
     return service;
   }
 
+  final vm_service.VmService _delegateService;
   final Uri httpAddress;
   final Uri wsAddress;
-  final rpc.Peer _peer;
-  final Completer<Map<String, dynamic>> _connectionError = Completer<Map<String, dynamic>>();
+  final Completer<void> streamClosedCompleter;
 
   VM _vm;
   /// The singleton [VM] object. Owns [Isolate] and [FlutterView] objects.
@@ -362,46 +409,36 @@
   final Map<String, StreamController<ServiceEvent>> _eventControllers =
       <String, StreamController<ServiceEvent>>{};
 
-  final Set<String> _listeningFor = <String>{};
-
   /// Whether our connection to the VM service has been closed;
-  bool get isClosed => _peer.isClosed;
+  bool get isClosed => streamClosedCompleter.isCompleted;
 
   Future<void> get done async {
-    await _peer.done;
+    return streamClosedCompleter.future;
   }
 
-  // Events
-  Future<Stream<ServiceEvent>> get onDebugEvent => onEvent('Debug');
-  Future<Stream<ServiceEvent>> get onExtensionEvent => onEvent('Extension');
-  // IsolateStart, IsolateRunnable, IsolateExit, IsolateUpdate, ServiceExtensionAdded
-  Future<Stream<ServiceEvent>> get onIsolateEvent => onEvent('Isolate');
-  Future<Stream<ServiceEvent>> get onTimelineEvent => onEvent('Timeline');
-  Future<Stream<ServiceEvent>> get onStdoutEvent => onEvent('Stdout'); // WriteEvent
+  @override
+  Stream<vm_service.Event> get onDebugEvent => onEvent('Debug');
 
-  // TODO(johnmccutchan): Add FlutterView events.
+  @override
+  Stream<vm_service.Event> get onExtensionEvent => onEvent('Extension');
 
-  /// Returns a stream of VM service events.
-  ///
-  /// This purposely returns a `Future<Stream<T>>` rather than a `Stream<T>`
-  /// because it first registers with the VM to receive events on the stream,
-  /// and only once the VM has acknowledged that the stream has started will
-  /// we return the associated stream. Any attempt to streamline this API into
-  /// returning `Stream<T>` should take that into account to avoid race
-  /// conditions.
-  Future<Stream<ServiceEvent>> onEvent(String streamId) async {
-    await _streamListen(streamId);
-    return _getEventController(streamId).stream;
+  @override
+  Stream<vm_service.Event> get onIsolateEvent => onEvent('Isolate');
+
+  @override
+  Stream<vm_service.Event> get onTimelineEvent => onEvent('Timeline');
+
+  @override
+  Stream<vm_service.Event> get onStdoutEvent => onEvent('Stdout');
+
+  @override
+  Future<vm_service.Success> streamListen(String streamId) {
+    return _delegateService.streamListen(streamId);
   }
 
-  Future<Map<String, dynamic>> _sendRequest(
-    String method,
-    Map<String, dynamic> params,
-  ) {
-    return Future.any<Map<String, dynamic>>(<Future<Map<String, dynamic>>>[
-      _peer.sendRequest(method, params).then<Map<String, dynamic>>(castStringKeyedMap),
-      _connectionError.future,
-    ]);
+  @override
+  Stream<vm_service.Event> onEvent(String streamId) {
+    return _delegateService.onEvent(streamId);
   }
 
   StreamController<ServiceEvent> _getEventController(String eventName) {
@@ -441,19 +478,20 @@
     _getEventController(streamId).add(event);
   }
 
-  Future<void> _streamListen(String streamId) async {
-    if (!_listeningFor.contains(streamId)) {
-      _listeningFor.add(streamId);
-      await _sendRequest('streamListen', <String, dynamic>{'streamId': streamId});
-    }
-  }
-
   /// Reloads the VM.
-  Future<void> getVM() async => await vm.reload();
+  Future<void> getVMOld() async => await vm.reload();
 
   Future<void> refreshViews({ bool waitForViews = false }) => vm.refreshViews(waitForViews: waitForViews);
 
-  Future<void> close() async => await _peer.close();
+  Future<void> close() async {
+    _delegateService?.dispose();
+  }
+
+  // To enable a gradual migration to package:vm_service
+  @override
+  dynamic noSuchMethod(Invocation invocation) {
+    throw UnsupportedError('${invocation.memberName} is not currently supported');
+  }
 }
 
 /// An error that is thrown when constructing/updating a service object.
@@ -936,36 +974,15 @@
     return Future<Isolate>.value(_isolateCache[isolateId]);
   }
 
-  static String _truncate(String message, int width, String ellipsis) {
-    assert(ellipsis.length < width);
-    if (message.length <= width) {
-      return message;
-    }
-    return message.substring(0, width - ellipsis.length) + ellipsis;
-  }
-
   /// Invoke the RPC and return the raw response.
   Future<Map<String, dynamic>> invokeRpcRaw(
     String method, {
     Map<String, dynamic> params = const <String, dynamic>{},
     bool truncateLogs = true,
   }) async {
-    globals.printTrace('Sending to VM service: $method($params)');
-    assert(params != null);
-    try {
-      final Map<String, dynamic> result = await _vmService._sendRequest(method, params);
-      final String resultString =
-          truncateLogs ? _truncate(result.toString(), 250, '...') : result.toString();
-      globals.printTrace('Result: $resultString');
-      return result;
-    } on WebSocketChannelException catch (error) {
-      throwToolExit('Error connecting to observatory: $error');
-      return null;
-    } on rpc.RpcException catch (error) {
-      globals.printError('Error ${error.code} received from application: ${error.message}');
-      globals.printTrace('${error.data}');
-      rethrow;
-    }
+    final vm_service.Response response = await _vmService
+      ._delegateService.callServiceExtension(method, args: params);
+    return response.json;
   }
 
   /// Invoke the RPC and return a [ServiceObject] response.
@@ -1264,13 +1281,17 @@
       }
       final Map<String, dynamic> response = await invokeRpcRaw('_reloadSources', params: arguments);
       return response;
-    } on rpc.RpcException catch (e) {
+    } on vm_service.RPCError catch (e) {
       return Future<Map<String, dynamic>>.value(<String, dynamic>{
         'code': e.code,
         'message': e.message,
         'data': e.data,
       });
+    } on vm_service.SentinelException catch (e) {
+      throwToolExit('Unexpected Sentinel while reloading sources: $e');
     }
+    assert(false);
+    return null;
   }
 
   Future<Map<String, dynamic>> getObject(Map<String, dynamic> objectRef) {
@@ -1293,9 +1314,9 @@
   }) async {
     try {
       return await invokeRpcRaw(method, params: params);
-    } on rpc.RpcException catch (e) {
+    } on vm_service.RPCError catch (err) {
       // If an application is not using the framework
-      if (e.code == rpc_error_code.METHOD_NOT_FOUND) {
+      if (err.code == RPCErrorCodes.kMethodNotFound) {
         return null;
       }
       rethrow;
@@ -1491,10 +1512,13 @@
     final String viewId = id;
     // When this completer completes the isolate is running.
     final Completer<void> completer = Completer<void>();
-    final StreamSubscription<ServiceEvent> subscription =
-      (await owner.vm.vmService.onIsolateEvent).listen((ServiceEvent event) {
-        // TODO(johnmccutchan): Listen to the debug stream and catch initial
-        // launch errors.
+    try {
+      await owner.vm.vmService.streamListen('Isolate');
+    } on vm_service.RPCError {
+      // Do nothing, since the tool is already subscribed.
+    }
+    final StreamSubscription<vm_service.Event> subscription =
+      owner.vm.vmService.onIsolateEvent.listen((vm_service.Event event) {
         if (event.kind == ServiceEvent.kIsolateRunnable) {
           globals.printTrace('Isolate is runnable.');
           if (!completer.isCompleted) {
diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml
index d83fa20..57276ff 100644
--- a/packages/flutter_tools/pubspec.yaml
+++ b/packages/flutter_tools/pubspec.yaml
@@ -19,7 +19,6 @@
   flutter_template_images: 1.0.1
   http: 0.12.0+4
   intl: 0.16.1
-  json_rpc_2: 2.1.0
   meta: 1.1.8
   multicast_dns: 0.2.2
   mustache_template: 1.0.0+1
@@ -28,10 +27,8 @@
   process: 3.0.12
   quiver: 2.1.3
   stack_trace: 1.9.3
-  stream_channel: 2.0.0
   usage: 3.4.1
   webdriver: 2.1.2
-  web_socket_channel: 1.1.0
   webkit_inspection_protocol: 0.5.0+1
   xml: 3.7.0
   yaml: 2.2.0
@@ -88,6 +85,7 @@
   source_maps: 0.10.9 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   source_span: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   sse: 3.2.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  stream_channel: 2.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   stream_transform: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   string_scanner: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   sync_http: 0.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -95,6 +93,7 @@
   typed_data: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   uuid: 2.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  web_socket_channel: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
 
 dev_dependencies:
   collection: 1.14.12
@@ -112,4 +111,4 @@
   # Exclude this package from the hosted API docs.
   nodoc: true
 
-# PUBSPEC CHECKSUM: 8ac6
+# PUBSPEC CHECKSUM: 7689
diff --git a/packages/flutter_tools/test/general.shard/devfs_test.dart b/packages/flutter_tools/test/general.shard/devfs_test.dart
index 1b47fe5..f85f99e 100644
--- a/packages/flutter_tools/test/general.shard/devfs_test.dart
+++ b/packages/flutter_tools/test/general.shard/devfs_test.dart
@@ -15,8 +15,8 @@
 import 'package:flutter_tools/src/compile.dart';
 import 'package:flutter_tools/src/devfs.dart';
 import 'package:flutter_tools/src/vmservice.dart';
-import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
 import 'package:mockito/mockito.dart';
+import 'package:vm_service/vm_service.dart' as vm_service;
 
 import '../src/common.dart';
 import '../src/context.dart';
@@ -403,7 +403,7 @@
   Future<Map<String, dynamic>> createDevFS(String fsName) async {
     _service.messages.add('create $fsName');
     if (_devFSExists) {
-      throw rpc.RpcException(kFileSystemAlreadyExists, 'File system already exists');
+      throw vm_service.RPCError('File system already exists', kFileSystemAlreadyExists, '');
     }
     _devFSExists = true;
     return <String, dynamic>{'uri': '$_baseUri'};
diff --git a/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart b/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart
index fd6e8a7..43e2854 100644
--- a/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart
+++ b/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart
@@ -596,7 +596,7 @@
           .thenAnswer((Invocation invocation) async => <int>[1]);
       when(portForwarder.forward(1))
           .thenAnswer((Invocation invocation) async => 2);
-      when(vmService.getVM())
+      when(vmService.getVMOld())
           .thenAnswer((Invocation invocation) => Future<void>.value(null));
       when(vmService.refreshViews())
           .thenAnswer((Invocation invocation) => Future<void>.value(null));
diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_logger_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_logger_test.dart
index 0510ef3..415a3eb 100644
--- a/packages/flutter_tools/test/general.shard/ios/ios_device_logger_test.dart
+++ b/packages/flutter_tools/test/general.shard/ios/ios_device_logger_test.dart
@@ -2,13 +2,18 @@
 // 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_tools/src/artifacts.dart';
 import 'package:flutter_tools/src/base/logger.dart';
 import 'package:flutter_tools/src/build_info.dart';
+import 'package:flutter_tools/src/convert.dart';
 import 'package:flutter_tools/src/device.dart';
 import 'package:flutter_tools/src/ios/devices.dart';
 import 'package:flutter_tools/src/ios/mac.dart';
+import 'package:flutter_tools/src/vmservice.dart';
 import 'package:mockito/mockito.dart';
+import 'package:vm_service/vm_service.dart';
 
 import '../../src/common.dart';
 import '../../src/context.dart';
@@ -137,6 +142,39 @@
       '  with a non-Flutter log message following it.',
     ]);
   });
+
+  testWithoutContext('IOSDeviceLogReader can listen to VM Service logs', () async {
+    final MockVmService vmService = MockVmService();
+    final DeviceLogReader logReader = IOSDeviceLogReader.test(
+      useSyslog: false,
+      iMobileDevice: IMobileDevice(
+        artifacts: artifacts,
+        processManager: processManager,
+        cache: fakeCache,
+        logger: logger,
+      ),
+    );
+    final StreamController<Event> controller = StreamController<Event>();
+    final Completer<Success> stdoutCompleter = Completer<Success>();
+    when(vmService.streamListen('Stdout')).thenAnswer((Invocation invocation) {
+      return stdoutCompleter.future;
+    });
+    when(vmService.onStdoutEvent).thenAnswer((Invocation invocation) {
+      return controller.stream;
+    });
+    logReader.connectedVMService = vmService;
+
+    stdoutCompleter.complete(Success());
+    controller.add(Event(
+      kind: 'Stdout',
+      timestamp: 0,
+      bytes: base64.encode(utf8.encode('  This is a message ')),
+    ));
+
+    // Wait for stream listeners to fire.
+    await expectLater(logReader.logLines, emits('  This is a message '));
+  });
 }
 
 class MockArtifacts extends Mock implements Artifacts {}
+class MockVmService extends Mock implements VMService, VmService {}
diff --git a/packages/flutter_tools/test/general.shard/resident_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_runner_test.dart
index 5e3d1e2..569ae98 100644
--- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart
+++ b/packages/flutter_tools/test/general.shard/resident_runner_test.dart
@@ -24,8 +24,8 @@
 import 'package:flutter_tools/src/run_cold.dart';
 import 'package:flutter_tools/src/run_hot.dart';
 import 'package:flutter_tools/src/vmservice.dart';
-import 'package:json_rpc_2/json_rpc_2.dart';
 import 'package:mockito/mockito.dart';
+import 'package:vm_service/vm_service.dart' as vm_service;
 
 import '../src/common.dart';
 import '../src/context.dart';
@@ -224,7 +224,7 @@
       pathToReload: anyNamed('pathToReload'),
       invalidatedFiles: anyNamed('invalidatedFiles'),
       dillOutputPath: anyNamed('dillOutputPath'),
-    )).thenThrow(RpcException(666, 'something bad happened'));
+    )).thenThrow(vm_service.RPCError('something bad happened', 666, ''));
 
     final OperationResult result = await residentRunner.restart(fullRestart: false);
     expect(result.fatal, true);
@@ -329,7 +329,7 @@
       pathToReload: anyNamed('pathToReload'),
       invalidatedFiles: anyNamed('invalidatedFiles'),
       dillOutputPath: anyNamed('dillOutputPath'),
-    )).thenThrow(RpcException(666, 'something bad happened'));
+    )).thenThrow(vm_service.RPCError('something bad happened', 666, ''));
 
     final OperationResult result = await residentRunner.restart(fullRestart: true);
     expect(result.fatal, true);
diff --git a/packages/flutter_tools/test/general.shard/vmservice_test.dart b/packages/flutter_tools/test/general.shard/vmservice_test.dart
index 3af20ff..7320373 100644
--- a/packages/flutter_tools/test/general.shard/vmservice_test.dart
+++ b/packages/flutter_tools/test/general.shard/vmservice_test.dart
@@ -3,362 +3,218 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:io';
 
-import 'package:flutter_tools/src/base/io.dart';
+import 'package:vm_service/vm_service.dart' as vm_service;
+import 'package:mockito/mockito.dart';
 import 'package:flutter_tools/src/base/logger.dart';
-import 'package:flutter_tools/src/base/terminal.dart';
 import 'package:flutter_tools/src/device.dart';
-import 'package:flutter_tools/src/globals.dart' as globals;
 import 'package:flutter_tools/src/version.dart';
 import 'package:flutter_tools/src/vmservice.dart';
-import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
-import 'package:mockito/mockito.dart';
-import 'package:platform/platform.dart';
-import 'package:quiver/testing/async.dart';
 
 import '../src/common.dart';
 import '../src/context.dart';
-import '../src/mocks.dart';
 
-class MockPeer implements rpc.Peer {
+final Map<String, Object> vm = <String, dynamic>{
+  'type': 'VM',
+  'name': 'vm',
+  'architectureBits': 64,
+  'targetCPU': 'x64',
+  'hostCPU': '      Intel(R) Xeon(R) CPU    E5-1650 v2 @ 3.50GHz',
+  'version': '2.1.0-dev.7.1.flutter-45f9462398 (Fri Oct 19 19:27:56 2018 +0000) on "linux_x64"',
+  '_profilerMode': 'Dart',
+  '_nativeZoneMemoryUsage': 0,
+  'pid': 103707,
+  'startTime': 1540426121876,
+  '_embedder': 'Flutter',
+  '_maxRSS': 312614912,
+  '_currentRSS': 33091584,
+  'isolates': <dynamic>[
+    <String, dynamic>{
+      'type': '@Isolate',
+      'fixedId': true,
+      'id': 'isolates/242098474',
+      'name': 'main.dart:main()',
+      'number': 242098474,
+    },
+  ],
+};
 
-  Function _versionFn = (dynamic _) => null;
-
-  @override
-  rpc.ErrorCallback get onUnhandledError => null;
-
-  @override
-  Future<dynamic> get done async {
-    throw 'unexpected call to done';
+final vm_service.Isolate isolate = vm_service.Isolate.parse(
+  <String, dynamic>{
+    'type': 'Isolate',
+    'fixedId': true,
+    'id': 'isolates/242098474',
+    'name': 'main.dart:main()',
+    'number': 242098474,
+    '_originNumber': 242098474,
+    'startTime': 1540488745340,
+    '_heaps': <String, dynamic>{
+      'new': <String, dynamic>{
+        'used': 0,
+        'capacity': 0,
+        'external': 0,
+        'collections': 0,
+        'time': 0.0,
+        'avgCollectionPeriodMillis': 0.0,
+      },
+      'old': <String, dynamic>{
+        'used': 0,
+        'capacity': 0,
+        'external': 0,
+        'collections': 0,
+        'time': 0.0,
+        'avgCollectionPeriodMillis': 0.0,
+      },
+    },
   }
+);
 
-  @override
-  bool get isClosed => _isClosed;
-
-  @override
-  Future<dynamic> close() async {
-    _isClosed = true;
-  }
-
-  @override
-  Future<dynamic> listen() async {
-    // this does get called
-  }
-
-  @override
-  void registerFallback(dynamic callback(rpc.Parameters parameters)) {
-    throw 'unexpected call to registerFallback';
-  }
-
-  @override
-  void registerMethod(String name, Function callback) {
-    registeredMethods.add(name);
-    if (name == 'flutterVersion') {
-      _versionFn = callback;
-    }
-  }
-
-  @override
-  void sendNotification(String method, [ dynamic parameters ]) {
-    // this does get called
-    sentNotifications.putIfAbsent(method, () => <dynamic>[]).add(parameters);
-  }
-
-  Map<String, List<dynamic>> sentNotifications = <String, List<dynamic>>{};
-  List<String> registeredMethods = <String>[];
-
-  bool isolatesEnabled = false;
-  bool _isClosed = false;
-
-  Future<void> _getVMLatch;
-  Completer<void> _currentGetVMLatchCompleter;
-
-  void tripGetVMLatch() {
-    final Completer<void> lastCompleter = _currentGetVMLatchCompleter;
-    _currentGetVMLatchCompleter = Completer<void>();
-    _getVMLatch = _currentGetVMLatchCompleter.future;
-    lastCompleter?.complete();
-  }
-
-  int returnedFromSendRequest = 0;
-
-  @override
-  Future<dynamic> sendRequest(String method, [ dynamic parameters ]) async {
-    if (method == 'getVM') {
-      await _getVMLatch;
-    }
-    await Future<void>.delayed(Duration.zero);
-    returnedFromSendRequest += 1;
-    if (method == 'getVM') {
-      return <String, dynamic>{
-        'type': 'VM',
-        'name': 'vm',
-        'architectureBits': 64,
-        'targetCPU': 'x64',
-        'hostCPU': '      Intel(R) Xeon(R) CPU    E5-1650 v2 @ 3.50GHz',
-        'version': '2.1.0-dev.7.1.flutter-45f9462398 (Fri Oct 19 19:27:56 2018 +0000) on "linux_x64"',
-        '_profilerMode': 'Dart',
-        '_nativeZoneMemoryUsage': 0,
-        'pid': 103707,
-        'startTime': 1540426121876,
-        '_embedder': 'Flutter',
-        '_maxRSS': 312614912,
-        '_currentRSS': 33091584,
-        'isolates': isolatesEnabled ? <dynamic>[
-          <String, dynamic>{
-            'type': '@Isolate',
-            'fixedId': true,
-            'id': 'isolates/242098474',
-            'name': 'main.dart:main()',
-            'number': 242098474,
-          },
-        ] : <dynamic>[],
-      };
-    }
-    if (method == 'getIsolate') {
-      return <String, dynamic>{
-        'type': 'Isolate',
+final Map<String, Object> listViews = <String, dynamic>{
+  'type': 'FlutterViewList',
+  'views': <dynamic>[
+    <String, dynamic>{
+      'type': 'FlutterView',
+      'id': '_flutterView/0x4a4c1f8',
+      'isolate': <String, dynamic>{
+        'type': '@Isolate',
         'fixedId': true,
         'id': 'isolates/242098474',
         'name': 'main.dart:main()',
         'number': 242098474,
-        '_originNumber': 242098474,
-        'startTime': 1540488745340,
-        '_heaps': <String, dynamic>{
-          'new': <String, dynamic>{
-            'used': 0,
-            'capacity': 0,
-            'external': 0,
-            'collections': 0,
-            'time': 0.0,
-            'avgCollectionPeriodMillis': 0.0,
-          },
-          'old': <String, dynamic>{
-            'used': 0,
-            'capacity': 0,
-            'external': 0,
-            'collections': 0,
-            'time': 0.0,
-            'avgCollectionPeriodMillis': 0.0,
-          },
-        },
-      };
-    }
-    if (method == '_flutter.listViews') {
-      return <String, dynamic>{
-        'type': 'FlutterViewList',
-        'views': isolatesEnabled ? <dynamic>[
-          <String, dynamic>{
-            'type': 'FlutterView',
-            'id': '_flutterView/0x4a4c1f8',
-            'isolate': <String, dynamic>{
-              'type': '@Isolate',
-              'fixedId': true,
-              'id': 'isolates/242098474',
-              'name': 'main.dart:main()',
-              'number': 242098474,
-            },
-          },
-        ] : <dynamic>[],
-      };
-    }
-    if (method == 'flutterVersion') {
-      return _versionFn(parameters);
-    }
-    return null;
-  }
+      },
+    },
+  ]
+};
 
-  @override
-  dynamic withBatch(dynamic callback()) {
-    throw 'unexpected call to withBatch';
-  }
-}
+typedef ServiceCallback = Future<Map<String, dynamic>> Function(Map<String, Object>);
 
 void main() {
-  MockStdio mockStdio;
-  final MockFlutterVersion mockVersion = MockFlutterVersion();
-  group('VMService', () {
+  testUsingContext('VMService can refreshViews', () async {
+    final MockVMService mockVmService = MockVMService();
+    final VMService vmService = VMService(
+      null,
+      null,
+      null,
+      null,
+      null,
+      null,
+      null,
+      mockVmService,
+      Completer<void>(),
+      const Stream<dynamic>.empty(),
+    );
 
-    setUp(() {
-      mockStdio = MockStdio();
+    verify(mockVmService.registerService('flutterVersion', 'Flutter Tools')).called(1);
+
+    when(mockVmService.callServiceExtension('getVM',
+      args: anyNamed('args'), // Empty
+      isolateId: null
+    )).thenAnswer((Invocation invocation) async {
+      return vm_service.Response.parse(vm);
     });
+    await vmService.getVMOld();
 
-    testUsingContext('fails connection eagerly in the connect() method', () async {
-      FakeAsync().run((FakeAsync time) {
-        bool failed = false;
-        final Future<VMService> future = VMService.connect(Uri.parse('http://host.invalid:9999/'));
-        future.whenComplete(() {
-          failed = true;
-        });
-        time.elapse(const Duration(seconds: 5));
-        expect(failed, isFalse);
-        expect(mockStdio.writtenToStdout.join(''), '');
-        expect(mockStdio.writtenToStderr.join(''), '');
-        time.elapse(const Duration(seconds: 5));
-        expect(failed, isFalse);
-        expect(mockStdio.writtenToStdout.join(''), 'This is taking longer than expected...\n');
-        expect(mockStdio.writtenToStderr.join(''), '');
-      });
-    }, overrides: <Type, Generator>{
-      Logger: () => StdoutLogger(
-        outputPreferences: OutputPreferences.test(),
-        stdio: mockStdio,
-        terminal: AnsiTerminal(
-          stdio: mockStdio,
-          platform: const LocalPlatform(),
-        ),
-        timeoutConfiguration: const TimeoutConfiguration(),
-      ),
-      WebSocketConnector: () => (String url, {CompressionOptions compression}) async => throw const SocketException('test'),
+
+    when(mockVmService.callServiceExtension('_flutter.listViews',
+      args: anyNamed('args'),
+      isolateId: anyNamed('isolateId')
+    )).thenAnswer((Invocation invocation) async {
+      return vm_service.Response.parse(listViews);
     });
+    await vmService.refreshViews(waitForViews: true);
 
-    testUsingContext('closing VMService closes Peer', () async {
-      final MockPeer mockPeer = MockPeer();
-      final VMService vmService = VMService(mockPeer, null, null, null, null, null, MockDevice(), null);
-      expect(mockPeer.isClosed, equals(false));
-      await vmService.close();
-      expect(mockPeer.isClosed, equals(true));
-    });
+    expect(vmService.vm.name, 'vm');
+    expect(vmService.vm.views.single.id, '_flutterView/0x4a4c1f8');
+  }, overrides: <Type, Generator>{
+    Logger: () => BufferLogger.test()
+  });
 
-    testUsingContext('refreshViews', () {
-      FakeAsync().run((FakeAsync time) {
-        bool done = false;
-        final MockPeer mockPeer = MockPeer();
-        expect(mockPeer.returnedFromSendRequest, 0);
-        final VMService vmService = VMService(mockPeer, null, null, null, null, null, null, null);
-        expect(mockPeer.sentNotifications, contains('registerService'));
-        final List<String> registeredServices =
-          mockPeer.sentNotifications['registerService']
-            .map((dynamic service) => (service as Map<String, String>)['service'])
-            .toList();
-        expect(registeredServices, contains('flutterVersion'));
-        vmService.getVM().then((void value) { done = true; });
-        expect(done, isFalse);
-        expect(mockPeer.returnedFromSendRequest, 0);
-        time.elapse(Duration.zero);
-        expect(done, isTrue);
-        expect(mockPeer.returnedFromSendRequest, 1);
+  testUsingContext('VmService registers reloadSources', () {
+    Future<void> reloadSources(String isolateId, { bool pause, bool force}) async {}
+    final MockVMService mockVMService = MockVMService();
+    VMService(
+      null,
+      null,
+      reloadSources,
+      null,
+      null,
+      null,
+      null,
+      mockVMService,
+      Completer<void>(),
+      const Stream<dynamic>.empty(),
+    );
 
-        done = false;
-        mockPeer.tripGetVMLatch(); // this blocks the upcoming getVM call
-        final Future<void> ready = vmService.refreshViews(waitForViews: true);
-        ready.then((void value) { done = true; });
-        expect(mockPeer.returnedFromSendRequest, 1);
-        time.elapse(Duration.zero); // this unblocks the listViews call which returns nothing
-        expect(mockPeer.returnedFromSendRequest, 2);
-        time.elapse(const Duration(milliseconds: 50)); // the last listViews had no views, so it waits 50ms, then calls getVM
-        expect(done, isFalse);
-        expect(mockPeer.returnedFromSendRequest, 2);
-        mockPeer.tripGetVMLatch(); // this unblocks the getVM call
-        expect(mockPeer.returnedFromSendRequest, 2);
-        time.elapse(Duration.zero); // here getVM returns with no isolates and listViews returns no views
-        expect(mockPeer.returnedFromSendRequest, 4);
-        time.elapse(const Duration(milliseconds: 50)); // so refreshViews waits another 50ms
-        expect(done, isFalse);
-        expect(mockPeer.returnedFromSendRequest, 4);
-        mockPeer.tripGetVMLatch(); // this unblocks the getVM call
-        expect(mockPeer.returnedFromSendRequest, 4);
-        time.elapse(Duration.zero); // here getVM returns with no isolates and listViews returns no views
-        expect(mockPeer.returnedFromSendRequest, 6);
-        time.elapse(const Duration(milliseconds: 50)); // so refreshViews waits another 50ms
-        expect(done, isFalse);
-        expect(mockPeer.returnedFromSendRequest, 6);
-        mockPeer.tripGetVMLatch(); // this unblocks the getVM call
-        expect(mockPeer.returnedFromSendRequest, 6);
-        time.elapse(Duration.zero); // here getVM returns with no isolates and listViews returns no views
-        expect(mockPeer.returnedFromSendRequest, 8);
-        time.elapse(const Duration(milliseconds: 50)); // so refreshViews waits another 50ms
-        expect(done, isFalse);
-        expect(mockPeer.returnedFromSendRequest, 8);
-        mockPeer.tripGetVMLatch(); // this unblocks the getVM call
-        expect(mockPeer.returnedFromSendRequest, 8);
-        time.elapse(Duration.zero); // here getVM returns with no isolates and listViews returns no views
-        expect(mockPeer.returnedFromSendRequest, 10);
-        const String message = 'Flutter is taking longer than expected to report its views. Still trying...\n';
-        expect(mockStdio.writtenToStdout.join(''), message);
-        expect(mockStdio.writtenToStderr.join(''), '');
-        time.elapse(const Duration(milliseconds: 50)); // so refreshViews waits another 50ms
-        expect(done, isFalse);
-        expect(mockPeer.returnedFromSendRequest, 10);
-        mockPeer.isolatesEnabled = true;
-        mockPeer.tripGetVMLatch(); // this unblocks the getVM call
-        expect(mockPeer.returnedFromSendRequest, 10);
-        time.elapse(Duration.zero); // now it returns an isolate and the listViews call returns views
-        expect(mockPeer.returnedFromSendRequest, 13);
-        expect(done, isTrue);
-        expect(mockStdio.writtenToStdout.join(''), message);
-        expect(mockStdio.writtenToStderr.join(''), '');
-      });
-    }, overrides: <Type, Generator>{
-      Logger: () => StdoutLogger(
-        outputPreferences: globals.outputPreferences,
-        terminal: AnsiTerminal(
-          stdio: mockStdio,
-          platform: const LocalPlatform(),
-        ),
-        stdio: mockStdio,
-        timeoutConfiguration: const TimeoutConfiguration(),
-      ),
-    });
+    verify(mockVMService.registerService('reloadSources', 'Flutter Tools')).called(1);
+  }, overrides: <Type, Generator>{
+    Logger: () => BufferLogger.test()
+  });
 
-    testUsingContext('registers hot UI method', () {
-      FakeAsync().run((FakeAsync time) {
-        final MockPeer mockPeer = MockPeer();
-        Future<void> reloadMethod({ String classId, String libraryId }) async {}
-        VMService(mockPeer, null, null, null, null, null, null, reloadMethod);
+  testUsingContext('VmService registers reloadMethod', () {
+    Future<void> reloadMethod({  String classId, String libraryId,}) async {}
+    final MockVMService mockVMService = MockVMService();
+    VMService(
+      null,
+      null,
+      null,
+      null,
+      null,
+      null,
+      reloadMethod,
+      mockVMService,
+      Completer<void>(),
+      const Stream<dynamic>.empty(),
+    );
 
-        expect(mockPeer.registeredMethods, contains('reloadMethod'));
-      });
-    }, overrides: <Type, Generator>{
-      Logger: () => StdoutLogger(
-        outputPreferences: globals.outputPreferences,
-        terminal: AnsiTerminal(
-          stdio: mockStdio,
-          platform: const LocalPlatform(),
-        ),
-        stdio: mockStdio,
-        timeoutConfiguration: const TimeoutConfiguration(),
-      ),
-    });
+    verify(mockVMService.registerService('reloadMethod', 'Flutter Tools')).called(1);
+  }, overrides: <Type, Generator>{
+    Logger: () => BufferLogger.test()
+  });
 
-    testUsingContext('registers flutterMemoryInfo service', () {
-      FakeAsync().run((FakeAsync time) {
-        final MockDevice mockDevice = MockDevice();
-        final MockPeer mockPeer = MockPeer();
-        Future<void> reloadSources(String isolateId, { bool pause, bool force}) async {}
-        VMService(mockPeer, null, null, reloadSources, null, null, mockDevice, null);
+  testUsingContext('VmService registers flutterMemoryInfo service', () {
+    final MockDevice mockDevice = MockDevice();
+    final MockVMService mockVMService = MockVMService();
+    VMService(
+      null,
+      null,
+      null,
+      null,
+      null,
+      mockDevice,
+      null,
+      mockVMService,
+      Completer<void>(),
+      const Stream<dynamic>.empty(),
+    );
 
-        expect(mockPeer.registeredMethods, contains('flutterMemoryInfo'));
-      });
-    }, overrides: <Type, Generator>{
-      Logger: () => StdoutLogger(
-        outputPreferences: globals.outputPreferences,
-        terminal: AnsiTerminal(
-          stdio: mockStdio,
-          platform: const LocalPlatform(),
-        ),
-        stdio: mockStdio,
-        timeoutConfiguration: const TimeoutConfiguration(),
-      ),
-    });
+    verify(mockVMService.registerService('flutterMemoryInfo', 'Flutter Tools')).called(1);
+  }, overrides: <Type, Generator>{
+    Logger: () => BufferLogger.test()
+  });
 
-    testUsingContext('returns correct FlutterVersion', () {
-      FakeAsync().run((FakeAsync time) async {
-        final MockPeer mockPeer = MockPeer();
-        VMService(mockPeer, null, null, null, null, null, MockDevice(), null);
+  testUsingContext('VMService returns correct FlutterVersion', () async {
+    final MockVMService mockVMService = MockVMService();
+    VMService(
+      null,
+      null,
+      null,
+      null,
+      null,
+      null,
+      null,
+      mockVMService,
+      Completer<void>(),
+      const Stream<dynamic>.empty(),
+    );
 
-        expect(mockPeer.registeredMethods, contains('flutterVersion'));
-        expect(await mockPeer.sendRequest('flutterVersion'), equals(mockVersion.toJson()));
-      });
-    }, overrides: <Type, Generator>{
-      FlutterVersion: () => mockVersion,
-    });
+    verify(mockVMService.registerService('flutterVersion', 'Flutter Tools')).called(1);
+  }, overrides: <Type, Generator>{
+    FlutterVersion: () => MockFlutterVersion(),
   });
 }
 
 class MockDevice extends Mock implements Device {}
-
+class MockVMService extends Mock implements vm_service.VmService {}
 class MockFlutterVersion extends Mock implements FlutterVersion {
   @override
   Map<String, Object> toJson() => const <String, Object>{'Mock': 'Version'};