Switch many `Device` methods to be async (#9587)

`adb` can sometimes hang, which will in turn hang the Dart isolate if
we're using `Process.runSync()`. This changes many of the `Device` methods
to return `Future<T>` in order to allow them to use the async process
methods. A future change will add timeouts to the associated calls so
that we can properly alert the user to the hung `adb` process.

This is work towards #7102, #9567
diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart
index c83ad4a..0d3044c 100644
--- a/packages/flutter_tools/lib/src/android/android_device.dart
+++ b/packages/flutter_tools/lib/src/android/android_device.dart
@@ -51,7 +51,7 @@
   bool _isLocalEmulator;
   TargetPlatform _platform;
 
-  String _getProperty(String name) {
+  Future<String> _getProperty(String name) async {
     if (_properties == null) {
       _properties = <String, String>{};
 
@@ -79,19 +79,19 @@
   }
 
   @override
-  bool get isLocalEmulator {
+  Future<bool> get isLocalEmulator async {
     if (_isLocalEmulator == null) {
-      final String characteristics = _getProperty('ro.build.characteristics');
+      final String characteristics = await _getProperty('ro.build.characteristics');
       _isLocalEmulator = characteristics != null && characteristics.contains('emulator');
     }
     return _isLocalEmulator;
   }
 
   @override
-  TargetPlatform get targetPlatform {
+  Future<TargetPlatform> get targetPlatform async {
     if (_platform == null) {
       // http://developer.android.com/ndk/guides/abis.html (x86, armeabi-v7a, ...)
-      switch (_getProperty('ro.product.cpu.abi')) {
+      switch (await _getProperty('ro.product.cpu.abi')) {
         case 'x86_64':
           _platform = TargetPlatform.android_x64;
           break;
@@ -108,11 +108,12 @@
   }
 
   @override
-  String get sdkNameAndVersion => 'Android $_sdkVersion (API $_apiVersion)';
+  Future<String> get sdkNameAndVersion async =>
+      'Android ${await _sdkVersion} (API ${await _apiVersion})';
 
-  String get _sdkVersion => _getProperty('ro.build.version.release');
+  Future<String> get _sdkVersion => _getProperty('ro.build.version.release');
 
-  String get _apiVersion => _getProperty('ro.build.version.sdk');
+  Future<String> get _apiVersion => _getProperty('ro.build.version.sdk');
 
   _AdbLogReader _logReader;
   _AndroidDevicePortForwarder _portForwarder;
@@ -160,16 +161,16 @@
     return false;
   }
 
-  bool _checkForSupportedAndroidVersion() {
+  Future<bool> _checkForSupportedAndroidVersion() async {
     try {
       // If the server is automatically restarted, then we get irrelevant
       // output lines like this, which we want to ignore:
       //   adb server is out of date.  killing..
       //   * daemon started successfully *
-      runCheckedSync(<String>[getAdbPath(androidSdk), 'start-server']);
+      await runCheckedAsync(<String>[getAdbPath(androidSdk), 'start-server']);
 
       // Sample output: '22'
-      final String sdkVersion = _getProperty('ro.build.version.sdk');
+      final String sdkVersion = await _getProperty('ro.build.version.sdk');
 
       final int sdkVersionParsed = int.parse(sdkVersion, onError: (String source) => null);
       if (sdkVersionParsed == null) {
@@ -195,8 +196,9 @@
     return '/data/local/tmp/sky.${app.id}.sha1';
   }
 
-  String _getDeviceApkSha1(ApplicationPackage app) {
-    return runSync(adbCommandForDevice(<String>['shell', 'cat', _getDeviceSha1Path(app)]));
+  Future<String> _getDeviceApkSha1(ApplicationPackage app) async {
+    final RunResult result = await runAsync(adbCommandForDevice(<String>['shell', 'cat', _getDeviceSha1Path(app)]));
+    return result.stdout;
   }
 
   String _getSourceSha1(ApplicationPackage app) {
@@ -209,15 +211,15 @@
   String get name => modelID;
 
   @override
-  bool isAppInstalled(ApplicationPackage app) {
+  Future<bool> isAppInstalled(ApplicationPackage app) async {
     // This call takes 400ms - 600ms.
-    final String listOut = runCheckedSync(adbCommandForDevice(<String>['shell', 'pm', 'list', 'packages', app.id]));
-    return LineSplitter.split(listOut).contains("package:${app.id}");
+    final RunResult listOut = await runCheckedAsync(adbCommandForDevice(<String>['shell', 'pm', 'list', 'packages', app.id]));
+    return LineSplitter.split(listOut.stdout).contains("package:${app.id}");
   }
 
   @override
-  bool isLatestBuildInstalled(ApplicationPackage app) {
-    final String installedSha1 = _getDeviceApkSha1(app);
+  Future<bool> isLatestBuildInstalled(ApplicationPackage app) async {
+    final String installedSha1 = await _getDeviceApkSha1(app);
     return installedSha1.isNotEmpty && installedSha1 == _getSourceSha1(app);
   }
 
@@ -229,7 +231,7 @@
       return false;
     }
 
-    if (!_checkForSupportedAdbVersion() || !_checkForSupportedAndroidVersion())
+    if (!_checkForSupportedAdbVersion() || !await _checkForSupportedAndroidVersion())
       return false;
 
     final Status status = logger.startProgress('Installing ${apk.apkPath}...', expectSlowOperation: true);
@@ -249,11 +251,11 @@
   }
 
   @override
-  bool uninstallApp(ApplicationPackage app) {
-    if (!_checkForSupportedAdbVersion() || !_checkForSupportedAndroidVersion())
+  Future<bool> uninstallApp(ApplicationPackage app) async {
+    if (!_checkForSupportedAdbVersion() || !await _checkForSupportedAndroidVersion())
       return false;
 
-    final String uninstallOut = runCheckedSync(adbCommandForDevice(<String>['uninstall', app.id]));
+    final String uninstallOut = (await runCheckedAsync(adbCommandForDevice(<String>['uninstall', app.id]))).stdout;
     final RegExp failureExp = new RegExp(r'^Failure.*$', multiLine: true);
     final String failure = failureExp.stringMatch(uninstallOut);
     if (failure != null) {
@@ -265,9 +267,9 @@
   }
 
   Future<bool> _installLatestApp(ApplicationPackage package) async {
-    final bool wasInstalled = isAppInstalled(package);
+    final bool wasInstalled = await isAppInstalled(package);
     if (wasInstalled) {
-      if (isLatestBuildInstalled(package)) {
+      if (await isLatestBuildInstalled(package)) {
         printTrace('Latest build already installed.');
         return true;
       }
@@ -277,7 +279,7 @@
       printTrace('Warning: Failed to install APK.');
       if (wasInstalled) {
         printStatus('Uninstalling old version...');
-        if (!uninstallApp(package)) {
+        if (!await uninstallApp(package)) {
           printError('Error: Uninstalling old version failed.');
           return false;
         }
@@ -304,10 +306,10 @@
     bool prebuiltApplication: false,
     bool applicationNeedsRebuild: false,
   }) async {
-    if (!_checkForSupportedAdbVersion() || !_checkForSupportedAndroidVersion())
+    if (!_checkForSupportedAdbVersion() || !await _checkForSupportedAndroidVersion())
       return new LaunchResult.failed();
 
-    if (targetPlatform != TargetPlatform.android_arm && mode != BuildMode.debug) {
+    if (await targetPlatform != TargetPlatform.android_arm && mode != BuildMode.debug) {
       printError('Profile and release builds are only supported on ARM targets.');
       return new LaunchResult.failed();
     }
@@ -369,7 +371,7 @@
         cmd.addAll(<String>['--ez', 'use-test-fonts', 'true']);
     }
     cmd.add(apk.launchActivity);
-    final String result = runCheckedSync(cmd);
+    final String result = (await runCheckedAsync(cmd)).stdout;
     // This invocation returns 0 even when it fails.
     if (result.contains('Error: ')) {
       printError(result.trim());
@@ -455,16 +457,15 @@
   bool get supportsScreenshot => true;
 
   @override
-  Future<Null> takeScreenshot(File outputFile) {
+  Future<Null> takeScreenshot(File outputFile) async {
     const String remotePath = '/data/local/tmp/flutter_screenshot.png';
-    runCheckedSync(adbCommandForDevice(<String>['shell', 'screencap', '-p', remotePath]));
-    runCheckedSync(adbCommandForDevice(<String>['pull', remotePath, outputFile.path]));
-    runCheckedSync(adbCommandForDevice(<String>['shell', 'rm', remotePath]));
-    return new Future<Null>.value();
+    await runCheckedAsync(adbCommandForDevice(<String>['shell', 'screencap', '-p', remotePath]));
+    await runCheckedAsync(adbCommandForDevice(<String>['pull', remotePath, outputFile.path]));
+    await runCheckedAsync(adbCommandForDevice(<String>['shell', 'rm', remotePath]));
   }
 
   @override
-  Future<List<DiscoveredApp>> discoverApps() {
+  Future<List<DiscoveredApp>> discoverApps() async {
     final RegExp discoverExp = new RegExp(r'DISCOVER: (.*)');
     final List<DiscoveredApp> result = <DiscoveredApp>[];
     final StreamSubscription<String> logs = getLogReader().logLines.listen((String line) {
@@ -475,14 +476,13 @@
       }
     });
 
-    runCheckedSync(adbCommandForDevice(<String>[
+    await runCheckedAsync(adbCommandForDevice(<String>[
       'shell', 'am', 'broadcast', '-a', 'io.flutter.view.DISCOVER'
     ]));
 
-    return new Future<List<DiscoveredApp>>.delayed(const Duration(seconds: 1), () {
-      logs.cancel();
-      return result;
-    });
+    await new Future<Null>.delayed(const Duration(seconds: 1));
+    logs.cancel();
+    return result;
   }
 }
 
@@ -744,7 +744,7 @@
       hostPort = await portScanner.findAvailablePort();
     }
 
-    runCheckedSync(device.adbCommandForDevice(
+    await runCheckedAsync(device.adbCommandForDevice(
       <String>['forward', 'tcp:$hostPort', 'tcp:$devicePort']
     ));
 
@@ -753,7 +753,7 @@
 
   @override
   Future<Null> unforward(ForwardedPort forwardedPort) async {
-    runCheckedSync(device.adbCommandForDevice(
+    await runCheckedAsync(device.adbCommandForDevice(
       <String>['forward', '--remove', 'tcp:${forwardedPort.hostPort}']
     ));
   }
diff --git a/packages/flutter_tools/lib/src/base/process.dart b/packages/flutter_tools/lib/src/base/process.dart
index 8ded4f6..7b589c4 100644
--- a/packages/flutter_tools/lib/src/base/process.dart
+++ b/packages/flutter_tools/lib/src/base/process.dart
@@ -221,6 +221,15 @@
   }
 }
 
+Future<bool> exitsHappyAsync(List<String> cli) async {
+  _traceCommand(cli);
+  try {
+    return (await processManager.run(cli)).exitCode == 0;
+  } catch (error) {
+    return false;
+  }
+}
+
 /// Run cmd and return stdout.
 ///
 /// Throws an error if cmd exits with a non-zero value.
@@ -241,16 +250,6 @@
   );
 }
 
-/// Run cmd and return stdout on success.
-///
-/// Throws the standard error output if cmd exits with a non-zero value.
-String runSyncAndThrowStdErrOnError(List<String> cmd) {
-  return _runWithLoggingSync(cmd,
-                             checked: true,
-                             throwStandardErrorOnError: true,
-                             hideStdout: true);
-}
-
 /// Run cmd and return stdout.
 String runSync(List<String> cmd, {
   String workingDirectory,
diff --git a/packages/flutter_tools/lib/src/commands/build_aot.dart b/packages/flutter_tools/lib/src/commands/build_aot.dart
index de0dce6..10f79bb 100644
--- a/packages/flutter_tools/lib/src/commands/build_aot.dart
+++ b/packages/flutter_tools/lib/src/commands/build_aot.dart
@@ -273,24 +273,24 @@
     final List<String> commonBuildOptions = <String>['-arch', 'arm64', '-miphoneos-version-min=8.0'];
 
     if (interpreter) {
-      runCheckedSync(<String>['mv', vmSnapshotData, fs.path.join(outputDir.path, kVmSnapshotData)]);
-      runCheckedSync(<String>['mv', isolateSnapshotData, fs.path.join(outputDir.path, kIsolateSnapshotData)]);
+      await runCheckedAsync(<String>['mv', vmSnapshotData, fs.path.join(outputDir.path, kVmSnapshotData)]);
+      await runCheckedAsync(<String>['mv', isolateSnapshotData, fs.path.join(outputDir.path, kIsolateSnapshotData)]);
 
-      runCheckedSync(<String>[
+      await runCheckedAsync(<String>[
         'xxd', '--include', kVmSnapshotData, fs.path.basename(kVmSnapshotDataC)
       ], workingDirectory: outputDir.path);
-      runCheckedSync(<String>[
+      await runCheckedAsync(<String>[
         'xxd', '--include', kIsolateSnapshotData, fs.path.basename(kIsolateSnapshotDataC)
       ], workingDirectory: outputDir.path);
 
-      runCheckedSync(<String>['xcrun', 'cc']
+      await runCheckedAsync(<String>['xcrun', 'cc']
         ..addAll(commonBuildOptions)
         ..addAll(<String>['-c', kVmSnapshotDataC, '-o', kVmSnapshotDataO]));
-      runCheckedSync(<String>['xcrun', 'cc']
+      await runCheckedAsync(<String>['xcrun', 'cc']
         ..addAll(commonBuildOptions)
         ..addAll(<String>['-c', kIsolateSnapshotDataC, '-o', kIsolateSnapshotDataO]));
     } else {
-      runCheckedSync(<String>['xcrun', 'cc']
+      await runCheckedAsync(<String>['xcrun', 'cc']
         ..addAll(commonBuildOptions)
         ..addAll(<String>['-c', assembly, '-o', assemblyO]));
     }
@@ -313,7 +313,7 @@
     } else {
       linkCommand.add(assemblyO);
     }
-    runCheckedSync(linkCommand);
+    await runCheckedAsync(linkCommand);
   }
 
   return outputPath;
diff --git a/packages/flutter_tools/lib/src/commands/config.dart b/packages/flutter_tools/lib/src/commands/config.dart
index 59484e5..11f7e07 100644
--- a/packages/flutter_tools/lib/src/commands/config.dart
+++ b/packages/flutter_tools/lib/src/commands/config.dart
@@ -44,7 +44,7 @@
 
   /// Return `null` to disable tracking of the `config` command.
   @override
-  String get usagePath => null;
+  Future<String> get usagePath => null;
 
   @override
   Future<Null> runCommand() async {
diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart
index 50483fe..951a044 100644
--- a/packages/flutter_tools/lib/src/commands/daemon.dart
+++ b/packages/flutter_tools/lib/src/commands/daemon.dart
@@ -346,7 +346,7 @@
     String packagesFilePath,
     String projectAssets,
   }) async {
-    if (device.isLocalEmulator && !isEmulatorBuildMode(options.buildMode))
+    if (await device.isLocalEmulator && !isEmulatorBuildMode(options.buildMode))
       throw '${toTitleCase(getModeName(options.buildMode))} mode is not supported for emulators.';
 
     // We change the current working directory for the duration of the `start` command.
@@ -381,7 +381,7 @@
     _sendAppEvent(app, 'start', <String, dynamic>{
       'deviceId': device.id,
       'directory': projectDirectory,
-      'supportsRestart': isRestartSupported(enableHotReload, device)
+      'supportsRestart': isRestartSupported(enableHotReload, device),
     });
 
     Completer<DebugConnectionInfo> connectionInfoCompleter;
@@ -505,6 +505,8 @@
   }
 }
 
+typedef void _DeviceEventHandler(Device device);
+
 /// This domain lets callers list and monitor connected devices.
 ///
 /// It exports a `getDevices()` call, as well as firing `device.added` and
@@ -530,15 +532,20 @@
       _discoverers.add(deviceDiscovery);
 
     for (PollingDeviceDiscovery discoverer in _discoverers) {
-      discoverer.onAdded.listen((Device device) {
-        sendEvent('device.added', _deviceToMap(device));
-      });
-      discoverer.onRemoved.listen((Device device) {
-        sendEvent('device.removed', _deviceToMap(device));
-      });
+      discoverer.onAdded.listen(_onDeviceEvent('device.added'));
+      discoverer.onRemoved.listen(_onDeviceEvent('device.removed'));
     }
   }
 
+  Future<Null> _deviceEvents = new Future<Null>.value();
+  _DeviceEventHandler _onDeviceEvent(String eventName) {
+    return (Device device) {
+      _deviceEvents = _deviceEvents.then((_) async {
+        sendEvent(eventName, await _deviceToMap(device));
+      });
+    };
+  }
+
   final List<PollingDeviceDiscovery> _discoverers = <PollingDeviceDiscovery>[];
 
   Future<List<Device>> getDevices([Map<String, dynamic> args]) {
@@ -638,18 +645,16 @@
 }
 
 dynamic _jsonEncodeObject(dynamic object) {
-  if (object is Device)
-    return _deviceToMap(object);
   if (object is OperationResult)
     return _operationResultToMap(object);
   return object;
 }
 
-Map<String, dynamic> _deviceToMap(Device device) {
+Future<Map<String, dynamic>> _deviceToMap(Device device) async {
   return <String, dynamic>{
     'id': device.id,
     'name': device.name,
-    'platform': getNameForTargetPlatform(device.targetPlatform),
+    'platform': getNameForTargetPlatform(await device.targetPlatform),
     'emulator': device.isLocalEmulator
   };
 }
@@ -664,8 +669,6 @@
 dynamic _toJsonable(dynamic obj) {
   if (obj is String || obj is int || obj is bool || obj is Map<dynamic, dynamic> || obj is List<dynamic> || obj == null)
     return obj;
-  if (obj is Device)
-    return obj;
   if (obj is OperationResult)
     return obj;
   return '$obj';
diff --git a/packages/flutter_tools/lib/src/commands/devices.dart b/packages/flutter_tools/lib/src/commands/devices.dart
index 2982216..51d7aed 100644
--- a/packages/flutter_tools/lib/src/commands/devices.dart
+++ b/packages/flutter_tools/lib/src/commands/devices.dart
@@ -27,7 +27,7 @@
         exitCode: 1);
     }
 
-    final List<Device> devices = await deviceManager.getAllConnectedDevices();
+    final List<Device> devices = await deviceManager.getAllConnectedDevices().toList();
 
     if (devices.isEmpty) {
       printStatus(
@@ -36,7 +36,7 @@
         'potential issues, or visit https://flutter.io/setup/ for troubleshooting tips.');
     } else {
       printStatus('${devices.length} connected ${pluralize('device', devices.length)}:\n');
-      Device.printDevices(devices);
+      await Device.printDevices(devices);
     }
   }
 }
diff --git a/packages/flutter_tools/lib/src/commands/drive.dart b/packages/flutter_tools/lib/src/commands/drive.dart
index 4e7d2bd..bdc0b9f 100644
--- a/packages/flutter_tools/lib/src/commands/drive.dart
+++ b/packages/flutter_tools/lib/src/commands/drive.dart
@@ -183,7 +183,7 @@
 }
 
 Future<Device> findTargetDevice() async {
-  final List<Device> devices = await deviceManager.getDevices();
+  final List<Device> devices = await deviceManager.getDevices().toList();
 
   if (deviceManager.hasSpecifiedDeviceId) {
     if (devices.isEmpty) {
@@ -192,7 +192,7 @@
     }
     if (devices.length > 1) {
       printStatus("Found ${devices.length} devices with name or id matching '${deviceManager.specifiedDeviceId}':");
-      Device.printDevices(devices);
+      await Device.printDevices(devices);
       return null;
     }
     return devices.first;
@@ -203,16 +203,24 @@
     // On Mac we look for the iOS Simulator. If available, we use that. Then
     // we look for an Android device. If there's one, we use that. Otherwise,
     // we launch a new iOS Simulator.
-    final Device reusableDevice = devices.firstWhere(
-      (Device d) => d.isLocalEmulator,
-      orElse: () {
-        return devices.firstWhere((Device d) => d is AndroidDevice,
-            orElse: () => null);
+    Device reusableDevice;
+    for (Device device in devices) {
+      if (await device.isLocalEmulator) {
+        reusableDevice = device;
+        break;
       }
-    );
+    }
+    if (reusableDevice == null) {
+      for (Device device in devices) {
+        if (device is AndroidDevice) {
+          reusableDevice = device;
+          break;
+        }
+      }
+    }
 
     if (reusableDevice != null) {
-      printStatus('Found connected ${reusableDevice.isLocalEmulator ? "emulator" : "device"} "${reusableDevice.name}"; will reuse it.');
+      printStatus('Found connected ${await reusableDevice.isLocalEmulator ? "emulator" : "device"} "${reusableDevice.name}"; will reuse it.');
       return reusableDevice;
     }
 
@@ -262,8 +270,8 @@
 
   printTrace('Installing application package.');
   final ApplicationPackage package = command.applicationPackages
-      .getPackageForPlatform(command.device.targetPlatform);
-  if (command.device.isAppInstalled(package))
+      .getPackageForPlatform(await command.device.targetPlatform);
+  if (await command.device.isAppInstalled(package))
     command.device.uninstallApp(package);
   command.device.installApp(package);
 
@@ -335,7 +343,7 @@
 
 Future<bool> _stopApp(DriveCommand command) async {
   printTrace('Stopping application.');
-  final ApplicationPackage package = command.applicationPackages.getPackageForPlatform(command.device.targetPlatform);
+  final ApplicationPackage package = command.applicationPackages.getPackageForPlatform(await command.device.targetPlatform);
   final bool stopped = await command.device.stopApp(package);
   await command._deviceLogSubscription?.cancel();
   return stopped;
diff --git a/packages/flutter_tools/lib/src/commands/install.dart b/packages/flutter_tools/lib/src/commands/install.dart
index 063585e..3651c01 100644
--- a/packages/flutter_tools/lib/src/commands/install.dart
+++ b/packages/flutter_tools/lib/src/commands/install.dart
@@ -31,7 +31,7 @@
 
   @override
   Future<Null> runCommand() async {
-    final ApplicationPackage package = applicationPackages.getPackageForPlatform(device.targetPlatform);
+    final ApplicationPackage package = applicationPackages.getPackageForPlatform(await device.targetPlatform);
 
     Cache.releaseLockEarly();
 
@@ -46,9 +46,9 @@
   if (package == null)
     return false;
 
-  if (uninstall && device.isAppInstalled(package)) {
+  if (uninstall && await device.isAppInstalled(package)) {
     printStatus('Uninstalling old version...');
-    if (!device.uninstallApp(package))
+    if (!await device.uninstallApp(package))
       printError('Warning: uninstalling old version failed');
   }
 
diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart
index 50fe3ed..aad16e6 100644
--- a/packages/flutter_tools/lib/src/commands/run.dart
+++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -153,14 +153,14 @@
   Device device;
 
   @override
-  String get usagePath {
+  Future<String> get usagePath async {
     final String command = shouldUseHotMode() ? 'hotrun' : name;
 
     if (device == null)
       return command;
 
     // Return 'run/ios'.
-    return '$command/${getNameForTargetPlatform(device.targetPlatform)}';
+    return '$command/${getNameForTargetPlatform(await device.targetPlatform)}';
   }
 
   @override
@@ -249,7 +249,7 @@
       return null;
     }
 
-    if (device.isLocalEmulator && !isEmulatorBuildMode(getBuildMode()))
+    if (await device.isLocalEmulator && !isEmulatorBuildMode(getBuildMode()))
       throwToolExit('${toTitleCase(getModeName(getBuildMode()))} mode is not supported for emulators.');
 
     if (hotMode) {
diff --git a/packages/flutter_tools/lib/src/commands/stop.dart b/packages/flutter_tools/lib/src/commands/stop.dart
index c506ee2..836538f 100644
--- a/packages/flutter_tools/lib/src/commands/stop.dart
+++ b/packages/flutter_tools/lib/src/commands/stop.dart
@@ -31,9 +31,10 @@
 
   @override
   Future<Null> runCommand() async {
-    final ApplicationPackage app = applicationPackages.getPackageForPlatform(device.targetPlatform);
+    final TargetPlatform targetPlatform = await device.targetPlatform;
+    final ApplicationPackage app = applicationPackages.getPackageForPlatform(targetPlatform);
     if (app == null) {
-      final String platformName = getNameForTargetPlatform(device.targetPlatform);
+      final String platformName = getNameForTargetPlatform(targetPlatform);
       throwToolExit('No Flutter application for $platformName found in the current directory.');
     }
     printStatus('Stopping apps on ${device.name}.');
diff --git a/packages/flutter_tools/lib/src/commands/upgrade.dart b/packages/flutter_tools/lib/src/commands/upgrade.dart
index d86864c..7722dc8 100644
--- a/packages/flutter_tools/lib/src/commands/upgrade.dart
+++ b/packages/flutter_tools/lib/src/commands/upgrade.dart
@@ -28,7 +28,7 @@
   @override
   Future<Null> runCommand() async {
     try {
-      runCheckedSync(<String>[
+      await runCheckedAsync(<String>[
         'git', 'rev-parse', '@{u}'
       ], workingDirectory: Cache.flutterRoot);
     } catch (e) {
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index 9a76b7e..b4e0fbc 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -37,42 +37,40 @@
 
   bool get hasSpecifiedDeviceId => specifiedDeviceId != null;
 
-  /// Return the devices with a name or id matching [deviceId].
-  /// This does a case insentitive compare with [deviceId].
-  Future<List<Device>> getDevicesById(String deviceId) async {
+  Stream<Device> getDevicesById(String deviceId) async* {
+    final Stream<Device> devices = getAllConnectedDevices();
     deviceId = deviceId.toLowerCase();
-    final List<Device> devices = await getAllConnectedDevices();
-    final Device device = devices.firstWhere(
-        (Device device) =>
-            device.id.toLowerCase() == deviceId ||
-            device.name.toLowerCase() == deviceId,
-        orElse: () => null);
+    bool exactlyMatchesDeviceId(Device device) =>
+        device.id.toLowerCase() == deviceId ||
+        device.name.toLowerCase() == deviceId;
+    bool startsWithDeviceId(Device device) =>
+        device.id.toLowerCase().startsWith(deviceId) ||
+        device.name.toLowerCase().startsWith(deviceId);
 
-    if (device != null)
-      return <Device>[device];
+    final Device exactMatch = await devices.firstWhere(
+        exactlyMatchesDeviceId, defaultValue: () => null);
+    if (exactMatch != null) {
+      yield exactMatch;
+      return;
+    }
 
     // Match on a id or name starting with [deviceId].
-    return devices.where((Device device) {
-      return (device.id.toLowerCase().startsWith(deviceId) ||
-        device.name.toLowerCase().startsWith(deviceId));
-    }).toList();
+    await for (Device device in devices.where(startsWithDeviceId))
+      yield device;
   }
 
   /// Return the list of connected devices, filtered by any user-specified device id.
-  Future<List<Device>> getDevices() async {
-    if (specifiedDeviceId == null) {
-      return getAllConnectedDevices();
-    } else {
-      return getDevicesById(specifiedDeviceId);
-    }
+  Stream<Device> getDevices() {
+    return hasSpecifiedDeviceId
+        ? getDevicesById(specifiedDeviceId)
+        : getAllConnectedDevices();
   }
 
   /// Return the list of all connected devices.
-  Future<List<Device>> getAllConnectedDevices() async {
-    return _deviceDiscoverers
+  Stream<Device> getAllConnectedDevices() {
+    return new Stream<Device>.fromIterable(_deviceDiscoverers
       .where((DeviceDiscovery discoverer) => discoverer.supportsPlatform)
-      .expand((DeviceDiscovery discoverer) => discoverer.devices)
-      .toList();
+      .expand((DeviceDiscovery discoverer) => discoverer.devices));
   }
 }
 
@@ -141,19 +139,19 @@
   bool get supportsStartPaused => true;
 
   /// Whether it is an emulated device running on localhost.
-  bool get isLocalEmulator;
+  Future<bool> get isLocalEmulator;
 
   /// Check if a version of the given app is already installed
-  bool isAppInstalled(ApplicationPackage app);
+  Future<bool> isAppInstalled(ApplicationPackage app);
 
   /// Check if the latest build of the [app] is already installed.
-  bool isLatestBuildInstalled(ApplicationPackage app);
+  Future<bool> isLatestBuildInstalled(ApplicationPackage app);
 
   /// Install an app package on the current device
   Future<bool> installApp(ApplicationPackage app);
 
   /// Uninstall an app package from the current device
-  bool uninstallApp(ApplicationPackage app);
+  Future<bool> uninstallApp(ApplicationPackage app);
 
   /// Check if the device is supported by Flutter
   bool isSupported();
@@ -162,9 +160,10 @@
   // supported by Flutter, and, if not, why.
   String supportMessage() => isSupported() ? "Supported" : "Unsupported";
 
-  TargetPlatform get targetPlatform;
+  /// The device's platform.
+  Future<TargetPlatform> get targetPlatform;
 
-  String get sdkNameAndVersion;
+  Future<String> get sdkNameAndVersion;
 
   /// Get a log reader for this device.
   /// If [app] is specified, this will return a log reader specific to that
@@ -238,23 +237,24 @@
   @override
   String toString() => name;
 
-  static Iterable<String> descriptions(List<Device> devices) {
+  static Stream<String> descriptions(List<Device> devices) async* {
     if (devices.isEmpty)
-      return <String>[];
+      return;
 
     // Extract device information
     final List<List<String>> table = <List<String>>[];
     for (Device device in devices) {
       String supportIndicator = device.isSupported() ? '' : ' (unsupported)';
-      if (device.isLocalEmulator) {
-        final String type = device.targetPlatform == TargetPlatform.ios ? 'simulator' : 'emulator';
+      final TargetPlatform targetPlatform = await device.targetPlatform;
+      if (await device.isLocalEmulator) {
+        final String type = targetPlatform == TargetPlatform.ios ? 'simulator' : 'emulator';
         supportIndicator += ' ($type)';
       }
       table.add(<String>[
         device.name,
         device.id,
-        '${getNameForTargetPlatform(device.targetPlatform)}',
-        '${device.sdkNameAndVersion}$supportIndicator',
+        '${getNameForTargetPlatform(targetPlatform)}',
+        '${await device.sdkNameAndVersion}$supportIndicator',
       ]);
     }
 
@@ -266,13 +266,13 @@
     }
 
     // Join columns into lines of text
-    return table.map((List<String> row) =>
-        indices.map((int i) => row[i].padRight(widths[i])).join(' • ') +
-        ' • ${row.last}');
+    for (List<String> row in table) {
+      yield indices.map((int i) => row[i].padRight(widths[i])).join(' • ') + ' • ${row.last}';
+    }
   }
 
-  static void printDevices(List<Device> devices) {
-    descriptions(devices).forEach(printStatus);
+  static Future<Null> printDevices(List<Device> devices) async {
+    await descriptions(devices).forEach(printStatus);
   }
 }
 
diff --git a/packages/flutter_tools/lib/src/doctor.dart b/packages/flutter_tools/lib/src/doctor.dart
index 3bac2cc..ba28a7b 100644
--- a/packages/flutter_tools/lib/src/doctor.dart
+++ b/packages/flutter_tools/lib/src/doctor.dart
@@ -486,12 +486,12 @@
 
   @override
   Future<ValidationResult> validate() async {
-    final List<Device> devices = await deviceManager.getAllConnectedDevices();
+    final List<Device> devices = await deviceManager.getAllConnectedDevices().toList();
     List<ValidationMessage> messages;
     if (devices.isEmpty) {
       messages = <ValidationMessage>[new ValidationMessage('None')];
     } else {
-      messages = Device.descriptions(devices)
+      messages = await Device.descriptions(devices)
           .map((String msg) => new ValidationMessage(msg)).toList();
     }
     return new ValidationResult(ValidationType.installed, messages);
diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
index 0c4ce20..9b1a6a5 100644
--- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
+++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
@@ -37,22 +37,22 @@
   final String name;
 
   @override
-  bool get isLocalEmulator => false;
+  Future<bool> get isLocalEmulator async => false;
 
   @override
   bool get supportsStartPaused => false;
 
   @override
-  bool isAppInstalled(ApplicationPackage app) => false;
+  Future<bool> isAppInstalled(ApplicationPackage app) async => false;
 
   @override
-  bool isLatestBuildInstalled(ApplicationPackage app) => false;
+  Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => false;
 
   @override
   Future<bool> installApp(ApplicationPackage app) => new Future<bool>.value(false);
 
   @override
-  bool uninstallApp(ApplicationPackage app) => false;
+  Future<bool> uninstallApp(ApplicationPackage app) async => false;
 
   @override
   bool isSupported() => true;
@@ -77,10 +77,10 @@
   }
 
   @override
-  TargetPlatform get targetPlatform => TargetPlatform.fuchsia;
+  Future<TargetPlatform> get targetPlatform async => TargetPlatform.fuchsia;
 
   @override
-  String get sdkNameAndVersion => 'Fuchsia';
+  Future<String> get sdkNameAndVersion async => 'Fuchsia';
 
   _FuchsiaLogReader _logReader;
   @override
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart
index fb6bc3d..f61d486 100644
--- a/packages/flutter_tools/lib/src/ios/devices.dart
+++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -87,7 +87,7 @@
   _IOSDevicePortForwarder _portForwarder;
 
   @override
-  bool get isLocalEmulator => false;
+  Future<bool> get isLocalEmulator async => false;
 
   @override
   bool get supportsStartPaused => false;
@@ -139,10 +139,10 @@
   }
 
   @override
-  bool isAppInstalled(ApplicationPackage app) {
+  Future<bool> isAppInstalled(ApplicationPackage app) async {
     try {
-      final String apps = runCheckedSync(<String>[installerPath, '--list-apps']);
-      if (new RegExp(app.id, multiLine: true).hasMatch(apps)) {
+      final RunResult apps = await runCheckedAsync(<String>[installerPath, '--list-apps']);
+      if (new RegExp(app.id, multiLine: true).hasMatch(apps.stdout)) {
         return true;
       }
     } catch (e) {
@@ -152,7 +152,7 @@
   }
 
   @override
-  bool isLatestBuildInstalled(ApplicationPackage app) => false;
+  Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => false;
 
   @override
   Future<bool> installApp(ApplicationPackage app) async {
@@ -164,7 +164,7 @@
     }
 
     try {
-      runCheckedSync(<String>[installerPath, '-i', iosApp.deviceBundlePath]);
+      await runCheckedAsync(<String>[installerPath, '-i', iosApp.deviceBundlePath]);
       return true;
     } catch (e) {
       return false;
@@ -172,9 +172,9 @@
   }
 
   @override
-  bool uninstallApp(ApplicationPackage app) {
+  Future<bool> uninstallApp(ApplicationPackage app) async {
     try {
-      runCheckedSync(<String>[installerPath, '-U', app.id]);
+      await runCheckedAsync(<String>[installerPath, '-U', app.id]);
       return true;
     } catch (e) {
       return false;
@@ -343,10 +343,10 @@
   }
 
   @override
-  TargetPlatform get targetPlatform => TargetPlatform.ios;
+  Future<TargetPlatform> get targetPlatform async => TargetPlatform.ios;
 
   @override
-  String get sdkNameAndVersion => 'iOS $_sdkVersion ($_buildVersion)';
+  Future<String> get sdkNameAndVersion async => 'iOS $_sdkVersion ($_buildVersion)';
 
   String get _sdkVersion => _getDeviceInfo(id, 'ProductVersion');
 
@@ -370,8 +370,7 @@
 
   @override
   Future<Null> takeScreenshot(File outputFile) {
-    runCheckedSync(<String>[screenshotPath, outputFile.path]);
-    return new Future<Null>.value();
+    return runCheckedAsync(<String>[screenshotPath, outputFile.path]);
   }
 }
 
diff --git a/packages/flutter_tools/lib/src/ios/ios_workflow.dart b/packages/flutter_tools/lib/src/ios/ios_workflow.dart
index 018469d..70bb702 100644
--- a/packages/flutter_tools/lib/src/ios/ios_workflow.dart
+++ b/packages/flutter_tools/lib/src/ios/ios_workflow.dart
@@ -165,7 +165,7 @@
         // Check for compatibility between libimobiledevice and Xcode.
         // TODO(cbracken) remove this check once libimobiledevice > 1.2.0 is released.
         final ProcessResult result = (await runAsync(<String>['idevice_id', '-l'])).processResult;
-        if (result.exitCode == 0 && result.stdout.isNotEmpty && !exitsHappy(<String>['ideviceName'])) {
+        if (result.exitCode == 0 && result.stdout.isNotEmpty && !await exitsHappyAsync(<String>['ideviceName'])) {
           brewStatus = ValidationType.partial;
           messages.add(new ValidationMessage.error(
             'libimobiledevice is incompatible with the installed Xcode version. To update, run:\n'
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart
index 9baf97f..cf675ac 100644
--- a/packages/flutter_tools/lib/src/ios/mac.dart
+++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -399,7 +399,7 @@
       continue;
     }
     // Shell out so permissions on the dylib are preserved.
-    runCheckedSync(<String>['/bin/cp', dylib.path, frameworksDirectory.path]);
+    await runCheckedAsync(<String>['/bin/cp', dylib.path, frameworksDirectory.path]);
   }
 }
 
diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart
index 6fd46b8..1f5f3e7 100644
--- a/packages/flutter_tools/lib/src/ios/simulators.dart
+++ b/packages/flutter_tools/lib/src/ios/simulators.dart
@@ -224,8 +224,8 @@
 
   bool _isAnyConnected() => getConnectedDevices().isNotEmpty;
 
-  bool isInstalled(String appId) {
-    return exitsHappy(<String>[
+  Future<bool> isInstalled(String appId) {
+    return exitsHappyAsync(<String>[
       _xcrunPath,
       'simctl',
       'get_app_container',
@@ -234,23 +234,23 @@
     ]);
   }
 
-  void install(String deviceId, String appPath) {
-    runCheckedSync(<String>[_xcrunPath, 'simctl', 'install', deviceId, appPath]);
+  Future<Null> install(String deviceId, String appPath) {
+    return runCheckedAsync(<String>[_xcrunPath, 'simctl', 'install', deviceId, appPath]);
   }
 
-  void uninstall(String deviceId, String appId) {
-    runCheckedSync(<String>[_xcrunPath, 'simctl', 'uninstall', deviceId, appId]);
+  Future<Null> uninstall(String deviceId, String appId) {
+    return runCheckedAsync(<String>[_xcrunPath, 'simctl', 'uninstall', deviceId, appId]);
   }
 
-  void launch(String deviceId, String appIdentifier, [List<String> launchArgs]) {
+  Future<Null> launch(String deviceId, String appIdentifier, [List<String> launchArgs]) {
     final List<String> args = <String>[_xcrunPath, 'simctl', 'launch', deviceId, appIdentifier];
     if (launchArgs != null)
       args.addAll(launchArgs);
-    runCheckedSync(args);
+    return runCheckedAsync(args);
   }
 
-  void takeScreenshot(String outputPath) {
-    runCheckedSync(<String>[_xcrunPath, 'simctl', 'io', 'booted', 'screenshot', outputPath]);
+  Future<Null> takeScreenshot(String outputPath) {
+    return runCheckedAsync(<String>[_xcrunPath, 'simctl', 'io', 'booted', 'screenshot', outputPath]);
   }
 }
 
@@ -313,7 +313,7 @@
   final String category;
 
   @override
-  bool get isLocalEmulator => true;
+  Future<bool> get isLocalEmulator async => true;
 
   @override
   bool get supportsHotMode => true;
@@ -335,12 +335,12 @@
   }
 
   @override
-  bool isAppInstalled(ApplicationPackage app) {
+  Future<bool> isAppInstalled(ApplicationPackage app) {
     return SimControl.instance.isInstalled(app.id);
   }
 
   @override
-  bool isLatestBuildInstalled(ApplicationPackage app) => false;
+  Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => false;
 
   @override
   Future<bool> installApp(ApplicationPackage app) async {
@@ -354,9 +354,9 @@
   }
 
   @override
-  bool uninstallApp(ApplicationPackage app) {
+  Future<bool> uninstallApp(ApplicationPackage app) async {
     try {
-      SimControl.instance.uninstall(id, app.id);
+      await SimControl.instance.uninstall(id, app.id);
       return true;
     } catch (e) {
       return false;
@@ -495,21 +495,18 @@
     }
   }
 
-  bool _applicationIsInstalledAndRunning(ApplicationPackage app) {
-    final bool isInstalled = isAppInstalled(app);
-
-    final bool isRunning = exitsHappy(<String>[
-      '/usr/bin/killall',
-      'Runner',
+  Future<bool> _applicationIsInstalledAndRunning(ApplicationPackage app) async {
+    final List<bool> criteria = await Future.wait(<Future<bool>>[
+      isAppInstalled(app),
+      exitsHappyAsync(<String>['/usr/bin/killall', 'Runner']),
     ]);
-
-    return isInstalled && isRunning;
+    return criteria.reduce((bool a, bool b) => a && b);
   }
 
   Future<Null> _setupUpdatedApplicationBundle(ApplicationPackage app) async {
     await _sideloadUpdatedAssetsForInstalledApplicationBundle(app);
 
-    if (!_applicationIsInstalledAndRunning(app))
+    if (!await _applicationIsInstalledAndRunning(app))
       return _buildAndInstallApplicationBundle(app);
   }
 
@@ -544,7 +541,7 @@
       ApplicationPackage app, String localFile, String targetFile) async {
     if (platform.isMacOS) {
       final String simulatorHomeDirectory = _getSimulatorAppHomeDirectory(app);
-      runCheckedSync(<String>['cp', localFile, fs.path.join(simulatorHomeDirectory, targetFile)]);
+      await runCheckedAsync(<String>['cp', localFile, fs.path.join(simulatorHomeDirectory, targetFile)]);
       return true;
     }
     return false;
@@ -555,10 +552,10 @@
   }
 
   @override
-  TargetPlatform get targetPlatform => TargetPlatform.ios;
+  Future<TargetPlatform> get targetPlatform async => TargetPlatform.ios;
 
   @override
-  String get sdkNameAndVersion => category;
+  Future<String> get sdkNameAndVersion async => category;
 
   @override
   DeviceLogReader getLogReader({ApplicationPackage app}) {
@@ -595,8 +592,7 @@
 
   @override
   Future<Null> takeScreenshot(File outputFile) {
-    SimControl.instance.takeScreenshot(outputFile.path);
-    return new Future<Null>.value();
+    return SimControl.instance.takeScreenshot(outputFile.path);
   }
 }
 
diff --git a/packages/flutter_tools/lib/src/run_cold.dart b/packages/flutter_tools/lib/src/run_cold.dart
index d114a7f..0a7b671 100644
--- a/packages/flutter_tools/lib/src/run_cold.dart
+++ b/packages/flutter_tools/lib/src/run_cold.dart
@@ -61,11 +61,12 @@
       printStatus('Launching ${getDisplayPath(mainPath)} on ${device.name} in $modeName mode...');
     }
 
-    package = getApplicationPackageForPlatform(device.targetPlatform, applicationBinary: applicationBinary);
+    final TargetPlatform targetPlatform = await device.targetPlatform;
+    package = getApplicationPackageForPlatform(targetPlatform, applicationBinary: applicationBinary);
 
     if (package == null) {
-      String message = 'No application found for ${device.targetPlatform}.';
-      final String hint = getMissingPackageHintForPlatform(device.targetPlatform);
+      String message = 'No application found for $targetPlatform.';
+      final String hint = getMissingPackageHintForPlatform(targetPlatform);
       if (hint != null)
         message += '\n$hint';
       printError(message);
diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart
index 823ff7e..3b05d16 100644
--- a/packages/flutter_tools/lib/src/run_hot.dart
+++ b/packages/flutter_tools/lib/src/run_hot.dart
@@ -177,11 +177,12 @@
     final String modeName = getModeName(debuggingOptions.buildMode);
     printStatus('Launching ${getDisplayPath(mainPath)} on ${device.name} in $modeName mode...');
 
-    package = getApplicationPackageForPlatform(device.targetPlatform, applicationBinary: applicationBinary);
+    final TargetPlatform targetPlatform = await device.targetPlatform;
+    package = getApplicationPackageForPlatform(targetPlatform, applicationBinary: applicationBinary);
 
     if (package == null) {
-      String message = 'No application found for ${device.targetPlatform}.';
-      final String hint = getMissingPackageHintForPlatform(device.targetPlatform);
+      String message = 'No application found for $targetPlatform.';
+      final String hint = getMissingPackageHintForPlatform(targetPlatform);
       if (hint != null)
         message += '\n$hint';
       printError(message);
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index 9485618..3fd1e0f 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -102,7 +102,7 @@
 
   /// The path to send to Google Analytics. Return `null` here to disable
   /// tracking of the command.
-  String get usagePath => name;
+  Future<String> get usagePath async => name;
 
   /// Runs this command.
   ///
@@ -144,9 +144,9 @@
 
     setupApplicationPackages();
 
-    final String commandPath = usagePath;
+    final String commandPath = await usagePath;
     if (commandPath != null)
-      flutterUsage.sendCommand(usagePath);
+      flutterUsage.sendCommand(commandPath);
 
     await runCommand();
   }
@@ -158,14 +158,14 @@
   /// devices and criteria entered by the user on the command line.
   /// If a device cannot be found that meets specified criteria,
   /// then print an error message and return `null`.
-  Future<Device> findTargetDevice({bool androidOnly: false}) async {
+  Future<Device> findTargetDevice() async {
     if (!doctor.canLaunchAnything) {
       printError("Unable to locate a development device; please run 'flutter doctor' "
           "for information about installing additional components.");
       return null;
     }
 
-    List<Device> devices = await deviceManager.getDevices();
+    List<Device> devices = await deviceManager.getDevices().toList();
 
     if (devices.isEmpty && deviceManager.hasSpecifiedDeviceId) {
       printStatus("No devices found with name or id "
@@ -178,9 +178,6 @@
 
     devices = devices.where((Device device) => device.isSupported()).toList();
 
-    if (androidOnly)
-      devices = devices.where((Device device) => device.targetPlatform == TargetPlatform.android_arm).toList();
-
     if (devices.isEmpty) {
       printStatus('No supported devices connected.');
       return null;
@@ -191,10 +188,10 @@
       } else {
         printStatus("More than one device connected; please specify a device with "
             "the '-d <deviceId>' flag.");
-        devices = await deviceManager.getAllConnectedDevices();
+        devices = await deviceManager.getAllConnectedDevices().toList();
       }
       printStatus('');
-      Device.printDevices(devices);
+      await Device.printDevices(devices);
       return null;
     }
     return devices.single;
diff --git a/packages/flutter_tools/lib/src/usage.dart b/packages/flutter_tools/lib/src/usage.dart
index 4f72326..bad768d 100644
--- a/packages/flutter_tools/lib/src/usage.dart
+++ b/packages/flutter_tools/lib/src/usage.dart
@@ -4,6 +4,7 @@
 
 import 'dart:async';
 
+import 'package:meta/meta.dart';
 import 'package:usage/usage_io.dart';
 
 import 'base/context.dart';
@@ -96,7 +97,8 @@
       _analytics.sendException('${exception.runtimeType}\n${sanitizeStacktrace(trace)}');
   }
 
-  /// Fires whenever analytics data is sent over the network; public for testing.
+  /// Fires whenever analytics data is sent over the network.
+  @visibleForTesting
   Stream<Map<String, dynamic>> get onSend => _analytics.onSend;
 
   /// Returns when the last analytics event has been sent, or after a fixed
diff --git a/packages/flutter_tools/lib/src/zip.dart b/packages/flutter_tools/lib/src/zip.dart
index a7b1161..f4107f0 100644
--- a/packages/flutter_tools/lib/src/zip.dart
+++ b/packages/flutter_tools/lib/src/zip.dart
@@ -86,7 +86,7 @@
 
     final Iterable<String> compressedNames = _getCompressedNames();
     if (compressedNames.isNotEmpty) {
-      runCheckedSync(
+      await runCheckedAsync(
         <String>['zip', '-q', outFile.absolute.path]..addAll(compressedNames),
         workingDirectory: zipBuildDir.path
       );
@@ -94,7 +94,7 @@
 
     final Iterable<String> storedNames = _getStoredNames();
     if (storedNames.isNotEmpty) {
-      runCheckedSync(
+      await runCheckedAsync(
         <String>['zip', '-q', '-0', outFile.absolute.path]..addAll(storedNames),
         workingDirectory: zipBuildDir.path
       );
diff --git a/packages/flutter_tools/test/analytics_test.dart b/packages/flutter_tools/test/analytics_test.dart
index 26f9f63..5619f97 100644
--- a/packages/flutter_tools/test/analytics_test.dart
+++ b/packages/flutter_tools/test/analytics_test.dart
@@ -56,7 +56,7 @@
       Usage: () => new Usage(),
     });
 
-    // Ensure we con't send for the 'flutter config' command.
+    // Ensure we don't send for the 'flutter config' command.
     testUsingContext('config doesn\'t send', () async {
       int count = 0;
       flutterUsage.onSend.listen((Map<String, dynamic> data) => count++);
diff --git a/packages/flutter_tools/test/device_test.dart b/packages/flutter_tools/test/device_test.dart
index f16d2e4..7f26d4a 100644
--- a/packages/flutter_tools/test/device_test.dart
+++ b/packages/flutter_tools/test/device_test.dart
@@ -14,7 +14,7 @@
     testUsingContext('getDevices', () async {
       // Test that DeviceManager.getDevices() doesn't throw.
       final DeviceManager deviceManager = new DeviceManager();
-      final List<Device> devices = await deviceManager.getDevices();
+      final List<Device> devices = await deviceManager.getDevices().toList();
       expect(devices, isList);
     });
 
@@ -26,7 +26,7 @@
       final DeviceManager deviceManager = new TestDeviceManager(devices);
 
       Future<Null> expectDevice(String id, List<Device> expected) async {
-        expect(await deviceManager.getDevicesById(id), expected);
+        expect(await deviceManager.getDevicesById(id).toList(), expected);
       }
       expectDevice('01abfc49119c410e', <Device>[device2]);
       expectDevice('Nexus 5X', <Device>[device2]);
@@ -44,8 +44,8 @@
   TestDeviceManager(this.allDevices);
 
   @override
-  Future<List<Device>> getAllConnectedDevices() async {
-    return allDevices;
+  Stream<Device> getAllConnectedDevices() {
+    return new Stream<Device>.fromIterable(allDevices);
   }
 }
 
diff --git a/packages/flutter_tools/test/src/context.dart b/packages/flutter_tools/test/src/context.dart
index 97fbd03..42bf7eb 100644
--- a/packages/flutter_tools/test/src/context.dart
+++ b/packages/flutter_tools/test/src/context.dart
@@ -134,20 +134,19 @@
   bool get hasSpecifiedDeviceId => specifiedDeviceId != null;
 
   @override
-  Future<List<Device>> getAllConnectedDevices() => new Future<List<Device>>.value(devices);
+  Stream<Device> getAllConnectedDevices() => new Stream<Device>.fromIterable(devices);
 
   @override
-  Future<List<Device>> getDevicesById(String deviceId) async {
-    return devices.where((Device device) => device.id == deviceId).toList();
+  Stream<Device> getDevicesById(String deviceId) {
+    return new Stream<Device>.fromIterable(
+        devices.where((Device device) => device.id == deviceId));
   }
 
   @override
-  Future<List<Device>> getDevices() async {
-    if (specifiedDeviceId == null) {
-      return getAllConnectedDevices();
-    } else {
-      return getDevicesById(specifiedDeviceId);
-    }
+  Stream<Device> getDevices() {
+    return hasSpecifiedDeviceId
+        ? getDevicesById(specifiedDeviceId)
+        : getAllConnectedDevices();
   }
 
   void addDevice(Device device) => devices.add(device);
diff --git a/packages/flutter_tools/test/src/ios/devices_test.dart b/packages/flutter_tools/test/src/ios/devices_test.dart
index 37a280d..20f9723 100644
--- a/packages/flutter_tools/test/src/ios/devices_test.dart
+++ b/packages/flutter_tools/test/src/ios/devices_test.dart
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'dart:async';
 import 'dart:io' show ProcessResult;
 
 import 'package:file/file.dart';
@@ -38,23 +39,23 @@
         // Let everything else return exit code 0 so process.dart doesn't crash.
         // The matcher order is important.
         when(
-          mockProcessManager.runSync(any, environment: null, workingDirectory:  null)
+          mockProcessManager.run(any, environment: null, workingDirectory:  null)
         ).thenReturn(
-          new ProcessResult(2, 0, '', null)
+          new Future<ProcessResult>.value(new ProcessResult(2, 0, '', ''))
         );
         // Let `which idevicescreenshot` fail with exit code 1.
         when(
           mockProcessManager.runSync(
             <String>['which', 'idevicescreenshot'], environment: null, workingDirectory: null)
         ).thenReturn(
-          new ProcessResult(1, 1, '', null)
+          new ProcessResult(1, 1, '', '')
         );
 
         iosDeviceUnderTest = new IOSDevice('1234');
-        iosDeviceUnderTest.takeScreenshot(mockOutputFile);
+        await iosDeviceUnderTest.takeScreenshot(mockOutputFile);
         verify(mockProcessManager.runSync(
           <String>['which', 'idevicescreenshot'], environment: null, workingDirectory: null));
-        verifyNever(mockProcessManager.runSync(
+        verifyNever(mockProcessManager.run(
           <String>['idevicescreenshot', fs.path.join('some', 'test', 'path', 'image.png')],
           environment: null,
           workingDirectory: null
@@ -74,23 +75,23 @@
         // Let everything else return exit code 0.
         // The matcher order is important.
         when(
-          mockProcessManager.runSync(any, environment: null, workingDirectory:  null)
+          mockProcessManager.run(any, environment: null, workingDirectory:  null)
         ).thenReturn(
-          new ProcessResult(4, 0, '', null)
+          new Future<ProcessResult>.value(new ProcessResult(4, 0, '', ''))
         );
         // Let there be idevicescreenshot in the PATH.
         when(
           mockProcessManager.runSync(
             <String>['which', 'idevicescreenshot'], environment: null, workingDirectory: null)
         ).thenReturn(
-          new ProcessResult(3, 0, fs.path.join('some', 'path', 'to', 'iscreenshot'), null)
+          new ProcessResult(3, 0, fs.path.join('some', 'path', 'to', 'iscreenshot'), '')
         );
 
         iosDeviceUnderTest = new IOSDevice('1234');
-        iosDeviceUnderTest.takeScreenshot(mockOutputFile);
+        await iosDeviceUnderTest.takeScreenshot(mockOutputFile);
         verify(mockProcessManager.runSync(
           <String>['which', 'idevicescreenshot'], environment: null, workingDirectory: null));
-        verify(mockProcessManager.runSync(
+        verify(mockProcessManager.run(
           <String>[
             fs.path.join('some', 'path', 'to', 'iscreenshot'),
             fs.path.join('some', 'test', 'path', 'image.png')
diff --git a/packages/flutter_tools/test/src/ios/simulators_test.dart b/packages/flutter_tools/test/src/ios/simulators_test.dart
index 610c9e8..a731197 100644
--- a/packages/flutter_tools/test/src/ios/simulators_test.dart
+++ b/packages/flutter_tools/test/src/ios/simulators_test.dart
@@ -1,3 +1,4 @@
+import 'dart:async';
 import 'dart:io' show ProcessResult;
 
 import 'package:file/file.dart';
@@ -128,9 +129,9 @@
       mockProcessManager = new MockProcessManager();
       // Let everything else return exit code 0 so process.dart doesn't crash.
       when(
-        mockProcessManager.runSync(any, environment: null, workingDirectory:  null)
+        mockProcessManager.run(any, environment: null, workingDirectory:  null)
       ).thenReturn(
-        new ProcessResult(2, 0, '', null)
+        new Future<ProcessResult>.value(new ProcessResult(2, 0, '', ''))
       );
       // Doesn't matter what the device is.
       deviceUnderTest = new IOSSimulator('x', name: 'iPhone SE');
@@ -148,14 +149,14 @@
 
     testUsingContext(
       'Xcode 8.2+ supports screenshots',
-      () {
+      () async {
         when(mockXcode.xcodeMajorVersion).thenReturn(8);
         when(mockXcode.xcodeMinorVersion).thenReturn(2);
         expect(deviceUnderTest.supportsScreenshot, true);
         final MockFile mockFile = new MockFile();
         when(mockFile.path).thenReturn(fs.path.join('some', 'path', 'to', 'screenshot.png'));
-        deviceUnderTest.takeScreenshot(mockFile);
-        verify(mockProcessManager.runSync(
+        await deviceUnderTest.takeScreenshot(mockFile);
+        verify(mockProcessManager.run(
           <String>[
               '/usr/bin/xcrun',
               'simctl',
diff --git a/packages/flutter_tools/test/src/mocks.dart b/packages/flutter_tools/test/src/mocks.dart
index 3fbc21d..6bd31ef 100644
--- a/packages/flutter_tools/test/src/mocks.dart
+++ b/packages/flutter_tools/test/src/mocks.dart
@@ -31,7 +31,7 @@
 
 class MockAndroidDevice extends Mock implements AndroidDevice {
   @override
-  TargetPlatform get targetPlatform => TargetPlatform.android_arm;
+  Future<TargetPlatform> get targetPlatform async => TargetPlatform.android_arm;
 
   @override
   bool isSupported() => true;
@@ -39,7 +39,7 @@
 
 class MockIOSDevice extends Mock implements IOSDevice {
   @override
-  TargetPlatform get targetPlatform => TargetPlatform.ios;
+  Future<TargetPlatform> get targetPlatform async => TargetPlatform.ios;
 
   @override
   bool isSupported() => true;
@@ -47,7 +47,7 @@
 
 class MockIOSSimulator extends Mock implements IOSSimulator {
   @override
-  TargetPlatform get targetPlatform => TargetPlatform.ios;
+  Future<TargetPlatform> get targetPlatform async => TargetPlatform.ios;
 
   @override
   bool isSupported() => true;