| // 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) { |
| _logger.printStatus('No authorized devices detected.'); |
| } else { |
| if (attachedDevices.isNotEmpty) { |
| _logger.printStatus('Found ${attachedDevices.length} connected ${pluralize('device', attachedDevices.length)}:'); |
| await Device.printDevices(attachedDevices, _logger, prefix: ' '); |
| } |
| if (wirelessDevices.isNotEmpty) { |
| if (attachedDevices.isNotEmpty) { |
| _logger.printStatus(''); |
| } |
| _logger.printStatus('Found ${wirelessDevices.length} wirelessly connected ${pluralize('device', wirelessDevices.length)}:'); |
| await Device.printDevices(wirelessDevices, _logger, prefix: ' '); |
| } |
| } |
| await _printDiagnostics(foundAny: allDevices.isNotEmpty); |
| } |
| |
| Future<void> _printDiagnostics({ required bool foundAny }) async { |
| final StringBuffer status = StringBuffer(); |
| status.writeln(); |
| final List<String> diagnostics = await _deviceManager?.getDeviceDiagnostics() ?? <String>[]; |
| if (diagnostics.isNotEmpty) { |
| for (final String diagnostic in diagnostics) { |
| status.writeln(diagnostic); |
| status.writeln(); |
| } |
| } |
| status.writeln('Run "flutter emulators" to list and start any available device emulators.'); |
| status.writeln(); |
| status.write('If you expected ${ foundAny ? 'another' : 'a' } 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> 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('Found ${attachedDevices.length} connected ${pluralize('device', attachedDevices.length)}:'); |
| await Device.printDevices(attachedDevices, _logger, prefix: ' '); |
| _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('\nFound ${attachedDevices.length} connected ${pluralize('device', attachedDevices.length)}:'); |
| await Device.printDevices(attachedDevices, _logger, prefix: ' '); |
| } |
| } 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. |
| _logger.printStatus('No authorized devices detected.'); |
| } else { |
| // Attached devices found, wireless devices not found. |
| _logger.printStatus(_noWirelessDevicesFoundMessage); |
| } |
| } else { |
| // Display list of wireless devices. |
| _logger.printStatus('Found ${wirelessDevices.length} wirelessly connected ${pluralize('device', wirelessDevices.length)}:'); |
| await Device.printDevices(wirelessDevices, _logger, prefix: ' '); |
| } |
| await _printDiagnostics(foundAny: wirelessDevices.isNotEmpty || attachedDevices.isNotEmpty); |
| } |
| } |