// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import '../base/common.dart'; import '../base/logger.dart'; import '../base/platform.dart'; import '../base/terminal.dart'; import '../base/utils.dart'; import '../convert.dart'; import '../device.dart'; import '../globals.dart' as globals; import '../runner/flutter_command.dart'; class DevicesCommand extends FlutterCommand { DevicesCommand({ bool verboseHelp = false }) { argParser.addFlag('machine', negatable: false, help: 'Output device information in machine readable structured JSON format.', ); argParser.addOption( 'timeout', abbr: 't', help: '(deprecated) This option has been replaced by "--${FlutterOptions.kDeviceTimeout}".', hide: !verboseHelp, ); usesDeviceTimeoutOption(); usesDeviceConnectionOption(); } @override final String name = 'devices'; @override final String description = 'List all connected devices.'; @override final String category = FlutterCommandCategory.tools; @override Duration? get deviceDiscoveryTimeout { if (argResults?['timeout'] != null) { final int? timeoutSeconds = int.tryParse(stringArg('timeout')!); if (timeoutSeconds == null) { throwToolExit('Could not parse -t/--timeout argument. It must be an integer.'); } return Duration(seconds: timeoutSeconds); } return super.deviceDiscoveryTimeout; } @override Future<void> validateCommand() { if (argResults?['timeout'] != null) { globals.printWarning('${globals.logger.terminal.warningMark} The "--timeout" argument is deprecated; use "--${FlutterOptions.kDeviceTimeout}" instead.'); } return super.validateCommand(); } @override Future<FlutterCommandResult> runCommand() async { if (globals.doctor?.canListAnything != true) { throwToolExit( "Unable to locate a development device; please run 'flutter doctor' for " 'information about installing additional components.', exitCode: 1); } final DevicesCommandOutput output = DevicesCommandOutput( platform: globals.platform, logger: globals.logger, deviceManager: globals.deviceManager, deviceDiscoveryTimeout: deviceDiscoveryTimeout, deviceConnectionInterface: deviceConnectionInterface, ); await output.findAndOutputAllTargetDevices( machine: boolArg('machine'), ); return FlutterCommandResult.success(); } } class DevicesCommandOutput { factory DevicesCommandOutput({ required Platform platform, required Logger logger, DeviceManager? deviceManager, Duration? deviceDiscoveryTimeout, DeviceConnectionInterface? deviceConnectionInterface, }) { if (platform.isMacOS) { return DevicesCommandOutputWithExtendedWirelessDeviceDiscovery( logger: logger, deviceManager: deviceManager, deviceDiscoveryTimeout: deviceDiscoveryTimeout, deviceConnectionInterface: deviceConnectionInterface, ); } return DevicesCommandOutput._private( logger: logger, deviceManager: deviceManager, deviceDiscoveryTimeout: deviceDiscoveryTimeout, deviceConnectionInterface: deviceConnectionInterface, ); } DevicesCommandOutput._private({ required Logger logger, required DeviceManager? deviceManager, required this.deviceDiscoveryTimeout, required this.deviceConnectionInterface, }) : _deviceManager = deviceManager, _logger = logger; final DeviceManager? _deviceManager; final Logger _logger; final Duration? deviceDiscoveryTimeout; final DeviceConnectionInterface? deviceConnectionInterface; bool get _includeAttachedDevices => deviceConnectionInterface == null || deviceConnectionInterface == DeviceConnectionInterface.attached; bool get _includeWirelessDevices => deviceConnectionInterface == null || deviceConnectionInterface == DeviceConnectionInterface.wireless; Future<List<Device>> _getAttachedDevices(DeviceManager deviceManager) async { if (!_includeAttachedDevices) { return <Device>[]; } return deviceManager.getAllDevices( filter: DeviceDiscoveryFilter( deviceConnectionInterface: DeviceConnectionInterface.attached, ), ); } Future<List<Device>> _getWirelessDevices(DeviceManager deviceManager) async { if (!_includeWirelessDevices) { return <Device>[]; } return deviceManager.getAllDevices( filter: DeviceDiscoveryFilter( deviceConnectionInterface: DeviceConnectionInterface.wireless, ), ); } Future<void> findAndOutputAllTargetDevices({required bool machine}) async { List<Device> attachedDevices = <Device>[]; List<Device> wirelessDevices = <Device>[]; final DeviceManager? deviceManager = _deviceManager; if (deviceManager != null) { // Refresh the cache and then get the attached and wireless devices from // the cache. await deviceManager.refreshAllDevices(timeout: deviceDiscoveryTimeout); attachedDevices = await _getAttachedDevices(deviceManager); wirelessDevices = await _getWirelessDevices(deviceManager); } final List<Device> allDevices = attachedDevices + wirelessDevices; if (machine) { await printDevicesAsJson(allDevices); return; } if (allDevices.isEmpty) { _printNoDevicesDetected(); } else { if (attachedDevices.isNotEmpty) { _logger.printStatus('${attachedDevices.length} connected ${pluralize('device', attachedDevices.length)}:\n'); await Device.printDevices(attachedDevices, _logger); } if (wirelessDevices.isNotEmpty) { if (attachedDevices.isNotEmpty) { _logger.printStatus(''); } _logger.printStatus('${wirelessDevices.length} wirelessly connected ${pluralize('device', wirelessDevices.length)}:\n'); await Device.printDevices(wirelessDevices, _logger); } } await _printDiagnostics(); } void _printNoDevicesDetected() { final StringBuffer status = StringBuffer('No devices detected.'); status.writeln(); status.writeln(); status.writeln('Run "flutter emulators" to list and start any available device emulators.'); status.writeln(); status.write('If you expected your device to be detected, please run "flutter doctor" to diagnose potential issues. '); if (deviceDiscoveryTimeout == null) { status.write('You may also try increasing the time to wait for connected devices with the --${FlutterOptions.kDeviceTimeout} flag. '); } status.write('Visit https://flutter.dev/setup/ for troubleshooting tips.'); _logger.printStatus(status.toString()); } Future<void> _printDiagnostics() async { final List<String> diagnostics = await _deviceManager?.getDeviceDiagnostics() ?? <String>[]; if (diagnostics.isNotEmpty) { _logger.printStatus(''); for (final String diagnostic in diagnostics) { _logger.printStatus('• $diagnostic', hangingIndent: 2); } } } Future<void> printDevicesAsJson(List<Device> devices) async { _logger.printStatus( const JsonEncoder.withIndent(' ').convert( await Future.wait(devices.map((Device d) => d.toJson())) ) ); } } const String _checkingForWirelessDevicesMessage = 'Checking for wireless devices...'; const String _noAttachedCheckForWireless = 'No devices found yet. Checking for wireless devices...'; const String _noWirelessDevicesFoundMessage = 'No wireless devices were found.'; class DevicesCommandOutputWithExtendedWirelessDeviceDiscovery extends DevicesCommandOutput { DevicesCommandOutputWithExtendedWirelessDeviceDiscovery({ required super.logger, super.deviceManager, super.deviceDiscoveryTimeout, super.deviceConnectionInterface, }) : super._private(); @override Future<void> findAndOutputAllTargetDevices({required bool machine}) async { // When a user defines the timeout or filters to only attached devices, // use the super function that does not do longer wireless device discovery. if (deviceDiscoveryTimeout != null || deviceConnectionInterface == DeviceConnectionInterface.attached) { return super.findAndOutputAllTargetDevices(machine: machine); } if (machine) { final List<Device> devices = await _deviceManager?.refreshAllDevices( filter: DeviceDiscoveryFilter( deviceConnectionInterface: deviceConnectionInterface, ), timeout: DeviceManager.minimumWirelessDeviceDiscoveryTimeout, ) ?? <Device>[]; await printDevicesAsJson(devices); return; } final Future<void>? extendedWirelessDiscovery = _deviceManager?.refreshExtendedWirelessDeviceDiscoverers( timeout: DeviceManager.minimumWirelessDeviceDiscoveryTimeout, ); List<Device> attachedDevices = <Device>[]; final DeviceManager? deviceManager = _deviceManager; if (deviceManager != null) { attachedDevices = await _getAttachedDevices(deviceManager); } // Number of lines to clear starts at 1 because it's inclusive of the line // the cursor is on, which will be blank for this use case. int numLinesToClear = 1; // Display list of attached devices. if (attachedDevices.isNotEmpty) { _logger.printStatus('${attachedDevices.length} connected ${pluralize('device', attachedDevices.length)}:\n'); await Device.printDevices(attachedDevices, _logger); _logger.printStatus(''); numLinesToClear += 1; } // Display waiting message. if (attachedDevices.isEmpty && _includeAttachedDevices) { _logger.printStatus(_noAttachedCheckForWireless); } else { _logger.printStatus(_checkingForWirelessDevicesMessage); } numLinesToClear += 1; final Status waitingStatus = _logger.startSpinner(); await extendedWirelessDiscovery; List<Device> wirelessDevices = <Device>[]; if (deviceManager != null) { wirelessDevices = await _getWirelessDevices(deviceManager); } waitingStatus.stop(); final Terminal terminal = _logger.terminal; if (_logger.isVerbose && _includeAttachedDevices) { // Reprint the attach devices. if (attachedDevices.isNotEmpty) { _logger.printStatus('\n${attachedDevices.length} connected ${pluralize('device', attachedDevices.length)}:\n'); await Device.printDevices(attachedDevices, _logger); } } else if (terminal.supportsColor && terminal is AnsiTerminal) { _logger.printStatus( terminal.clearLines(numLinesToClear), newline: false, ); } if (attachedDevices.isNotEmpty || !_logger.terminal.supportsColor) { _logger.printStatus(''); } if (wirelessDevices.isEmpty) { if (attachedDevices.isEmpty) { // No wireless or attached devices were found. _printNoDevicesDetected(); } else { // Attached devices found, wireless devices not found. _logger.printStatus(_noWirelessDevicesFoundMessage); } } else { // Display list of wireless devices. _logger.printStatus('${wirelessDevices.length} wirelessly connected ${pluralize('device', wirelessDevices.length)}:\n'); await Device.printDevices(wirelessDevices, _logger); } await _printDiagnostics(); } }