blob: b71259248d52ae4c19e0872fe481e6b5f3ea658f [file] [log] [blame]
// 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);
}
}