Re-enable use of instruments for iOS device lookup (#10838)

This reverts commit b2909a245a607995ce7ec286585cd1f643124f57.

This resubmits the following patches:

1. Use Xcode instruments to list devices (#10801)
Eliminates the dependency on idevice_id from libimobiledevice. Instead,
uses Xcode built-in functionality.

2. Make device discovery asynchronous (#10803)
Migrates DeviceDiscovery.devices and all device-specific lookup to be
asynchronous.
diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart
index f6ed675..8584455 100644
--- a/packages/flutter_tools/lib/src/android/android_device.dart
+++ b/packages/flutter_tools/lib/src/android/android_device.dart
@@ -48,7 +48,7 @@
   bool get canListAnything => androidWorkflow.canListDevices;
 
   @override
-  List<Device> pollingGetDevices() => getAdbDevices();
+  Future<List<Device>> pollingGetDevices() async => getAdbDevices();
 }
 
 class AndroidDevice extends Device {
diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart
index 8c39078..cb069f1 100644
--- a/packages/flutter_tools/lib/src/commands/daemon.dart
+++ b/packages/flutter_tools/lib/src/commands/daemon.dart
@@ -305,7 +305,7 @@
     final String target = _getStringArg(args, 'target');
     final bool enableHotReload = _getBoolArg(args, 'hot') ?? kHotReloadDefault;
 
-    final Device device = daemon.deviceDomain._getOrLocateDevice(deviceId);
+    final Device device = await daemon.deviceDomain._getOrLocateDevice(deviceId);
     if (device == null)
       throw "device '$deviceId' not found";
 
@@ -493,7 +493,7 @@
   Future<List<Map<String, dynamic>>> discover(Map<String, dynamic> args) async {
     final String deviceId = _getStringArg(args, 'deviceId', required: true);
 
-    final Device device = daemon.deviceDomain._getDevice(deviceId);
+    final Device device = await daemon.deviceDomain._getDevice(deviceId);
     if (device == null)
       throw "device '$deviceId' not found";
 
@@ -575,11 +575,12 @@
 
   final List<PollingDeviceDiscovery> _discoverers = <PollingDeviceDiscovery>[];
 
-  Future<List<Device>> getDevices([Map<String, dynamic> args]) {
-    final List<Device> devices = _discoverers.expand((PollingDeviceDiscovery discoverer) {
-      return discoverer.devices;
-    }).toList();
-    return new Future<List<Device>>.value(devices);
+  Future<List<Device>> getDevices([Map<String, dynamic> args]) async {
+    final List<Device> devices = <Device>[];
+    for (PollingDeviceDiscovery discoverer in _discoverers) {
+      devices.addAll(await discoverer.devices);
+    }
+    return devices;
   }
 
   /// Enable device events.
@@ -602,7 +603,7 @@
     final int devicePort = _getIntArg(args, 'devicePort', required: true);
     int hostPort = _getIntArg(args, 'hostPort');
 
-    final Device device = daemon.deviceDomain._getDevice(deviceId);
+    final Device device = await daemon.deviceDomain._getDevice(deviceId);
     if (device == null)
       throw "device '$deviceId' not found";
 
@@ -617,7 +618,7 @@
     final int devicePort = _getIntArg(args, 'devicePort', required: true);
     final int hostPort = _getIntArg(args, 'hostPort', required: true);
 
-    final Device device = daemon.deviceDomain._getDevice(deviceId);
+    final Device device = await daemon.deviceDomain._getDevice(deviceId);
     if (device == null)
       throw "device '$deviceId' not found";
 
@@ -631,23 +632,25 @@
   }
 
   /// Return the device matching the deviceId field in the args.
-  Device _getDevice(String deviceId) {
-    final List<Device> devices = _discoverers.expand((PollingDeviceDiscovery discoverer) {
-      return discoverer.devices;
-    }).toList();
-    return devices.firstWhere((Device device) => device.id == deviceId, orElse: () => null);
+  Future<Device> _getDevice(String deviceId) async {
+    for (PollingDeviceDiscovery discoverer in _discoverers) {
+      final Device device = (await discoverer.devices).firstWhere((Device device) => device.id == deviceId, orElse: () => null);
+      if (device != null)
+        return device;
+    }
+    return null;
   }
 
   /// Return a known matching device, or scan for devices if no known match is found.
-  Device _getOrLocateDevice(String deviceId) {
+  Future<Device> _getOrLocateDevice(String deviceId) async {
     // Look for an already known device.
-    final Device device = _getDevice(deviceId);
+    final Device device = await _getDevice(deviceId);
     if (device != null)
       return device;
 
     // Scan the different device providers for a match.
     for (PollingDeviceDiscovery discoverer in _discoverers) {
-      final List<Device> devices = discoverer.pollingGetDevices();
+      final List<Device> devices = await discoverer.pollingGetDevices();
       for (Device device in devices)
         if (device.id == deviceId)
           return device;
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index de3f873..8208adf 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -81,11 +81,17 @@
         : getAllConnectedDevices();
   }
 
+  Iterable<DeviceDiscovery> get _platformDiscoverers {
+    return _deviceDiscoverers.where((DeviceDiscovery discoverer) => discoverer.supportsPlatform);
+  }
+
   /// Return the list of all connected devices.
-  Stream<Device> getAllConnectedDevices() {
-    return new Stream<Device>.fromIterable(_deviceDiscoverers
-      .where((DeviceDiscovery discoverer) => discoverer.supportsPlatform)
-      .expand((DeviceDiscovery discoverer) => discoverer.devices));
+  Stream<Device> getAllConnectedDevices() async* {
+    for (DeviceDiscovery discoverer in _platformDiscoverers) {
+      for (Device device in await discoverer.devices) {
+        yield device;
+      }
+    }
   }
 }
 
@@ -97,7 +103,7 @@
   /// current environment configuration.
   bool get canListAnything;
 
-  List<Device> get devices;
+  Future<List<Device>> get devices;
 }
 
 /// A [DeviceDiscovery] implementation that uses polling to discover device adds
@@ -111,13 +117,13 @@
   ItemListNotifier<Device> _items;
   Timer _timer;
 
-  List<Device> pollingGetDevices();
+  Future<List<Device>> pollingGetDevices();
 
   void startPolling() {
     if (_timer == null) {
       _items ??= new ItemListNotifier<Device>();
-      _timer = new Timer.periodic(_pollingDuration, (Timer timer) {
-        _items.updateWithNewList(pollingGetDevices());
+      _timer = new Timer.periodic(_pollingDuration, (Timer timer) async {
+        _items.updateWithNewList(await pollingGetDevices());
       });
     }
   }
@@ -128,8 +134,8 @@
   }
 
   @override
-  List<Device> get devices {
-    _items ??= new ItemListNotifier<Device>.from(pollingGetDevices());
+  Future<List<Device>> get devices async {
+    _items ??= new ItemListNotifier<Device>.from(await pollingGetDevices());
     return _items.items;
   }
 
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart
index bb3eca7..1cc5c96 100644
--- a/packages/flutter_tools/lib/src/ios/devices.dart
+++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -36,11 +36,11 @@
   bool get canListAnything => iosWorkflow.canListDevices;
 
   @override
-  List<Device> pollingGetDevices() => IOSDevice.getAttachedDevices();
+  Future<List<Device>> pollingGetDevices() => IOSDevice.getAttachedDevices();
 }
 
 class IOSDevice extends Device {
-  IOSDevice(String id, { this.name }) : super(id) {
+  IOSDevice(String id, { this.name, String sdkVersion }) : _sdkVersion = sdkVersion, super(id) {
     _installerPath = _checkForCommand('ideviceinstaller');
     _iproxyPath = _checkForCommand('iproxy');
     _pusherPath = _checkForCommand(
@@ -55,6 +55,8 @@
   String _iproxyPath;
   String _pusherPath;
 
+  final String _sdkVersion;
+
   @override
   bool get supportsHotMode => true;
 
@@ -71,14 +73,30 @@
   @override
   bool get supportsStartPaused => false;
 
-  static List<IOSDevice> getAttachedDevices() {
-    if (!iMobileDevice.isInstalled)
+  // Physical device line format to be matched:
+  // My iPhone (10.3.2) [75b90e947c5f429fa67f3e9169fda0d89f0492f1]
+  //
+  // Other formats in output (desktop, simulator) to be ignored:
+  // my-mac-pro [2C10513E-4dA5-405C-8EF5-C44353DB3ADD]
+  // iPhone 6s (9.3) [F6CEE7CF-81EB-4448-81B4-1755288C7C11] (Simulator)
+  static final RegExp _deviceRegex = new RegExp(r'^(.*) +\((.*)\) +\[(.*)\]$');
+
+  static Future<List<IOSDevice>> getAttachedDevices() async {
+    if (!xcode.isInstalled)
       return <IOSDevice>[];
 
     final List<IOSDevice> devices = <IOSDevice>[];
-    for (String id in iMobileDevice.getAttachedDeviceIDs()) {
-      final String name = iMobileDevice.getInfoForDevice(id, 'DeviceName');
-      devices.add(new IOSDevice(id, name: name));
+    final Iterable<String> deviceLines = (await xcode.getAvailableDevices())
+        .split('\n')
+        .map((String line) => line.trim());
+    for (String line in deviceLines) {
+      final Match match = _deviceRegex.firstMatch(line);
+      if (match != null) {
+        final String deviceName = match.group(1);
+        final String sdkVersion = match.group(2);
+        final String deviceID = match.group(3);
+        devices.add(new IOSDevice(deviceID, name: deviceName, sdkVersion: sdkVersion));
+      }
     }
     return devices;
   }
@@ -311,11 +329,7 @@
   Future<TargetPlatform> get targetPlatform async => TargetPlatform.ios;
 
   @override
-  Future<String> get sdkNameAndVersion async => 'iOS $_sdkVersion ($_buildVersion)';
-
-  String get _sdkVersion => iMobileDevice.getInfoForDevice(id, 'ProductVersion');
-
-  String get _buildVersion => iMobileDevice.getInfoForDevice(id, 'BuildVersion');
+  Future<String> get sdkNameAndVersion async => 'iOS $_sdkVersion';
 
   @override
   DeviceLogReader getLogReader({ApplicationPackage app}) {
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart
index 7d1bfa6..1b1317e 100644
--- a/packages/flutter_tools/lib/src/ios/mac.dart
+++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -70,14 +70,6 @@
     return await exitsHappyAsync(<String>['idevicename']);
   }
 
-  List<String> getAttachedDeviceIDs() {
-    return runSync(<String>['idevice_id', '-l'])
-        .trim()
-        .split('\n')
-        .where((String line) => line.isNotEmpty)
-        .toList();
-  }
-
   /// Returns the value associated with the specified `ideviceinfo` key for a device.
   ///
   /// If either the specified key or device does not exist, returns the empty string.
@@ -165,6 +157,13 @@
 
     return _xcodeVersionCheckValid(_xcodeMajorVersion, _xcodeMinorVersion);
   }
+
+  Future<String> getAvailableDevices() async {
+    final RunResult result = await runAsync(<String>['/usr/bin/instruments', '-s', 'devices']);
+    if (result.exitCode != 0)
+      throw new ToolExit('Failed to invoke /usr/bin/instruments. Is Xcode installed?');
+    return result.stdout;
+  }
 }
 
 bool _xcodeVersionCheckValid(int major, int minor) {
diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart
index 697be3b..92788c6 100644
--- a/packages/flutter_tools/lib/src/ios/simulators.dart
+++ b/packages/flutter_tools/lib/src/ios/simulators.dart
@@ -37,7 +37,7 @@
   bool get canListAnything => iosWorkflow.canListDevices;
 
   @override
-  List<Device> pollingGetDevices() => IOSSimulatorUtils.instance.getAttachedDevices();
+  Future<List<Device>> pollingGetDevices() async => IOSSimulatorUtils.instance.getAttachedDevices();
 }
 
 class IOSSimulatorUtils {