Only show iOS simulators, reduce output spew in verbose (#108345)

diff --git a/packages/flutter_tools/lib/src/ios/ios_workflow.dart b/packages/flutter_tools/lib/src/ios/ios_workflow.dart
index a8d8f03..a656fd4 100644
--- a/packages/flutter_tools/lib/src/ios/ios_workflow.dart
+++ b/packages/flutter_tools/lib/src/ios/ios_workflow.dart
@@ -25,7 +25,7 @@
 
   // We need xcode (+simctl) to list simulator devices, and libimobiledevice to list real devices.
   @override
-  bool get canListDevices => appliesToHostPlatform && _xcode.isInstalledAndMeetsVersionCheck && _xcode.isSimctlInstalled;
+  bool get canListDevices => appliesToHostPlatform && _xcode.isSimctlInstalled;
 
   // We need xcode to launch simulator devices, and ios-deploy
   // for real devices.
diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart
index 5484510..4bcfc9e 100644
--- a/packages/flutter_tools/lib/src/ios/simulators.dart
+++ b/packages/flutter_tools/lib/src/ios/simulators.dart
@@ -71,8 +71,8 @@
       return <IOSSimulator>[];
     }
 
-    final List<SimDevice> connected = await _simControl.getConnectedDevices();
-    return connected.map<IOSSimulator?>((SimDevice device) {
+    final List<BootedSimDevice> connected = await _simControl.getConnectedDevices();
+    return connected.map<IOSSimulator?>((BootedSimDevice device) {
       final String? udid = device.udid;
       final String? name = device.name;
       if (udid == null) {
@@ -109,30 +109,45 @@
 
   /// Runs `simctl list --json` and returns the JSON of the corresponding
   /// [section].
-  Future<Map<String, Object?>> _list(SimControlListSection section) async {
-    // Sample output from `simctl list --json`:
+  Future<Map<String, Object?>> _listBootedDevices() async {
+    // Sample output from `simctl list available booted --json`:
     //
     // {
-    //   "devicetypes": { ... },
-    //   "runtimes": { ... },
     //   "devices" : {
-    //     "com.apple.CoreSimulator.SimRuntime.iOS-8-2" : [
+    //     "com.apple.CoreSimulator.SimRuntime.iOS-14-0" : [
     //       {
-    //         "state" : "Shutdown",
-    //         "availability" : " (unavailable, runtime profile not found)",
-    //         "name" : "iPhone 4s",
-    //         "udid" : "1913014C-6DCB-485D-AC6B-7CD76D322F5B"
-    //       },
-    //       ...
-    //   },
-    //   "pairs": { ... },
+    //         "lastBootedAt" : "2022-07-26T01:46:23Z",
+    //         "dataPath" : "\/Users\/magder\/Library\/Developer\/CoreSimulator\/Devices\/9EC90A99-6924-472D-8CDD-4D8234AB4779\/data",
+    //         "dataPathSize" : 1620578304,
+    //         "logPath" : "\/Users\/magder\/Library\/Logs\/CoreSimulator\/9EC90A99-6924-472D-8CDD-4D8234AB4779",
+    //         "udid" : "9EC90A99-6924-472D-8CDD-4D8234AB4779",
+    //         "isAvailable" : true,
+    //         "logPathSize" : 9740288,
+    //         "deviceTypeIdentifier" : "com.apple.CoreSimulator.SimDeviceType.iPhone-11",
+    //         "state" : "Booted",
+    //         "name" : "iPhone 11"
+    //       }
+    //     ],
+    //     "com.apple.CoreSimulator.SimRuntime.iOS-13-0" : [
+    //
+    //     ],
+    //     "com.apple.CoreSimulator.SimRuntime.iOS-12-4" : [
+    //
+    //     ],
+    //     "com.apple.CoreSimulator.SimRuntime.iOS-16-0" : [
+    //
+    //     ]
+    //   }
+    // }
 
     final List<String> command = <String>[
       ..._xcode.xcrunCommand(),
       'simctl',
       'list',
+      'devices',
+      'booted',
+      'iOS',
       '--json',
-      section.name,
     ];
     _logger.printTrace(command.join(' '));
     final RunResult results = await _processUtils.run(command);
@@ -141,7 +156,7 @@
       return <String, Map<String, Object?>>{};
     }
     try {
-      final Object? decodeResult = (json.decode(results.stdout) as Map<String, Object?>)[section.name];
+      final Object? decodeResult = (json.decode(results.stdout) as Map<String, Object?>)['devices'];
       if (decodeResult is Map<String, Object?>) {
         return decodeResult;
       }
@@ -156,17 +171,17 @@
     }
   }
 
-  /// Returns a list of all available devices, both potential and connected.
-  Future<List<SimDevice>> getDevices() async {
-    final List<SimDevice> devices = <SimDevice>[];
+  /// Returns all the connected simulator devices.
+  Future<List<BootedSimDevice>> getConnectedDevices() async {
+    final List<BootedSimDevice> devices = <BootedSimDevice>[];
 
-    final Map<String, Object?> devicesSection = await _list(SimControlListSection.devices);
+    final Map<String, Object?> devicesSection = await _listBootedDevices();
 
     for (final String deviceCategory in devicesSection.keys) {
       final Object? devicesData = devicesSection[deviceCategory];
       if (devicesData != null && devicesData is List<Object?>) {
         for (final Map<String, Object?> data in devicesData.map<Map<String, Object?>?>(castStringKeyedMap).whereType<Map<String, Object?>>()) {
-          devices.add(SimDevice(deviceCategory, data));
+          devices.add(BootedSimDevice(deviceCategory, data));
         }
       }
     }
@@ -174,12 +189,6 @@
     return devices;
   }
 
-  /// Returns all the connected simulator devices.
-  Future<List<SimDevice>> getConnectedDevices() async {
-    final List<SimDevice> simDevices = await getDevices();
-    return simDevices.where((SimDevice device) => device.isBooted).toList();
-  }
-
   Future<bool> isInstalled(String deviceId, String appId) {
     return _processUtils.exitsHappy(<String>[
       ..._xcode.xcrunCommand(),
@@ -267,54 +276,15 @@
   }
 }
 
-/// Enumerates all data sections of `xcrun simctl list --json` command.
-class SimControlListSection {
-  const SimControlListSection._(this.name);
 
-  final String name;
-
-  static const SimControlListSection devices = SimControlListSection._('devices');
-  static const SimControlListSection devicetypes = SimControlListSection._('devicetypes');
-  static const SimControlListSection runtimes = SimControlListSection._('runtimes');
-  static const SimControlListSection pairs = SimControlListSection._('pairs');
-}
-
-/// A simulated device type.
-///
-/// Simulated device types can be listed using the command
-/// `xcrun simctl list devicetypes`.
-class SimDeviceType {
-  SimDeviceType(this.name, this.identifier);
-
-  /// The name of the device type.
-  ///
-  /// Examples:
-  ///
-  ///     "iPhone 6s"
-  ///     "iPhone 6 Plus"
-  final String name;
-
-  /// The identifier of the device type.
-  ///
-  /// Examples:
-  ///
-  ///     "com.apple.CoreSimulator.SimDeviceType.iPhone-6s"
-  ///     "com.apple.CoreSimulator.SimDeviceType.iPhone-6-Plus"
-  final String identifier;
-}
-
-class SimDevice {
-  SimDevice(this.category, this.data);
+class BootedSimDevice {
+  BootedSimDevice(this.category, this.data);
 
   final String category;
   final Map<String, Object?> data;
 
-  String? get state => data['state']?.toString();
-  String? get availability => data['availability']?.toString();
   String? get name => data['name']?.toString();
   String? get udid => data['udid']?.toString();
-
-  bool get isBooted => state == 'Booted';
 }
 
 class IOSSimulator extends Device {
diff --git a/packages/flutter_tools/lib/src/macos/xcode.dart b/packages/flutter_tools/lib/src/macos/xcode.dart
index d92fd26..fa7212f 100644
--- a/packages/flutter_tools/lib/src/macos/xcode.dart
+++ b/packages/flutter_tools/lib/src/macos/xcode.dart
@@ -136,7 +136,7 @@
         // This command will error if additional components need to be installed in
         // xcode 9.2 and above.
         final RunResult result = _processUtils.runSync(
-          <String>[...xcrunCommand(), 'simctl', 'list'],
+          <String>[...xcrunCommand(), 'simctl', 'list', 'devices', 'booted'],
         );
         _isSimctlInstalled = result.exitCode == 0;
       } on ProcessException {
diff --git a/packages/flutter_tools/test/general.shard/ios/ios_workflow_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_workflow_test.dart
index 688b360..85b6f29 100644
--- a/packages/flutter_tools/test/general.shard/ios/ios_workflow_test.dart
+++ b/packages/flutter_tools/test/general.shard/ios/ios_workflow_test.dart
@@ -49,10 +49,18 @@
     expect(iosWorkflow.canListDevices, false);
   });
 
-  testWithoutContext('iOS workflow applies on macOS, no Xcode', () {
+  testWithoutContext('iOS workflow applies on macOS, no Xcode or simctl', () {
+    final FakeProcessManager xcodeProcessManager = FakeProcessManager.list(<FakeCommand>[
+      const FakeCommand(
+        command: <String>[
+          'xcrun', 'simctl', 'list', 'devices', 'booted',
+        ],
+        exitCode: 1,
+      ),
+    ]);
     final IOSWorkflow iosWorkflow = IOSWorkflow(
       platform: FakePlatform(operatingSystem: 'macos'),
-      xcode: Xcode.test(processManager: FakeProcessManager.any(),
+      xcode: Xcode.test(processManager: xcodeProcessManager,
         xcodeProjectInterpreter: XcodeProjectInterpreter.test(
           processManager: FakeProcessManager.any(),
           version: null,
@@ -65,9 +73,33 @@
     expect(iosWorkflow.canLaunchDevices, false);
     expect(iosWorkflow.canListDevices, false);
     expect(iosWorkflow.canListEmulators, false);
+    expect(xcodeProcessManager, hasNoRemainingExpectations);
   });
 
-  testWithoutContext('iOS workflow can launch and list devices when Xcode is set up', () {
+  testWithoutContext('iOS workflow can list devices even when Xcode version is too low', () {
+    final Xcode xcode = Xcode.test(
+      processManager: FakeProcessManager.any(),
+      xcodeProjectInterpreter: XcodeProjectInterpreter.test(
+          processManager: FakeProcessManager.any(),
+          version: Version(1, 0, 0)
+      ),
+    );
+
+    final IOSWorkflow iosWorkflow = IOSWorkflow(
+      platform: FakePlatform(operatingSystem: 'macos'),
+      xcode: xcode,
+      featureFlags: TestFeatureFlags(),
+    );
+
+    // Make sure we're testing the right Xcode state.
+    // expect(xcode.isInstalledAndMeetsVersionCheck, true);
+    expect(xcode.isSimctlInstalled, true);
+    expect(iosWorkflow.canLaunchDevices, false);
+    expect(iosWorkflow.canListDevices, true);
+    expect(iosWorkflow.canListEmulators, false);
+  });
+
+  testWithoutContext('iOS workflow can launch devices when Xcode is set up', () {
     final Xcode xcode = Xcode.test(
       processManager: FakeProcessManager.any(),
       xcodeProjectInterpreter: XcodeProjectInterpreter.test(
diff --git a/packages/flutter_tools/test/general.shard/ios/simulators_test.dart b/packages/flutter_tools/test/general.shard/ios/simulators_test.dart
index 2177afa..f7d8d8e 100644
--- a/packages/flutter_tools/test/general.shard/ios/simulators_test.dart
+++ b/packages/flutter_tools/test/general.shard/ios/simulators_test.dart
@@ -724,28 +724,43 @@
     const String validSimControlOutput = '''
 {
   "devices" : {
-    "watchOS 4.3" : [
+    "com.apple.CoreSimulator.SimRuntime.iOS-14-0" : [
       {
-        "state" : "Shutdown",
-        "availability" : "(available)",
-        "name" : "Apple Watch - 38mm",
-        "udid" : "TEST-WATCH-UDID"
-      }
-    ],
-    "iOS 11.4" : [
-      {
+        "dataPathSize" : 1734569984,
+        "udid" : "iPhone 11-UDID",
+        "isAvailable" : true,
+        "logPathSize" : 9506816,
+        "deviceTypeIdentifier" : "com.apple.CoreSimulator.SimDeviceType.iPhone-11",
         "state" : "Booted",
-        "availability" : "(available)",
-        "name" : "iPhone 5s",
-        "udid" : "TEST-PHONE-UDID"
+        "name" : "iPhone 11"
       }
     ],
-    "tvOS 11.4" : [
+    "com.apple.CoreSimulator.SimRuntime.iOS-13-0" : [
+    ],
+    "com.apple.CoreSimulator.SimRuntime.iOS-12-4" : [
+    ],
+    "com.apple.CoreSimulator.SimRuntime.tvOS-16-0" : [
+    ],
+    "com.apple.CoreSimulator.SimRuntime.watchOS-9-0" : [
+    ],
+    "com.apple.CoreSimulator.SimRuntime.iOS-16-0" : [
       {
-        "state" : "Shutdown",
-        "availability" : "(available)",
-        "name" : "Apple TV",
-        "udid" : "TEST-TV-UDID"
+        "dataPathSize" : 552366080,
+        "udid" : "Phone w Watch-UDID",
+        "isAvailable" : true,
+        "logPathSize" : 90112,
+        "deviceTypeIdentifier" : "com.apple.CoreSimulator.SimDeviceType.iPhone-11",
+        "state" : "Booted",
+        "name" : "Phone w Watch"
+      },
+      {
+        "dataPathSize" : 2186457088,
+        "udid" : "iPhone 13-UDID",
+        "isAvailable" : true,
+        "logPathSize" : 151552,
+        "deviceTypeIdentifier" : "com.apple.CoreSimulator.SimDeviceType.iPhone-13",
+        "state" : "Booted",
+        "name" : "iPhone 13"
       }
     ]
   }
@@ -768,59 +783,54 @@
       );
     });
 
-    testWithoutContext('getDevices succeeds', () async {
+    testWithoutContext('getConnectedDevices succeeds', () async {
       fakeProcessManager.addCommand(const FakeCommand(
         command: <String>[
           'xcrun',
           'simctl',
           'list',
-          '--json',
           'devices',
+          'booted',
+          'iOS',
+          '--json',
         ],
         stdout: validSimControlOutput,
       ));
 
-      final List<SimDevice> devices = await simControl.getDevices();
+      final List<BootedSimDevice> devices = await simControl.getConnectedDevices();
 
-      final SimDevice watch = devices[0];
-      expect(watch.category, 'watchOS 4.3');
-      expect(watch.state, 'Shutdown');
-      expect(watch.availability, '(available)');
-      expect(watch.name, 'Apple Watch - 38mm');
-      expect(watch.udid, 'TEST-WATCH-UDID');
-      expect(watch.isBooted, isFalse);
+      final BootedSimDevice phone1 = devices[0];
+      expect(phone1.category, 'com.apple.CoreSimulator.SimRuntime.iOS-14-0');
+      expect(phone1.name, 'iPhone 11');
+      expect(phone1.udid, 'iPhone 11-UDID');
 
-      final SimDevice phone = devices[1];
-      expect(phone.category, 'iOS 11.4');
-      expect(phone.state, 'Booted');
-      expect(phone.availability, '(available)');
-      expect(phone.name, 'iPhone 5s');
-      expect(phone.udid, 'TEST-PHONE-UDID');
-      expect(phone.isBooted, isTrue);
+      final BootedSimDevice phone2 = devices[1];
+      expect(phone2.category, 'com.apple.CoreSimulator.SimRuntime.iOS-16-0');
+      expect(phone2.name, 'Phone w Watch');
+      expect(phone2.udid, 'Phone w Watch-UDID');
 
-      final SimDevice tv = devices[2];
-      expect(tv.category, 'tvOS 11.4');
-      expect(tv.state, 'Shutdown');
-      expect(tv.availability, '(available)');
-      expect(tv.name, 'Apple TV');
-      expect(tv.udid, 'TEST-TV-UDID');
-      expect(tv.isBooted, isFalse);
+      final BootedSimDevice phone3 = devices[2];
+      expect(phone3.category, 'com.apple.CoreSimulator.SimRuntime.iOS-16-0');
+      expect(phone3.name, 'iPhone 13');
+      expect(phone3.udid, 'iPhone 13-UDID');
       expect(fakeProcessManager.hasRemainingExpectations, isFalse);
     });
 
-    testWithoutContext('getDevices handles bad simctl output', () async {
+    testWithoutContext('getConnectedDevices handles bad simctl output', () async {
       fakeProcessManager.addCommand(const FakeCommand(
         command: <String>[
           'xcrun',
           'simctl',
           'list',
-          '--json',
           'devices',
+          'booted',
+          'iOS',
+          '--json',
         ],
         stdout: 'Install Started',
       ));
 
-      final List<SimDevice> devices = await simControl.getDevices();
+      final List<BootedSimDevice> devices = await simControl.getConnectedDevices();
 
       expect(devices, isEmpty);
       expect(fakeProcessManager.hasRemainingExpectations, isFalse);
diff --git a/packages/flutter_tools/test/general.shard/macos/xcode_test.dart b/packages/flutter_tools/test/general.shard/macos/xcode_test.dart
index 3969151..d4473e1 100644
--- a/packages/flutter_tools/test/general.shard/macos/xcode_test.dart
+++ b/packages/flutter_tools/test/general.shard/macos/xcode_test.dart
@@ -59,6 +59,8 @@
               'xcrun',
               'simctl',
               'list',
+              'devices',
+              'booted',
             ],
           ),
         );
@@ -78,6 +80,8 @@
               'xcrun',
               'simctl',
               'list',
+              'devices',
+              'booted',
             ],
             exitCode: 1,
           ),
diff --git a/packages/flutter_tools/test/general.shard/macos/xcode_validator_test.dart b/packages/flutter_tools/test/general.shard/macos/xcode_validator_test.dart
index 9ddd7e7..cf540c5 100644
--- a/packages/flutter_tools/test/general.shard/macos/xcode_validator_test.dart
+++ b/packages/flutter_tools/test/general.shard/macos/xcode_validator_test.dart
@@ -98,7 +98,7 @@
           'Xcode EULA has not been accepted.\nLaunch Xcode and accept the license.',
         ),
         const FakeCommand(
-          command: <String>['xcrun', 'simctl', 'list'],
+          command: <String>['xcrun', 'simctl', 'list', 'devices', 'booted'],
         ),
       ]);
       final Xcode xcode = Xcode.test(
@@ -135,7 +135,7 @@
           command: <String>['xcrun', 'clang'],
         ),
         const FakeCommand(
-          command: <String>['xcrun', 'simctl', 'list'],
+          command: <String>['xcrun', 'simctl', 'list', 'devices', 'booted'],
           exitCode: 1,
         ),
       ]);
@@ -173,7 +173,7 @@
           command: <String>['xcrun', 'clang'],
         ),
         const FakeCommand(
-          command: <String>['xcrun', 'simctl', 'list'],
+          command: <String>['xcrun', 'simctl', 'list', 'devices', 'booted'],
         ),
       ]);
       final Xcode xcode = Xcode.test(