devices.dart 11.4 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import '../base/common.dart';
6 7 8
import '../base/logger.dart';
import '../base/platform.dart';
import '../base/terminal.dart';
9
import '../base/utils.dart';
10
import '../convert.dart';
11
import '../device.dart';
12
import '../globals.dart' as globals;
13
import '../runner/flutter_command.dart';
14

15
class DevicesCommand extends FlutterCommand {
16
  DevicesCommand({ bool verboseHelp = false }) {
17 18
    argParser.addFlag('machine',
      negatable: false,
19
      help: 'Output device information in machine readable structured JSON format.',
20
    );
21 22 23
    argParser.addOption(
      'timeout',
      abbr: 't',
24 25
      help: '(deprecated) This option has been replaced by "--${FlutterOptions.kDeviceTimeout}".',
      hide: !verboseHelp,
26
    );
27
    usesDeviceTimeoutOption();
28
    usesDeviceConnectionOption();
29 30
  }

31
  @override
32
  final String name = 'devices';
33 34

  @override
35
  final String description = 'List all connected devices.';
36

37 38 39
  @override
  final String category = FlutterCommandCategory.tools;

40
  @override
41 42
  Duration? get deviceDiscoveryTimeout {
    if (argResults?['timeout'] != null) {
43
      final int? timeoutSeconds = int.tryParse(stringArg('timeout')!);
44
      if (timeoutSeconds == null) {
45
        throwToolExit('Could not parse -t/--timeout argument. It must be an integer.');
46
      }
47 48 49 50 51 52 53
      return Duration(seconds: timeoutSeconds);
    }
    return super.deviceDiscoveryTimeout;
  }

  @override
  Future<void> validateCommand() {
54
    if (argResults?['timeout'] != null) {
55
      globals.printWarning('${globals.logger.terminal.warningMark} The "--timeout" argument is deprecated; use "--${FlutterOptions.kDeviceTimeout}" instead.');
56
    }
57
    return super.validateCommand();
58 59
  }

60
  @override
61
  Future<FlutterCommandResult> runCommand() async {
62
    if (globals.doctor?.canListAnything != true) {
63 64
      throwToolExit(
        "Unable to locate a development device; please run 'flutter doctor' for "
65
        'information about installing additional components.',
66
        exitCode: 1);
67 68
    }

69
    final DevicesCommandOutput output = DevicesCommandOutput(
70 71 72
      platform: globals.platform,
      logger: globals.logger,
      deviceManager: globals.deviceManager,
73
      deviceDiscoveryTimeout: deviceDiscoveryTimeout,
74
      deviceConnectionInterface: deviceConnectionInterface,
75 76 77
    );

    await output.findAndOutputAllTargetDevices(
78
      machine: boolArg('machine'),
79 80 81 82 83 84 85
    );

    return FlutterCommandResult.success();
  }
}

class DevicesCommandOutput {
86 87 88 89 90
  factory DevicesCommandOutput({
    required Platform platform,
    required Logger logger,
    DeviceManager? deviceManager,
    Duration? deviceDiscoveryTimeout,
91
    DeviceConnectionInterface? deviceConnectionInterface,
92 93 94 95 96 97
  }) {
    if (platform.isMacOS) {
      return DevicesCommandOutputWithExtendedWirelessDeviceDiscovery(
        logger: logger,
        deviceManager: deviceManager,
        deviceDiscoveryTimeout: deviceDiscoveryTimeout,
98
        deviceConnectionInterface: deviceConnectionInterface,
99 100 101 102 103 104
      );
    }
    return DevicesCommandOutput._private(
      logger: logger,
      deviceManager: deviceManager,
      deviceDiscoveryTimeout: deviceDiscoveryTimeout,
105
      deviceConnectionInterface: deviceConnectionInterface,
106 107 108 109 110 111 112
    );
  }

  DevicesCommandOutput._private({
    required Logger logger,
    required DeviceManager? deviceManager,
    required this.deviceDiscoveryTimeout,
113
    required this.deviceConnectionInterface,
114 115
  })  : _deviceManager = deviceManager,
        _logger = logger;
116

117 118
  final DeviceManager? _deviceManager;
  final Logger _logger;
119
  final Duration? deviceDiscoveryTimeout;
120 121 122 123 124 125 126 127 128
  final DeviceConnectionInterface? deviceConnectionInterface;

  bool get _includeAttachedDevices =>
      deviceConnectionInterface == null ||
      deviceConnectionInterface == DeviceConnectionInterface.attached;

  bool get _includeWirelessDevices =>
      deviceConnectionInterface == null ||
      deviceConnectionInterface == DeviceConnectionInterface.wireless;
129

130
  Future<List<Device>> _getAttachedDevices(DeviceManager deviceManager) async {
131 132 133
    if (!_includeAttachedDevices) {
      return <Device>[];
    }
134 135 136 137 138 139 140 141
    return deviceManager.getAllDevices(
      filter: DeviceDiscoveryFilter(
        deviceConnectionInterface: DeviceConnectionInterface.attached,
      ),
    );
  }

  Future<List<Device>> _getWirelessDevices(DeviceManager deviceManager) async {
142 143 144
    if (!_includeWirelessDevices) {
      return <Device>[];
    }
145 146 147 148 149 150 151
    return deviceManager.getAllDevices(
      filter: DeviceDiscoveryFilter(
        deviceConnectionInterface: DeviceConnectionInterface.wireless,
      ),
    );
  }

152
  Future<void> findAndOutputAllTargetDevices({required bool machine}) async {
153 154
    List<Device> attachedDevices = <Device>[];
    List<Device> wirelessDevices = <Device>[];
155
    final DeviceManager? deviceManager = _deviceManager;
156 157 158 159 160 161 162 163
    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;
164

165
    if (machine) {
166 167 168 169 170
      await printDevicesAsJson(allDevices);
      return;
    }

    if (allDevices.isEmpty) {
171
      _logger.printStatus('No authorized devices detected.');
172
    } else {
173
      if (attachedDevices.isNotEmpty) {
174 175
        _logger.printStatus('Found ${attachedDevices.length} connected ${pluralize('device', attachedDevices.length)}:');
        await Device.printDevices(attachedDevices, _logger, prefix: '  ');
176 177 178
      }
      if (wirelessDevices.isNotEmpty) {
        if (attachedDevices.isNotEmpty) {
179
          _logger.printStatus('');
180
        }
181 182
        _logger.printStatus('Found ${wirelessDevices.length} wirelessly connected ${pluralize('device', wirelessDevices.length)}:');
        await Device.printDevices(wirelessDevices, _logger, prefix: '  ');
183
      }
184
    }
185
    await _printDiagnostics(foundAny: allDevices.isNotEmpty);
186 187
  }

188 189
  Future<void> _printDiagnostics({ required bool foundAny }) async {
    final StringBuffer status = StringBuffer();
190
    status.writeln();
191 192 193 194 195 196 197
    final List<String> diagnostics = await _deviceManager?.getDeviceDiagnostics() ?? <String>[];
    if (diagnostics.isNotEmpty) {
      for (final String diagnostic in diagnostics) {
        status.writeln(diagnostic);
        status.writeln();
      }
    }
198 199
    status.writeln('Run "flutter emulators" to list and start any available device emulators.');
    status.writeln();
200
    status.write('If you expected ${ foundAny ? 'another' : 'a' } device to be detected, please run "flutter doctor" to diagnose potential issues. ');
201
    if (deviceDiscoveryTimeout == null) {
202
      status.write('You may also try increasing the time to wait for connected devices with the "--${FlutterOptions.kDeviceTimeout}" flag. ');
203 204
    }
    status.write('Visit https://flutter.dev/setup/ for troubleshooting tips.');
205
    _logger.printStatus(status.toString());
206
  }
207 208

  Future<void> printDevicesAsJson(List<Device> devices) async {
209
    _logger.printStatus(
210 211 212 213 214
      const JsonEncoder.withIndent('  ').convert(
        await Future.wait(devices.map((Device d) => d.toJson()))
      )
    );
  }
215
}
216 217 218 219 220 221 222 223 224 225

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,
226
    super.deviceConnectionInterface,
227 228 229 230
  }) : super._private();

  @override
  Future<void> findAndOutputAllTargetDevices({required bool machine}) async {
231 232 233
    // 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) {
234 235 236 237 238
      return super.findAndOutputAllTargetDevices(machine: machine);
    }

    if (machine) {
      final List<Device> devices = await _deviceManager?.refreshAllDevices(
239 240 241
        filter: DeviceDiscoveryFilter(
          deviceConnectionInterface: deviceConnectionInterface,
        ),
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
        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) {
264 265
      _logger.printStatus('Found ${attachedDevices.length} connected ${pluralize('device', attachedDevices.length)}:');
      await Device.printDevices(attachedDevices, _logger, prefix: '  ');
266 267 268 269 270
      _logger.printStatus('');
      numLinesToClear += 1;
    }

    // Display waiting message.
271
    if (attachedDevices.isEmpty && _includeAttachedDevices) {
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
      _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;
287
    if (_logger.isVerbose && _includeAttachedDevices) {
288 289
      // Reprint the attach devices.
      if (attachedDevices.isNotEmpty) {
290 291
        _logger.printStatus('\nFound ${attachedDevices.length} connected ${pluralize('device', attachedDevices.length)}:');
        await Device.printDevices(attachedDevices, _logger, prefix: '  ');
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306
      }
    } 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.
307
        _logger.printStatus('No authorized devices detected.');
308 309 310 311 312 313
      } else {
        // Attached devices found, wireless devices not found.
        _logger.printStatus(_noWirelessDevicesFoundMessage);
      }
    } else {
      // Display list of wireless devices.
314 315
      _logger.printStatus('Found ${wirelessDevices.length} wirelessly connected ${pluralize('device', wirelessDevices.length)}:');
      await Device.printDevices(wirelessDevices, _logger, prefix: '  ');
316
    }
317
    await _printDiagnostics(foundAny: wirelessDevices.isNotEmpty || attachedDevices.isNotEmpty);
318 319
  }
}