Unverified Commit fa01649a authored by Victoria Ashworth's avatar Victoria Ashworth Committed by GitHub

Update device selection to wait for wireless devices to load (#122932)

Update device selection to wait for wireless devices to load
parent 820ec70a
...@@ -216,6 +216,7 @@ abstract class Logger { ...@@ -216,6 +216,7 @@ abstract class Logger {
VoidCallback? onFinish, VoidCallback? onFinish,
Duration? timeout, Duration? timeout,
SlowWarningCallback? slowWarningCallback, SlowWarningCallback? slowWarningCallback,
TerminalColor? warningColor,
}); });
/// Send an event to be emitted. /// Send an event to be emitted.
...@@ -376,11 +377,13 @@ class DelegatingLogger implements Logger { ...@@ -376,11 +377,13 @@ class DelegatingLogger implements Logger {
VoidCallback? onFinish, VoidCallback? onFinish,
Duration? timeout, Duration? timeout,
SlowWarningCallback? slowWarningCallback, SlowWarningCallback? slowWarningCallback,
TerminalColor? warningColor,
}) { }) {
return _delegate.startSpinner( return _delegate.startSpinner(
onFinish: onFinish, onFinish: onFinish,
timeout: timeout, timeout: timeout,
slowWarningCallback: slowWarningCallback, slowWarningCallback: slowWarningCallback,
warningColor: warningColor,
); );
} }
...@@ -587,6 +590,7 @@ class StdoutLogger extends Logger { ...@@ -587,6 +590,7 @@ class StdoutLogger extends Logger {
VoidCallback? onFinish, VoidCallback? onFinish,
Duration? timeout, Duration? timeout,
SlowWarningCallback? slowWarningCallback, SlowWarningCallback? slowWarningCallback,
TerminalColor? warningColor,
}) { }) {
if (_status != null || !supportsColor) { if (_status != null || !supportsColor) {
return SilentStatus( return SilentStatus(
...@@ -606,6 +610,7 @@ class StdoutLogger extends Logger { ...@@ -606,6 +610,7 @@ class StdoutLogger extends Logger {
terminal: terminal, terminal: terminal,
timeout: timeout, timeout: timeout,
slowWarningCallback: slowWarningCallback, slowWarningCallback: slowWarningCallback,
warningColor: warningColor,
)..start(); )..start();
return _status!; return _status!;
} }
...@@ -888,6 +893,7 @@ class BufferLogger extends Logger { ...@@ -888,6 +893,7 @@ class BufferLogger extends Logger {
VoidCallback? onFinish, VoidCallback? onFinish,
Duration? timeout, Duration? timeout,
SlowWarningCallback? slowWarningCallback, SlowWarningCallback? slowWarningCallback,
TerminalColor? warningColor,
}) { }) {
return SilentStatus( return SilentStatus(
stopwatch: _stopwatchFactory.createStopwatch(), stopwatch: _stopwatchFactory.createStopwatch(),
...@@ -1269,6 +1275,7 @@ class AnonymousSpinnerStatus extends Status { ...@@ -1269,6 +1275,7 @@ class AnonymousSpinnerStatus extends Status {
required Stdio stdio, required Stdio stdio,
required Terminal terminal, required Terminal terminal,
this.slowWarningCallback, this.slowWarningCallback,
this.warningColor,
super.timeout, super.timeout,
}) : _stdio = stdio, }) : _stdio = stdio,
_terminal = terminal, _terminal = terminal,
...@@ -1278,6 +1285,7 @@ class AnonymousSpinnerStatus extends Status { ...@@ -1278,6 +1285,7 @@ class AnonymousSpinnerStatus extends Status {
final Terminal _terminal; final Terminal _terminal;
String _slowWarning = ''; String _slowWarning = '';
final SlowWarningCallback? slowWarningCallback; final SlowWarningCallback? slowWarningCallback;
final TerminalColor? warningColor;
static const String _backspaceChar = '\b'; static const String _backspaceChar = '\b';
static const String _clearChar = ' '; static const String _clearChar = ' ';
...@@ -1360,8 +1368,15 @@ class AnonymousSpinnerStatus extends Status { ...@@ -1360,8 +1368,15 @@ class AnonymousSpinnerStatus extends Status {
_clear(_currentLineLength - _lastAnimationFrameLength); _clear(_currentLineLength - _lastAnimationFrameLength);
} }
} }
if (_slowWarning == '' && slowWarningCallback != null) { final SlowWarningCallback? callback = slowWarningCallback;
_slowWarning = slowWarningCallback!(); if (_slowWarning.isEmpty && callback != null) {
final TerminalColor? color = warningColor;
if (color != null) {
_slowWarning = _terminal.color(callback(), color);
} else {
_slowWarning = callback();
}
_writeToStdOut(_slowWarning); _writeToStdOut(_slowWarning);
} }
} }
......
...@@ -175,6 +175,15 @@ class AnsiTerminal implements Terminal { ...@@ -175,6 +175,15 @@ class AnsiTerminal implements Terminal {
static const String yellow = '\u001b[33m'; static const String yellow = '\u001b[33m';
static const String grey = '\u001b[90m'; static const String grey = '\u001b[90m';
// Moves cursor up 1 line.
static const String cursorUpLineCode = '\u001b[1A';
// Moves cursor to the beginning of the line.
static const String cursorBeginningOfLineCode = '\u001b[1G';
// Clear the entire line, cursor position does not change.
static const String clearEntireLineCode = '\u001b[2K';
static const Map<TerminalColor, String> _colorMap = <TerminalColor, String>{ static const Map<TerminalColor, String> _colorMap = <TerminalColor, String>{
TerminalColor.red: red, TerminalColor.red: red,
TerminalColor.green: green, TerminalColor.green: green,
...@@ -268,6 +277,19 @@ class AnsiTerminal implements Terminal { ...@@ -268,6 +277,19 @@ class AnsiTerminal implements Terminal {
@override @override
String clearScreen() => supportsColor ? clear : '\n\n'; String clearScreen() => supportsColor ? clear : '\n\n';
/// Returns ANSI codes to clear [numberOfLines] lines starting with the line
/// the cursor is on.
///
/// If the terminal does not support ANSI codes, returns an empty string.
String clearLines(int numberOfLines) {
if (!supportsColor) {
return '';
}
return cursorBeginningOfLineCode +
clearEntireLineCode +
(cursorUpLineCode + clearEntireLineCode) * (numberOfLines - 1);
}
@override @override
bool get singleCharMode { bool get singleCharMode {
if (!_stdio.stdinHasTerminal) { if (!_stdio.stdinHasTerminal) {
......
...@@ -272,7 +272,6 @@ class UserMessages { ...@@ -272,7 +272,6 @@ class UserMessages {
String get flutterFoundButUnsupportedDevices => 'The following devices were found, but are not supported by this project:'; String get flutterFoundButUnsupportedDevices => 'The following devices were found, but are not supported by this project:';
String flutterFoundSpecifiedDevices(int count, String deviceId) => String flutterFoundSpecifiedDevices(int count, String deviceId) =>
'Found $count devices with name or id matching $deviceId:'; 'Found $count devices with name or id matching $deviceId:';
String get flutterMultipleDevicesFound => 'Multiple devices found:';
String flutterChooseDevice(int option, String name, String deviceId) => '[$option]: $name ($deviceId)'; String flutterChooseDevice(int option, String name, String deviceId) => '[$option]: $name ($deviceId)';
String get flutterChooseOne => 'Please choose one (or "q" to quit)'; String get flutterChooseOne => 'Please choose one (or "q" to quit)';
String get flutterSpecifyDeviceWithAllOption => String get flutterSpecifyDeviceWithAllOption =>
......
...@@ -175,6 +175,9 @@ known, it can be explicitly provided to attach via the command-line, e.g. ...@@ -175,6 +175,9 @@ known, it can be explicitly provided to attach via the command-line, e.g.
@override @override
final String category = FlutterCommandCategory.tools; final String category = FlutterCommandCategory.tools;
@override
bool get refreshWirelessDevices => true;
int? get debugPort { int? get debugPort {
if (argResults!['debug-port'] == null) { if (argResults!['debug-port'] == null) {
return null; return null;
......
...@@ -878,12 +878,12 @@ class DeviceDomain extends Domain { ...@@ -878,12 +878,12 @@ class DeviceDomain extends Domain {
final List<PollingDeviceDiscovery> _discoverers = <PollingDeviceDiscovery>[]; final List<PollingDeviceDiscovery> _discoverers = <PollingDeviceDiscovery>[];
/// Return a list of the current devices, with each device represented as a map /// Return a list of the currently connected devices, with each device
/// of properties (id, name, platform, ...). /// represented as a map of properties (id, name, platform, ...).
Future<List<Map<String, Object?>>> getDevices([ Map<String, Object?>? args ]) async { Future<List<Map<String, Object?>>> getDevices([ Map<String, Object?>? args ]) async {
return <Map<String, Object?>>[ return <Map<String, Object?>>[
for (final PollingDeviceDiscovery discoverer in _discoverers) for (final PollingDeviceDiscovery discoverer in _discoverers)
for (final Device device in await discoverer.devices()) for (final Device device in await discoverer.devices(filter: DeviceDiscoveryFilter()))
await _deviceToMap(device), await _deviceToMap(device),
]; ];
} }
...@@ -1066,10 +1066,12 @@ class DeviceDomain extends Domain { ...@@ -1066,10 +1066,12 @@ class DeviceDomain extends Domain {
return Future<void>.value(); return Future<void>.value();
} }
/// Return the device matching the deviceId field in the args. /// Return the connected device matching the deviceId field in the args.
Future<Device?> _getDevice(String? deviceId) async { Future<Device?> _getDevice(String? deviceId) async {
for (final PollingDeviceDiscovery discoverer in _discoverers) { for (final PollingDeviceDiscovery discoverer in _discoverers) {
final List<Device> devices = await discoverer.devices(); final List<Device> devices = await discoverer.devices(
filter: DeviceDiscoveryFilter(),
);
Device? device; Device? device;
for (final Device localDevice in devices) { for (final Device localDevice in devices) {
if (localDevice.id == deviceId) { if (localDevice.id == deviceId) {
......
...@@ -3,6 +3,9 @@ ...@@ -3,6 +3,9 @@
// found in the LICENSE file. // found in the LICENSE file.
import '../base/common.dart'; import '../base/common.dart';
import '../base/logger.dart';
import '../base/platform.dart';
import '../base/terminal.dart';
import '../base/utils.dart'; import '../base/utils.dart';
import '../convert.dart'; import '../convert.dart';
import '../device.dart'; import '../device.dart';
...@@ -63,6 +66,9 @@ class DevicesCommand extends FlutterCommand { ...@@ -63,6 +66,9 @@ class DevicesCommand extends FlutterCommand {
} }
final DevicesCommandOutput output = DevicesCommandOutput( final DevicesCommandOutput output = DevicesCommandOutput(
platform: globals.platform,
logger: globals.logger,
deviceManager: globals.deviceManager,
deviceDiscoveryTimeout: deviceDiscoveryTimeout, deviceDiscoveryTimeout: deviceDiscoveryTimeout,
); );
...@@ -75,8 +81,35 @@ class DevicesCommand extends FlutterCommand { ...@@ -75,8 +81,35 @@ class DevicesCommand extends FlutterCommand {
} }
class DevicesCommandOutput { class DevicesCommandOutput {
DevicesCommandOutput({this.deviceDiscoveryTimeout}); factory DevicesCommandOutput({
required Platform platform,
required Logger logger,
DeviceManager? deviceManager,
Duration? deviceDiscoveryTimeout,
}) {
if (platform.isMacOS) {
return DevicesCommandOutputWithExtendedWirelessDeviceDiscovery(
logger: logger,
deviceManager: deviceManager,
deviceDiscoveryTimeout: deviceDiscoveryTimeout,
);
}
return DevicesCommandOutput._private(
logger: logger,
deviceManager: deviceManager,
deviceDiscoveryTimeout: deviceDiscoveryTimeout,
);
}
DevicesCommandOutput._private({
required Logger logger,
required DeviceManager? deviceManager,
required this.deviceDiscoveryTimeout,
}) : _deviceManager = deviceManager,
_logger = logger;
final DeviceManager? _deviceManager;
final Logger _logger;
final Duration? deviceDiscoveryTimeout; final Duration? deviceDiscoveryTimeout;
Future<List<Device>> _getAttachedDevices(DeviceManager deviceManager) async { Future<List<Device>> _getAttachedDevices(DeviceManager deviceManager) async {
...@@ -98,7 +131,7 @@ class DevicesCommandOutput { ...@@ -98,7 +131,7 @@ class DevicesCommandOutput {
Future<void> findAndOutputAllTargetDevices({required bool machine}) async { Future<void> findAndOutputAllTargetDevices({required bool machine}) async {
List<Device> attachedDevices = <Device>[]; List<Device> attachedDevices = <Device>[];
List<Device> wirelessDevices = <Device>[]; List<Device> wirelessDevices = <Device>[];
final DeviceManager? deviceManager = globals.deviceManager; final DeviceManager? deviceManager = _deviceManager;
if (deviceManager != null) { if (deviceManager != null) {
// Refresh the cache and then get the attached and wireless devices from // Refresh the cache and then get the attached and wireless devices from
// the cache. // the cache.
...@@ -117,15 +150,15 @@ class DevicesCommandOutput { ...@@ -117,15 +150,15 @@ class DevicesCommandOutput {
_printNoDevicesDetected(); _printNoDevicesDetected();
} else { } else {
if (attachedDevices.isNotEmpty) { if (attachedDevices.isNotEmpty) {
globals.printStatus('${attachedDevices.length} connected ${pluralize('device', attachedDevices.length)}:\n'); _logger.printStatus('${attachedDevices.length} connected ${pluralize('device', attachedDevices.length)}:\n');
await Device.printDevices(attachedDevices, globals.logger); await Device.printDevices(attachedDevices, _logger);
} }
if (wirelessDevices.isNotEmpty) { if (wirelessDevices.isNotEmpty) {
if (attachedDevices.isNotEmpty) { if (attachedDevices.isNotEmpty) {
globals.printStatus(''); _logger.printStatus('');
} }
globals.printStatus('${wirelessDevices.length} wirelessly connected ${pluralize('device', wirelessDevices.length)}:\n'); _logger.printStatus('${wirelessDevices.length} wirelessly connected ${pluralize('device', wirelessDevices.length)}:\n');
await Device.printDevices(wirelessDevices, globals.logger); await Device.printDevices(wirelessDevices, _logger);
} }
} }
await _printDiagnostics(); await _printDiagnostics();
...@@ -143,24 +176,125 @@ class DevicesCommandOutput { ...@@ -143,24 +176,125 @@ class DevicesCommandOutput {
} }
status.write('Visit https://flutter.dev/setup/ for troubleshooting tips.'); status.write('Visit https://flutter.dev/setup/ for troubleshooting tips.');
globals.printStatus(status.toString()); _logger.printStatus(status.toString());
} }
Future<void> _printDiagnostics() async { Future<void> _printDiagnostics() async {
final List<String> diagnostics = await globals.deviceManager?.getDeviceDiagnostics() ?? <String>[]; final List<String> diagnostics = await _deviceManager?.getDeviceDiagnostics() ?? <String>[];
if (diagnostics.isNotEmpty) { if (diagnostics.isNotEmpty) {
globals.printStatus(''); _logger.printStatus('');
for (final String diagnostic in diagnostics) { for (final String diagnostic in diagnostics) {
globals.printStatus('• $diagnostic', hangingIndent: 2); _logger.printStatus('• $diagnostic', hangingIndent: 2);
} }
} }
} }
Future<void> printDevicesAsJson(List<Device> devices) async { Future<void> printDevicesAsJson(List<Device> devices) async {
globals.printStatus( _logger.printStatus(
const JsonEncoder.withIndent(' ').convert( const JsonEncoder.withIndent(' ').convert(
await Future.wait(devices.map((Device d) => d.toJson())) 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._private();
@override
Future<void> findAndOutputAllTargetDevices({required bool machine}) async {
// When a user defines the timeout, use the super function that does not do
// longer wireless device discovery.
if (deviceDiscoveryTimeout != null) {
return super.findAndOutputAllTargetDevices(machine: machine);
}
if (machine) {
final List<Device> devices = await _deviceManager?.refreshAllDevices(
filter: DeviceDiscoveryFilter(),
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) {
_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) {
// 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();
}
}
...@@ -35,6 +35,9 @@ class InstallCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts ...@@ -35,6 +35,9 @@ class InstallCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts
@override @override
final String category = FlutterCommandCategory.tools; final String category = FlutterCommandCategory.tools;
@override
bool get refreshWirelessDevices => true;
Device? device; Device? device;
bool get uninstallOnly => boolArg('uninstall-only'); bool get uninstallOnly => boolArg('uninstall-only');
......
...@@ -30,6 +30,9 @@ class LogsCommand extends FlutterCommand { ...@@ -30,6 +30,9 @@ class LogsCommand extends FlutterCommand {
@override @override
final String category = FlutterCommandCategory.tools; final String category = FlutterCommandCategory.tools;
@override
bool get refreshWirelessDevices => true;
@override @override
Future<Set<DevelopmentArtifact>> get requiredArtifacts async => const <DevelopmentArtifact>{}; Future<Set<DevelopmentArtifact>> get requiredArtifacts async => const <DevelopmentArtifact>{};
......
...@@ -199,6 +199,9 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment ...@@ -199,6 +199,9 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
bool get uninstallFirst => boolArg('uninstall-first'); bool get uninstallFirst => boolArg('uninstall-first');
bool get enableEmbedderApi => boolArg('enable-embedder-api'); bool get enableEmbedderApi => boolArg('enable-embedder-api');
@override
bool get refreshWirelessDevices => true;
@override @override
bool get reportNullSafety => true; bool get reportNullSafety => true;
......
...@@ -65,6 +65,9 @@ class ScreenshotCommand extends FlutterCommand { ...@@ -65,6 +65,9 @@ class ScreenshotCommand extends FlutterCommand {
@override @override
final String category = FlutterCommandCategory.tools; final String category = FlutterCommandCategory.tools;
@override
bool get refreshWirelessDevices => true;
@override @override
final List<String> aliases = <String>['pic']; final List<String> aliases = <String>['pic'];
......
...@@ -102,6 +102,11 @@ abstract class DeviceManager { ...@@ -102,6 +102,11 @@ abstract class DeviceManager {
_specifiedDeviceId = id; _specifiedDeviceId = id;
} }
/// A minimum duration to use when discovering wireless iOS devices.
static const Duration minimumWirelessDeviceDiscoveryTimeout = Duration(
seconds: 5,
);
/// True when the user has specified a single specific device. /// True when the user has specified a single specific device.
bool get hasSpecifiedDeviceId => specifiedDeviceId != null; bool get hasSpecifiedDeviceId => specifiedDeviceId != null;
...@@ -231,6 +236,22 @@ abstract class DeviceManager { ...@@ -231,6 +236,22 @@ abstract class DeviceManager {
return devices.expand<Device>((List<Device> deviceList) => deviceList).toList(); return devices.expand<Device>((List<Device> deviceList) => deviceList).toList();
} }
/// Discard existing cache of discoverers that are known to take longer to
/// discover wireless devices.
///
/// Then, search for devices for those discoverers to populate the cache for
/// no longer than [timeout].
Future<void> refreshExtendedWirelessDeviceDiscoverers({
Duration? timeout,
DeviceDiscoveryFilter? filter,
}) async {
await Future.wait<List<Device>>(<Future<List<Device>>>[
for (final DeviceDiscovery discoverer in _platformDiscoverers)
if (discoverer.requiresExtendedWirelessDeviceDiscovery)
discoverer.discoverDevices(timeout: timeout)
]);
}
/// Whether we're capable of listing any devices given the current environment configuration. /// Whether we're capable of listing any devices given the current environment configuration.
bool get canListAnything { bool get canListAnything {
return _platformDiscoverers.any((DeviceDiscovery discoverer) => discoverer.canListAnything); return _platformDiscoverers.any((DeviceDiscovery discoverer) => discoverer.canListAnything);
...@@ -434,6 +455,10 @@ abstract class DeviceDiscovery { ...@@ -434,6 +455,10 @@ abstract class DeviceDiscovery {
/// current environment configuration. /// current environment configuration.
bool get canListAnything; bool get canListAnything;
/// Whether this device discovery is known to take longer to discover
/// wireless devices.
bool get requiresExtendedWirelessDeviceDiscovery => false;
/// Return all connected devices, cached on subsequent calls. /// Return all connected devices, cached on subsequent calls.
Future<List<Device>> devices({DeviceDiscoveryFilter? filter}); Future<List<Device>> devices({DeviceDiscoveryFilter? filter});
...@@ -504,6 +529,8 @@ abstract class PollingDeviceDiscovery extends DeviceDiscovery { ...@@ -504,6 +529,8 @@ abstract class PollingDeviceDiscovery extends DeviceDiscovery {
/// Get devices from cache filtered by [filter]. /// Get devices from cache filtered by [filter].
/// ///
/// If the cache is empty, populate the cache. /// If the cache is empty, populate the cache.
///
/// If [filter] is null, it may return devices that are not connected.
@override @override
Future<List<Device>> devices({DeviceDiscoveryFilter? filter}) { Future<List<Device>> devices({DeviceDiscoveryFilter? filter}) {
return _populateDevices(filter: filter); return _populateDevices(filter: filter);
...@@ -512,6 +539,8 @@ abstract class PollingDeviceDiscovery extends DeviceDiscovery { ...@@ -512,6 +539,8 @@ abstract class PollingDeviceDiscovery extends DeviceDiscovery {
/// Empty the cache and repopulate it before getting devices from cache filtered by [filter]. /// Empty the cache and repopulate it before getting devices from cache filtered by [filter].
/// ///
/// Search for devices to populate the cache for no longer than [timeout]. /// Search for devices to populate the cache for no longer than [timeout].
///
/// If [filter] is null, it may return devices that are not connected.
@override @override
Future<List<Device>> discoverDevices({ Future<List<Device>> discoverDevices({
Duration? timeout, Duration? timeout,
......
...@@ -680,7 +680,9 @@ class DeviceValidator extends DoctorValidator { ...@@ -680,7 +680,9 @@ class DeviceValidator extends DoctorValidator {
@override @override
Future<ValidationResult> validate() async { Future<ValidationResult> validate() async {
final List<Device> devices = await _deviceManager.getAllDevices(); final List<Device> devices = await _deviceManager.refreshAllDevices(
timeout: DeviceManager.minimumWirelessDeviceDiscoveryTimeout,
);
List<ValidationMessage> installedMessages = <ValidationMessage>[]; List<ValidationMessage> installedMessages = <ValidationMessage>[];
if (devices.isNotEmpty) { if (devices.isNotEmpty) {
installedMessages = (await Device.descriptions(devices)) installedMessages = (await Device.descriptions(devices))
......
...@@ -35,26 +35,30 @@ import 'mac.dart'; ...@@ -35,26 +35,30 @@ import 'mac.dart';
class IOSDevices extends PollingDeviceDiscovery { class IOSDevices extends PollingDeviceDiscovery {
IOSDevices({ IOSDevices({
required Platform platform, required Platform platform,
required XCDevice xcdevice, required this.xcdevice,
required IOSWorkflow iosWorkflow, required IOSWorkflow iosWorkflow,
required Logger logger, required Logger logger,
}) : _platform = platform, }) : _platform = platform,
_xcdevice = xcdevice,
_iosWorkflow = iosWorkflow, _iosWorkflow = iosWorkflow,
_logger = logger, _logger = logger,
super('iOS devices'); super('iOS devices');
final Platform _platform; final Platform _platform;
final XCDevice _xcdevice;
final IOSWorkflow _iosWorkflow; final IOSWorkflow _iosWorkflow;
final Logger _logger; final Logger _logger;
@visibleForTesting
final XCDevice xcdevice;
@override @override
bool get supportsPlatform => _platform.isMacOS; bool get supportsPlatform => _platform.isMacOS;
@override @override
bool get canListAnything => _iosWorkflow.canListDevices; bool get canListAnything => _iosWorkflow.canListDevices;
@override
bool get requiresExtendedWirelessDeviceDiscovery => true;
StreamSubscription<Map<XCDeviceEvent, String>>? _observedDeviceEventsSubscription; StreamSubscription<Map<XCDeviceEvent, String>>? _observedDeviceEventsSubscription;
@override @override
...@@ -64,18 +68,22 @@ class IOSDevices extends PollingDeviceDiscovery { ...@@ -64,18 +68,22 @@ class IOSDevices extends PollingDeviceDiscovery {
'Control of iOS devices or simulators only supported on macOS.' 'Control of iOS devices or simulators only supported on macOS.'
); );
} }
if (!_xcdevice.isInstalled) { if (!xcdevice.isInstalled) {
return; return;
} }
deviceNotifier ??= ItemListNotifier<Device>(); deviceNotifier ??= ItemListNotifier<Device>();
// Start by populating all currently attached devices. // Start by populating all currently attached devices.
deviceNotifier!.updateWithNewList(await pollingGetDevices()); final List<Device> devices = await pollingGetDevices();
// Only show connected devices.
final List<Device> filteredDevices = devices.where((Device device) => device.isConnected == true).toList();
deviceNotifier!.updateWithNewList(filteredDevices);
// cancel any outstanding subscriptions. // cancel any outstanding subscriptions.
await _observedDeviceEventsSubscription?.cancel(); await _observedDeviceEventsSubscription?.cancel();
_observedDeviceEventsSubscription = _xcdevice.observedDeviceEvents()?.listen( _observedDeviceEventsSubscription = xcdevice.observedDeviceEvents()?.listen(
_onDeviceEvent, _onDeviceEvent,
onError: (Object error, StackTrace stack) { onError: (Object error, StackTrace stack) {
_logger.printTrace('Process exception running xcdevice observe:\n$error\n$stack'); _logger.printTrace('Process exception running xcdevice observe:\n$error\n$stack');
...@@ -109,7 +117,10 @@ class IOSDevices extends PollingDeviceDiscovery { ...@@ -109,7 +117,10 @@ class IOSDevices extends PollingDeviceDiscovery {
// There's no way to get details for an individual attached device, // There's no way to get details for an individual attached device,
// so repopulate them all. // so repopulate them all.
final List<Device> devices = await pollingGetDevices(); final List<Device> devices = await pollingGetDevices();
notifier.updateWithNewList(devices);
// Only show connected devices.
final List<Device> filteredDevices = devices.where((Device device) => device.isConnected == true).toList();
notifier.updateWithNewList(filteredDevices);
} else if (eventType == XCDeviceEvent.detach && knownDevice != null) { } else if (eventType == XCDeviceEvent.detach && knownDevice != null) {
notifier.removeItem(knownDevice); notifier.removeItem(knownDevice);
} }
...@@ -128,7 +139,26 @@ class IOSDevices extends PollingDeviceDiscovery { ...@@ -128,7 +139,26 @@ class IOSDevices extends PollingDeviceDiscovery {
); );
} }
return _xcdevice.getAvailableIOSDevices(timeout: timeout); return xcdevice.getAvailableIOSDevices(timeout: timeout);
}
Future<Device?> waitForDeviceToConnect(
IOSDevice device,
Logger logger,
) async {
final XCDeviceEventNotification? eventDetails =
await xcdevice.waitForDeviceToConnect(device.id);
if (eventDetails != null) {
device.isConnected = true;
device.connectionInterface = eventDetails.eventInterface.connectionInterface;
return device;
}
return null;
}
void cancelWaitForDeviceToConnect() {
xcdevice.cancelWaitForDeviceToConnect();
} }
@override @override
...@@ -139,7 +169,7 @@ class IOSDevices extends PollingDeviceDiscovery { ...@@ -139,7 +169,7 @@ class IOSDevices extends PollingDeviceDiscovery {
]; ];
} }
return _xcdevice.getDiagnostics(); return xcdevice.getDiagnostics();
} }
@override @override
...@@ -152,6 +182,7 @@ class IOSDevice extends Device { ...@@ -152,6 +182,7 @@ class IOSDevice extends Device {
required this.name, required this.name,
required this.cpuArchitecture, required this.cpuArchitecture,
required this.connectionInterface, required this.connectionInterface,
required this.isConnected,
String? sdkVersion, String? sdkVersion,
required Platform platform, required Platform platform,
required IOSDeploy iosDeploy, required IOSDeploy iosDeploy,
...@@ -200,7 +231,15 @@ class IOSDevice extends Device { ...@@ -200,7 +231,15 @@ class IOSDevice extends Device {
final DarwinArch cpuArchitecture; final DarwinArch cpuArchitecture;
@override @override
final DeviceConnectionInterface connectionInterface; /// The [connectionInterface] provided from `XCDevice.getAvailableIOSDevices`
/// may not be accurate. Sometimes if it doesn't have a long enough time
/// to connect, wireless devices will have an interface of `usb`/`attached`.
/// This may change after waiting for the device to connect in
/// `waitForDeviceToConnect`.
DeviceConnectionInterface connectionInterface;
@override
bool isConnected;
final Map<IOSApp?, DeviceLogReader> _logReaders = <IOSApp?, DeviceLogReader>{}; final Map<IOSApp?, DeviceLogReader> _logReaders = <IOSApp?, DeviceLogReader>{};
......
...@@ -210,6 +210,11 @@ abstract class FlutterCommand extends Command<void> { ...@@ -210,6 +210,11 @@ abstract class FlutterCommand extends Command<void> {
bool get deprecated => false; bool get deprecated => false;
/// When the command runs and this is true, trigger an async process to
/// discover devices from discoverers that support wireless devices for an
/// extended amount of time and refresh the device cache with the results.
bool get refreshWirelessDevices => false;
@override @override
bool get hidden => deprecated; bool get hidden => deprecated;
...@@ -719,6 +724,7 @@ abstract class FlutterCommand extends Command<void> { ...@@ -719,6 +724,7 @@ abstract class FlutterCommand extends Command<void> {
}(); }();
late final TargetDevices _targetDevices = TargetDevices( late final TargetDevices _targetDevices = TargetDevices(
platform: globals.platform,
deviceManager: globals.deviceManager!, deviceManager: globals.deviceManager!,
logger: globals.logger, logger: globals.logger,
); );
...@@ -1466,6 +1472,14 @@ Run 'flutter -h' (or 'flutter <command> -h') for available flutter commands and ...@@ -1466,6 +1472,14 @@ Run 'flutter -h' (or 'flutter <command> -h') for available flutter commands and
} }
globals.preRunValidator.validate(); globals.preRunValidator.validate();
if (refreshWirelessDevices) {
// Loading wireless devices takes longer so start it early.
_targetDevices.startExtendedWirelessDeviceDiscovery(
deviceDiscoveryTimeout: deviceDiscoveryTimeout,
);
}
// Populate the cache. We call this before pub get below so that the // Populate the cache. We call this before pub get below so that the
// sky_engine package is available in the flutter cache for pub to find. // sky_engine package is available in the flutter cache for pub to find.
if (shouldUpdateCache) { if (shouldUpdateCache) {
...@@ -1656,14 +1670,17 @@ Run 'flutter -h' (or 'flutter <command> -h') for available flutter commands and ...@@ -1656,14 +1670,17 @@ Run 'flutter -h' (or 'flutter <command> -h') for available flutter commands and
} }
/// A mixin which applies an implementation of [requiredArtifacts] that only /// A mixin which applies an implementation of [requiredArtifacts] that only
/// downloads artifacts corresponding to an attached device. /// downloads artifacts corresponding to potentially connected devices.
mixin DeviceBasedDevelopmentArtifacts on FlutterCommand { mixin DeviceBasedDevelopmentArtifacts on FlutterCommand {
@override @override
Future<Set<DevelopmentArtifact>> get requiredArtifacts async { Future<Set<DevelopmentArtifact>> get requiredArtifacts async {
// If there are no attached devices, use the default configuration. // If there are no devices, use the default configuration.
// Otherwise, only add development artifacts which correspond to a // Otherwise, only add development artifacts corresponding to
// connected device. // potentially connected devices. We might not be able to determine if a
final List<Device> devices = await globals.deviceManager!.getDevices(); // device is connected yet, so include it in case it becomes connected.
final List<Device> devices = await globals.deviceManager!.getDevices(
filter: DeviceDiscoveryFilter(excludeDisconnected: false),
);
if (devices.isEmpty) { if (devices.isEmpty) {
return super.requiredArtifacts; return super.requiredArtifacts;
} }
......
...@@ -1137,6 +1137,7 @@ class StreamLogger extends Logger { ...@@ -1137,6 +1137,7 @@ class StreamLogger extends Logger {
VoidCallback? onFinish, VoidCallback? onFinish,
Duration? timeout, Duration? timeout,
SlowWarningCallback? slowWarningCallback, SlowWarningCallback? slowWarningCallback,
TerminalColor? warningColor,
}) { }) {
return SilentStatus( return SilentStatus(
stopwatch: Stopwatch(), stopwatch: Stopwatch(),
...@@ -1353,6 +1354,9 @@ class FakeIOSDevice extends Fake implements IOSDevice { ...@@ -1353,6 +1354,9 @@ class FakeIOSDevice extends Fake implements IOSDevice {
@override @override
bool get isConnected => true; bool get isConnected => true;
@override
bool get ephemeral => true;
} }
class FakeMDnsClient extends Fake implements MDnsClient { class FakeMDnsClient extends Fake implements MDnsClient {
......
...@@ -850,6 +850,9 @@ class FakeAndroidDevice extends Fake implements AndroidDevice { ...@@ -850,6 +850,9 @@ class FakeAndroidDevice extends Fake implements AndroidDevice {
@override @override
final bool ephemeral = false; final bool ephemeral = false;
@override
final bool isConnected = true;
@override @override
Future<String> get sdkNameAndVersion async => 'Android 12'; Future<String> get sdkNameAndVersion async => 'Android 12';
......
...@@ -1196,6 +1196,12 @@ class FakeDeviceManager extends Fake implements DeviceManager { ...@@ -1196,6 +1196,12 @@ class FakeDeviceManager extends Fake implements DeviceManager {
DeviceDiscoveryFilter? filter, DeviceDiscoveryFilter? filter,
}) async => devices; }) async => devices;
@override
Future<List<Device>> refreshAllDevices({
Duration? timeout,
DeviceDiscoveryFilter? filter,
}) async => devices;
@override @override
Future<List<String>> getDeviceDiagnostics() async => diagnostics; Future<List<String>> getDeviceDiagnostics() async => diagnostics;
} }
......
...@@ -543,6 +543,9 @@ class ScreenshotDevice extends Fake implements Device { ...@@ -543,6 +543,9 @@ class ScreenshotDevice extends Fake implements Device {
@override @override
bool supportsScreenshot = true; bool supportsScreenshot = true;
@override
bool get isConnected => true;
@override @override
Future<LaunchResult> startApp( Future<LaunchResult> startApp(
ApplicationPackage? package, { ApplicationPackage? package, {
......
...@@ -268,6 +268,9 @@ class FakeAndroidDevice extends Fake implements AndroidDevice { ...@@ -268,6 +268,9 @@ class FakeAndroidDevice extends Fake implements AndroidDevice {
@override @override
final bool ephemeral = false; final bool ephemeral = false;
@override
bool get isConnected => true;
@override @override
Future<String> get sdkNameAndVersion async => 'Android 12'; Future<String> get sdkNameAndVersion async => 'Android 12';
......
...@@ -1170,6 +1170,9 @@ class FakeDevice extends Fake implements Device { ...@@ -1170,6 +1170,9 @@ class FakeDevice extends Fake implements Device {
@override @override
bool get supportsFastStart => false; bool get supportsFastStart => false;
@override
bool get ephemeral => true;
@override @override
bool get isConnected => true; bool get isConnected => true;
......
...@@ -480,7 +480,7 @@ void main() { ...@@ -480,7 +480,7 @@ void main() {
expect(done, isTrue); expect(done, isTrue);
}); });
testWithoutContext('AnonymousSpinnerStatus logs warning after timeout', () async { testWithoutContext('AnonymousSpinnerStatus logs warning after timeout without color support', () async {
mockStopwatch = FakeStopwatch(); mockStopwatch = FakeStopwatch();
const String warningMessage = 'a warning message.'; const String warningMessage = 'a warning message.';
final bool done = FakeAsync().run<bool>((FakeAsync time) { final bool done = FakeAsync().run<bool>((FakeAsync time) {
...@@ -489,6 +489,7 @@ void main() { ...@@ -489,6 +489,7 @@ void main() {
stopwatch: mockStopwatch, stopwatch: mockStopwatch,
terminal: terminal, terminal: terminal,
slowWarningCallback: () => warningMessage, slowWarningCallback: () => warningMessage,
warningColor: TerminalColor.red,
timeout: const Duration(milliseconds: 100), timeout: const Duration(milliseconds: 100),
)..start(); )..start();
// must be greater than the spinner timer duration // must be greater than the spinner timer duration
...@@ -497,6 +498,7 @@ void main() { ...@@ -497,6 +498,7 @@ void main() {
time.elapse(timeLapse); time.elapse(timeLapse);
List<String> lines = outputStdout(); List<String> lines = outputStdout();
expect(lines.join().contains(RegExp(red)), isFalse);
expect(lines.join(), '⣽\ba warning message.⣻'); expect(lines.join(), '⣽\ba warning message.⣻');
spinner.stop(); spinner.stop();
...@@ -506,6 +508,35 @@ void main() { ...@@ -506,6 +508,35 @@ void main() {
expect(done, isTrue); expect(done, isTrue);
}); });
testWithoutContext('AnonymousSpinnerStatus logs warning after timeout with color support', () async {
mockStopwatch = FakeStopwatch();
const String warningMessage = 'a warning message.';
final bool done = FakeAsync().run<bool>((FakeAsync time) {
final AnonymousSpinnerStatus spinner = AnonymousSpinnerStatus(
stdio: mockStdio,
stopwatch: mockStopwatch,
terminal: coloredTerminal,
slowWarningCallback: () => warningMessage,
warningColor: TerminalColor.red,
timeout: const Duration(milliseconds: 100),
)..start();
// must be greater than the spinner timer duration
const Duration timeLapse = Duration(milliseconds: 101);
mockStopwatch.elapsed += timeLapse;
time.elapse(timeLapse);
List<String> lines = outputStdout();
expect(lines.join().contains(RegExp(red)), isTrue);
expect(lines.join(), '⣽\b${AnsiTerminal.red}a warning message.${AnsiTerminal.resetColor}⣻');
expect(lines.join(), matches('$red$warningMessage$resetColor'));
spinner.stop();
lines = outputStdout();
return true;
});
expect(done, isTrue);
});
testWithoutContext('Stdout startProgress on colored terminal', () async { testWithoutContext('Stdout startProgress on colored terminal', () async {
final Logger logger = StdoutLogger( final Logger logger = StdoutLogger(
terminal: coloredTerminal, terminal: coloredTerminal,
......
...@@ -34,7 +34,7 @@ void main() { ...@@ -34,7 +34,7 @@ void main() {
}); });
}); });
group('ANSI coloring and bold', () { group('ANSI coloring, bold, and clearing', () {
late AnsiTerminal terminal; late AnsiTerminal terminal;
setUp(() { setUp(() {
...@@ -103,6 +103,39 @@ void main() { ...@@ -103,6 +103,39 @@ void main() {
equals('${AnsiTerminal.bold}bold output still bold${AnsiTerminal.resetBold}'), equals('${AnsiTerminal.bold}bold output still bold${AnsiTerminal.resetBold}'),
); );
}); });
testWithoutContext('clearing lines works', () {
expect(
terminal.clearLines(3),
equals(
'${AnsiTerminal.cursorBeginningOfLineCode}'
'${AnsiTerminal.clearEntireLineCode}'
'${AnsiTerminal.cursorUpLineCode}'
'${AnsiTerminal.clearEntireLineCode}'
'${AnsiTerminal.cursorUpLineCode}'
'${AnsiTerminal.clearEntireLineCode}'
),
);
expect(
terminal.clearLines(1),
equals(
'${AnsiTerminal.cursorBeginningOfLineCode}'
'${AnsiTerminal.clearEntireLineCode}'
),
);
});
testWithoutContext('clearing lines when color is not supported does not work', () {
terminal = AnsiTerminal(
stdio: Stdio(), // Danger, using real stdio.
platform: FakePlatform()..stdoutSupportsAnsi = false,
);
expect(
terminal.clearLines(3),
equals(''),
);
});
}); });
group('character input prompt', () { group('character input prompt', () {
......
...@@ -141,29 +141,92 @@ void main() { ...@@ -141,29 +141,92 @@ void main() {
}); });
testWithoutContext('getAllDevices caches', () async { testWithoutContext('getAllDevices caches', () async {
final FakeDevice device1 = FakeDevice('Nexus 5', '0553790d0a4e726f'); final FakePollingDeviceDiscovery notSupportedDiscoverer = FakePollingDeviceDiscovery();
final FakePollingDeviceDiscovery supportedDiscoverer = FakePollingDeviceDiscovery(requiresExtendedWirelessDeviceDiscovery: true);
final FakeDevice attachedDevice = FakeDevice('Nexus 5', '0553790d0a4e726f');
final FakeDevice wirelessDevice = FakeDevice('Wireless device', 'wireless-device', connectionInterface: DeviceConnectionInterface.wireless);
notSupportedDiscoverer.addDevice(attachedDevice);
supportedDiscoverer.addDevice(wirelessDevice);
final TestDeviceManager deviceManager = TestDeviceManager( final TestDeviceManager deviceManager = TestDeviceManager(
<Device>[device1], <Device>[],
logger: BufferLogger.test(), logger: BufferLogger.test(),
deviceDiscoveryOverrides: <DeviceDiscovery>[
notSupportedDiscoverer,
supportedDiscoverer,
],
); );
expect(await deviceManager.getAllDevices(), <Device>[device1]); expect(await deviceManager.getAllDevices(), <Device>[attachedDevice, wirelessDevice]);
final FakeDevice device2 = FakeDevice('Nexus 5X', '01abfc49119c410e'); final FakeDevice newAttachedDevice = FakeDevice('Nexus 5X', '01abfc49119c410e');
deviceManager.resetDevices(<Device>[device2]); notSupportedDiscoverer.addDevice(newAttachedDevice);
expect(await deviceManager.getAllDevices(), <Device>[device1]);
final FakeDevice newWirelessDevice = FakeDevice('New wireless device', 'new-wireless-device', connectionInterface: DeviceConnectionInterface.wireless);
supportedDiscoverer.addDevice(newWirelessDevice);
expect(await deviceManager.getAllDevices(), <Device>[attachedDevice, wirelessDevice]);
}); });
testWithoutContext('refreshAllDevices does not cache', () async { testWithoutContext('refreshAllDevices does not cache', () async {
final FakeDevice device1 = FakeDevice('Nexus 5', '0553790d0a4e726f'); final FakePollingDeviceDiscovery notSupportedDiscoverer = FakePollingDeviceDiscovery();
final FakePollingDeviceDiscovery supportedDiscoverer = FakePollingDeviceDiscovery(requiresExtendedWirelessDeviceDiscovery: true);
final FakeDevice attachedDevice = FakeDevice('Nexus 5', '0553790d0a4e726f');
final FakeDevice wirelessDevice = FakeDevice('Wireless device', 'wireless-device', connectionInterface: DeviceConnectionInterface.wireless);
notSupportedDiscoverer.addDevice(attachedDevice);
supportedDiscoverer.addDevice(wirelessDevice);
final TestDeviceManager deviceManager = TestDeviceManager( final TestDeviceManager deviceManager = TestDeviceManager(
<Device>[device1], <Device>[],
logger: BufferLogger.test(), logger: BufferLogger.test(),
deviceDiscoveryOverrides: <DeviceDiscovery>[
notSupportedDiscoverer,
supportedDiscoverer,
],
); );
expect(await deviceManager.refreshAllDevices(), <Device>[device1]); expect(await deviceManager.refreshAllDevices(), <Device>[attachedDevice, wirelessDevice]);
final FakeDevice device2 = FakeDevice('Nexus 5X', '01abfc49119c410e'); final FakeDevice newAttachedDevice = FakeDevice('Nexus 5X', '01abfc49119c410e');
deviceManager.resetDevices(<Device>[device2]); notSupportedDiscoverer.addDevice(newAttachedDevice);
expect(await deviceManager.refreshAllDevices(), <Device>[device2]);
final FakeDevice newWirelessDevice = FakeDevice('New wireless device', 'new-wireless-device', connectionInterface: DeviceConnectionInterface.wireless);
supportedDiscoverer.addDevice(newWirelessDevice);
expect(await deviceManager.refreshAllDevices(), <Device>[attachedDevice, newAttachedDevice, wirelessDevice, newWirelessDevice]);
});
testWithoutContext('refreshExtendedWirelessDeviceDiscoverers only refreshes discoverers that require extended time', () async {
final FakePollingDeviceDiscovery normalDiscoverer = FakePollingDeviceDiscovery();
final FakePollingDeviceDiscovery extendedDiscoverer = FakePollingDeviceDiscovery(requiresExtendedWirelessDeviceDiscovery: true);
final FakeDevice attachedDevice = FakeDevice('Nexus 5', '0553790d0a4e726f');
final FakeDevice wirelessDevice = FakeDevice('Wireless device', 'wireless-device', connectionInterface: DeviceConnectionInterface.wireless);
normalDiscoverer.addDevice(attachedDevice);
extendedDiscoverer.addDevice(wirelessDevice);
final TestDeviceManager deviceManager = TestDeviceManager(
<Device>[],
logger: BufferLogger.test(),
deviceDiscoveryOverrides: <DeviceDiscovery>[
normalDiscoverer,
extendedDiscoverer,
],
);
await deviceManager.refreshExtendedWirelessDeviceDiscoverers();
expect(await deviceManager.getAllDevices(), <Device>[attachedDevice, wirelessDevice]);
final FakeDevice newAttachedDevice = FakeDevice('Nexus 5X', '01abfc49119c410e');
normalDiscoverer.addDevice(newAttachedDevice);
final FakeDevice newWirelessDevice = FakeDevice('New wireless device', 'new-wireless-device', connectionInterface: DeviceConnectionInterface.wireless);
extendedDiscoverer.addDevice(newWirelessDevice);
await deviceManager.refreshExtendedWirelessDeviceDiscoverers();
expect(await deviceManager.getAllDevices(), <Device>[attachedDevice, wirelessDevice, newWirelessDevice]);
}); });
}); });
...@@ -1034,34 +1097,6 @@ class TestDeviceManager extends DeviceManager { ...@@ -1034,34 +1097,6 @@ class TestDeviceManager extends DeviceManager {
} }
} }
class MockDeviceDiscovery extends Fake implements DeviceDiscovery {
int devicesCalled = 0;
int discoverDevicesCalled = 0;
@override
bool supportsPlatform = true;
List<Device> deviceValues = <Device>[];
@override
Future<List<Device>> devices({DeviceDiscoveryFilter? filter}) async {
devicesCalled += 1;
return deviceValues;
}
@override
Future<List<Device>> discoverDevices({
Duration? timeout,
DeviceDiscoveryFilter? filter,
}) async {
discoverDevicesCalled += 1;
return deviceValues;
}
@override
List<String> get wellKnownIds => <String>[];
}
class TestDeviceDiscoverySupportFilter extends DeviceDiscoverySupportFilter { class TestDeviceDiscoverySupportFilter extends DeviceDiscoverySupportFilter {
TestDeviceDiscoverySupportFilter.excludeDevicesUnsupportedByFlutterOrProject({ TestDeviceDiscoverySupportFilter.excludeDevicesUnsupportedByFlutterOrProject({
required super.flutterProject, required super.flutterProject,
......
...@@ -75,6 +75,7 @@ void main() { ...@@ -75,6 +75,7 @@ void main() {
sdkVersion: '13.3', sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64, cpuArchitecture: DarwinArch.arm64,
connectionInterface: DeviceConnectionInterface.attached, connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
); );
expect(device.isSupported(), isTrue); expect(device.isSupported(), isTrue);
}); });
...@@ -91,6 +92,7 @@ void main() { ...@@ -91,6 +92,7 @@ void main() {
name: 'iPhone 1', name: 'iPhone 1',
cpuArchitecture: DarwinArch.armv7, cpuArchitecture: DarwinArch.armv7,
connectionInterface: DeviceConnectionInterface.attached, connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
); );
expect(device.isSupported(), isFalse); expect(device.isSupported(), isFalse);
}); });
...@@ -108,6 +110,7 @@ void main() { ...@@ -108,6 +110,7 @@ void main() {
cpuArchitecture: DarwinArch.arm64, cpuArchitecture: DarwinArch.arm64,
sdkVersion: '1.0.0', sdkVersion: '1.0.0',
connectionInterface: DeviceConnectionInterface.attached, connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
).majorSdkVersion, 1); ).majorSdkVersion, 1);
expect(IOSDevice( expect(IOSDevice(
'device-123', 'device-123',
...@@ -121,6 +124,7 @@ void main() { ...@@ -121,6 +124,7 @@ void main() {
cpuArchitecture: DarwinArch.arm64, cpuArchitecture: DarwinArch.arm64,
sdkVersion: '13.1.1', sdkVersion: '13.1.1',
connectionInterface: DeviceConnectionInterface.attached, connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
).majorSdkVersion, 13); ).majorSdkVersion, 13);
expect(IOSDevice( expect(IOSDevice(
'device-123', 'device-123',
...@@ -134,6 +138,7 @@ void main() { ...@@ -134,6 +138,7 @@ void main() {
cpuArchitecture: DarwinArch.arm64, cpuArchitecture: DarwinArch.arm64,
sdkVersion: '10', sdkVersion: '10',
connectionInterface: DeviceConnectionInterface.attached, connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
).majorSdkVersion, 10); ).majorSdkVersion, 10);
expect(IOSDevice( expect(IOSDevice(
'device-123', 'device-123',
...@@ -147,6 +152,7 @@ void main() { ...@@ -147,6 +152,7 @@ void main() {
cpuArchitecture: DarwinArch.arm64, cpuArchitecture: DarwinArch.arm64,
sdkVersion: '0', sdkVersion: '0',
connectionInterface: DeviceConnectionInterface.attached, connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
).majorSdkVersion, 0); ).majorSdkVersion, 0);
expect(IOSDevice( expect(IOSDevice(
'device-123', 'device-123',
...@@ -160,6 +166,7 @@ void main() { ...@@ -160,6 +166,7 @@ void main() {
cpuArchitecture: DarwinArch.arm64, cpuArchitecture: DarwinArch.arm64,
sdkVersion: 'bogus', sdkVersion: 'bogus',
connectionInterface: DeviceConnectionInterface.attached, connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
).majorSdkVersion, 0); ).majorSdkVersion, 0);
}); });
...@@ -176,6 +183,7 @@ void main() { ...@@ -176,6 +183,7 @@ void main() {
sdkVersion: '13.3 17C54', sdkVersion: '13.3 17C54',
cpuArchitecture: DarwinArch.arm64, cpuArchitecture: DarwinArch.arm64,
connectionInterface: DeviceConnectionInterface.attached, connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
); );
expect(await device.sdkNameAndVersion,'iOS 13.3 17C54'); expect(await device.sdkNameAndVersion,'iOS 13.3 17C54');
...@@ -194,6 +202,7 @@ void main() { ...@@ -194,6 +202,7 @@ void main() {
sdkVersion: '13.3', sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64, cpuArchitecture: DarwinArch.arm64,
connectionInterface: DeviceConnectionInterface.attached, connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
); );
expect(device.supportsRuntimeMode(BuildMode.debug), true); expect(device.supportsRuntimeMode(BuildMode.debug), true);
...@@ -218,6 +227,7 @@ void main() { ...@@ -218,6 +227,7 @@ void main() {
sdkVersion: '13.3', sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64, cpuArchitecture: DarwinArch.arm64,
connectionInterface: DeviceConnectionInterface.attached, connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
); );
}, },
throwsAssertionError, throwsAssertionError,
...@@ -308,6 +318,7 @@ void main() { ...@@ -308,6 +318,7 @@ void main() {
sdkVersion: '13.3', sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64, cpuArchitecture: DarwinArch.arm64,
connectionInterface: DeviceConnectionInterface.attached, connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
); );
logReader1 = createLogReader(device, appPackage1, process1); logReader1 = createLogReader(device, appPackage1, process1);
logReader2 = createLogReader(device, appPackage2, process2); logReader2 = createLogReader(device, appPackage2, process2);
...@@ -369,6 +380,7 @@ void main() { ...@@ -369,6 +380,7 @@ void main() {
platform: macPlatform, platform: macPlatform,
fileSystem: MemoryFileSystem.test(), fileSystem: MemoryFileSystem.test(),
connectionInterface: DeviceConnectionInterface.attached, connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
); );
device2 = IOSDevice( device2 = IOSDevice(
...@@ -383,6 +395,7 @@ void main() { ...@@ -383,6 +395,7 @@ void main() {
platform: macPlatform, platform: macPlatform,
fileSystem: MemoryFileSystem.test(), fileSystem: MemoryFileSystem.test(),
connectionInterface: DeviceConnectionInterface.attached, connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
); );
}); });
...@@ -587,6 +600,120 @@ void main() { ...@@ -587,6 +600,120 @@ void main() {
expect(diagnostics.first, 'Generic pairing error'); expect(diagnostics.first, 'Generic pairing error');
}); });
}); });
group('waitForDeviceToConnect', () {
late FakeXcdevice xcdevice;
late Cache cache;
late FakeProcessManager fakeProcessManager;
late BufferLogger logger;
late IOSDeploy iosDeploy;
late IMobileDevice iMobileDevice;
late IOSWorkflow iosWorkflow;
late IOSDevice notConnected1;
setUp(() {
xcdevice = FakeXcdevice();
final Artifacts artifacts = Artifacts.test();
cache = Cache.test(processManager: FakeProcessManager.any());
logger = BufferLogger.test();
iosWorkflow = FakeIOSWorkflow();
fakeProcessManager = FakeProcessManager.any();
iosDeploy = IOSDeploy(
artifacts: artifacts,
cache: cache,
logger: logger,
platform: macPlatform,
processManager: fakeProcessManager,
);
iMobileDevice = IMobileDevice(
artifacts: artifacts,
cache: cache,
processManager: fakeProcessManager,
logger: logger,
);
notConnected1 = IOSDevice(
'00000001-0000000000000000',
name: 'iPad',
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
iProxy: IProxy.test(logger: logger, processManager: FakeProcessManager.any()),
iosDeploy: iosDeploy,
iMobileDevice: iMobileDevice,
logger: logger,
platform: macPlatform,
fileSystem: MemoryFileSystem.test(),
connectionInterface: DeviceConnectionInterface.attached,
isConnected: false,
);
});
testWithoutContext('wait for device to connect via wifi', () async {
final IOSDevices iosDevices = IOSDevices(
platform: macPlatform,
xcdevice: xcdevice,
iosWorkflow: iosWorkflow,
logger: logger,
);
xcdevice.isInstalled = true;
xcdevice.waitForDeviceEvent = XCDeviceEventNotification(
XCDeviceEvent.attach,
XCDeviceEventInterface.wifi,
'00000001-0000000000000000'
);
final Device? device = await iosDevices.waitForDeviceToConnect(
notConnected1,
logger
);
expect(device?.isConnected, isTrue);
expect(device?.connectionInterface, DeviceConnectionInterface.wireless);
});
testWithoutContext('wait for device to connect via usb', () async {
final IOSDevices iosDevices = IOSDevices(
platform: macPlatform,
xcdevice: xcdevice,
iosWorkflow: iosWorkflow,
logger: logger,
);
xcdevice.isInstalled = true;
xcdevice.waitForDeviceEvent = XCDeviceEventNotification(
XCDeviceEvent.attach,
XCDeviceEventInterface.usb,
'00000001-0000000000000000'
);
final Device? device = await iosDevices.waitForDeviceToConnect(
notConnected1,
logger
);
expect(device?.isConnected, isTrue);
expect(device?.connectionInterface, DeviceConnectionInterface.attached);
});
testWithoutContext('wait for device returns null', () async {
final IOSDevices iosDevices = IOSDevices(
platform: macPlatform,
xcdevice: xcdevice,
iosWorkflow: iosWorkflow,
logger: logger,
);
xcdevice.isInstalled = true;
xcdevice.waitForDeviceEvent = null;
final Device? device = await iosDevices.waitForDeviceToConnect(
notConnected1,
logger
);
expect(device, isNull);
});
});
} }
class FakeIOSApp extends Fake implements IOSApp { class FakeIOSApp extends Fake implements IOSApp {
...@@ -603,6 +730,7 @@ class FakeXcdevice extends Fake implements XCDevice { ...@@ -603,6 +730,7 @@ class FakeXcdevice extends Fake implements XCDevice {
final List<List<IOSDevice>> devices = <List<IOSDevice>>[]; final List<List<IOSDevice>> devices = <List<IOSDevice>>[];
final List<String> diagnostics = <String>[]; final List<String> diagnostics = <String>[];
StreamController<Map<XCDeviceEvent, String>> deviceEventController = StreamController<Map<XCDeviceEvent, String>>(); StreamController<Map<XCDeviceEvent, String>> deviceEventController = StreamController<Map<XCDeviceEvent, String>>();
XCDeviceEventNotification? waitForDeviceEvent;
@override @override
bool isInstalled = true; bool isInstalled = true;
...@@ -621,6 +749,16 @@ class FakeXcdevice extends Fake implements XCDevice { ...@@ -621,6 +749,16 @@ class FakeXcdevice extends Fake implements XCDevice {
Future<List<IOSDevice>> getAvailableIOSDevices({Duration? timeout}) async { Future<List<IOSDevice>> getAvailableIOSDevices({Duration? timeout}) async {
return devices[getAvailableIOSDevicesCount++]; return devices[getAvailableIOSDevicesCount++];
} }
@override
Future<XCDeviceEventNotification?> waitForDeviceToConnect(String deviceId) async {
final XCDeviceEventNotification? waitEvent = waitForDeviceEvent;
if (waitEvent != null) {
return XCDeviceEventNotification(waitEvent.eventType, waitEvent.eventInterface, waitEvent.deviceIdentifier);
} else {
return null;
}
}
} }
class FakeProcess extends Fake implements Process { class FakeProcess extends Fake implements Process {
......
...@@ -359,5 +359,6 @@ IOSDevice setUpIOSDevice({ ...@@ -359,5 +359,6 @@ IOSDevice setUpIOSDevice({
), ),
iProxy: IProxy.test(logger: logger, processManager: processManager), iProxy: IProxy.test(logger: logger, processManager: processManager),
connectionInterface: interfaceType ?? DeviceConnectionInterface.attached, connectionInterface: interfaceType ?? DeviceConnectionInterface.attached,
isConnected: true,
); );
} }
...@@ -100,5 +100,6 @@ IOSDevice setUpIOSDevice(FileSystem fileSystem) { ...@@ -100,5 +100,6 @@ IOSDevice setUpIOSDevice(FileSystem fileSystem) {
cpuArchitecture: DarwinArch.arm64, cpuArchitecture: DarwinArch.arm64,
iProxy: IProxy.test(logger: logger, processManager: processManager), iProxy: IProxy.test(logger: logger, processManager: processManager),
connectionInterface: DeviceConnectionInterface.attached, connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
); );
} }
...@@ -338,6 +338,7 @@ IOSDevice setUpIOSDevice({ ...@@ -338,6 +338,7 @@ IOSDevice setUpIOSDevice({
), ),
cpuArchitecture: DarwinArch.arm64, cpuArchitecture: DarwinArch.arm64,
connectionInterface: DeviceConnectionInterface.attached, connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
); );
} }
......
...@@ -599,6 +599,7 @@ IOSDevice setUpIOSDevice({ ...@@ -599,6 +599,7 @@ IOSDevice setUpIOSDevice({
), ),
cpuArchitecture: DarwinArch.arm64, cpuArchitecture: DarwinArch.arm64,
connectionInterface: interfaceType, connectionInterface: interfaceType,
isConnected: true,
); );
} }
......
...@@ -377,6 +377,150 @@ void main() { ...@@ -377,6 +377,150 @@ void main() {
await detach1.future; await detach1.future;
expect(logger.traceText, contains('xcdevice observe error: Some error')); expect(logger.traceText, contains('xcdevice observe error: Some error'));
}); });
testUsingContext('handles exit code', () async {
fakeProcessManager.addCommand(const FakeCommand(
command: <String>[
'script',
'-t',
'0',
'/dev/null',
'xcrun',
'xcdevice',
'observe',
'--both',
],
));
final Completer<void> doneCompleter = Completer<void>();
xcdevice.observedDeviceEvents()!.listen(null, onDone: () {
doneCompleter.complete();
});
await doneCompleter.future;
expect(logger.traceText, contains('xcdevice exited with code 0'));
});
});
group('wait device events', () {
testUsingContext('relays events', () async {
const String deviceId = '00000001-0000000000000000';
fakeProcessManager.addCommand(const FakeCommand(
command: <String>[
'script',
'-t',
'0',
'/dev/null',
'xcrun',
'xcdevice',
'wait',
'--usb',
deviceId,
],
));
fakeProcessManager.addCommand(const FakeCommand(
command: <String>[
'script',
'-t',
'0',
'/dev/null',
'xcrun',
'xcdevice',
'wait',
'--wifi',
deviceId,
],
stdout: 'Attach: 00000001-0000000000000000\n',
));
// Attach: 00000001-0000000000000000
final XCDeviceEventNotification? event = await xcdevice.waitForDeviceToConnect(deviceId);
expect(event?.deviceIdentifier, deviceId);
expect(event?.eventInterface, XCDeviceEventInterface.wifi);
expect(event?.eventType, XCDeviceEvent.attach);
});
testUsingContext('handles exit code', () async {
const String deviceId = '00000001-0000000000000000';
fakeProcessManager.addCommand(const FakeCommand(
command: <String>[
'script',
'-t',
'0',
'/dev/null',
'xcrun',
'xcdevice',
'wait',
'--usb',
deviceId,
],
exitCode: 1,
));
fakeProcessManager.addCommand(const FakeCommand(
command: <String>[
'script',
'-t',
'0',
'/dev/null',
'xcrun',
'xcdevice',
'wait',
'--wifi',
deviceId,
],
));
final XCDeviceEventNotification? event = await xcdevice.waitForDeviceToConnect(deviceId);
expect(event, isNull);
expect(logger.traceText, contains('xcdevice wait --usb exited with code 0'));
expect(logger.traceText, contains('xcdevice wait --wifi exited with code 0'));
expect(xcdevice.waitStreamController?.isClosed, isTrue);
});
testUsingContext('handles cancel', () async {
const String deviceId = '00000001-0000000000000000';
fakeProcessManager.addCommand(const FakeCommand(
command: <String>[
'script',
'-t',
'0',
'/dev/null',
'xcrun',
'xcdevice',
'wait',
'--usb',
deviceId,
],
));
fakeProcessManager.addCommand(const FakeCommand(
command: <String>[
'script',
'-t',
'0',
'/dev/null',
'xcrun',
'xcdevice',
'wait',
'--wifi',
deviceId,
],
));
final Future<XCDeviceEventNotification?> futureEvent = xcdevice.waitForDeviceToConnect(deviceId);
xcdevice.cancelWaitForDeviceToConnect();
final XCDeviceEventNotification? event = await futureEvent;
expect(event, isNull);
expect(logger.traceText, contains('xcdevice wait --usb exited with code 0'));
expect(logger.traceText, contains('xcdevice wait --wifi exited with code 0'));
expect(xcdevice.waitStreamController?.isClosed, isTrue);
});
}); });
group('available devices', () { group('available devices', () {
...@@ -480,31 +624,41 @@ void main() { ...@@ -480,31 +624,41 @@ void main() {
stdout: devicesOutput, stdout: devicesOutput,
)); ));
final List<IOSDevice> devices = await xcdevice.getAvailableIOSDevices(); final List<IOSDevice> devices = await xcdevice.getAvailableIOSDevices();
expect(devices, hasLength(4)); expect(devices, hasLength(5));
expect(devices[0].id, '00008027-00192736010F802E'); expect(devices[0].id, '00008027-00192736010F802E');
expect(devices[0].name, 'An iPhone (Space Gray)'); expect(devices[0].name, 'An iPhone (Space Gray)');
expect(await devices[0].sdkNameAndVersion, 'iOS 13.3 17C54'); expect(await devices[0].sdkNameAndVersion, 'iOS 13.3 17C54');
expect(devices[0].cpuArchitecture, DarwinArch.arm64); expect(devices[0].cpuArchitecture, DarwinArch.arm64);
expect(devices[0].connectionInterface, DeviceConnectionInterface.attached); expect(devices[0].connectionInterface, DeviceConnectionInterface.attached);
expect(devices[0].isConnected, true);
expect(devices[1].id, '98206e7a4afd4aedaff06e687594e089dede3c44'); expect(devices[1].id, '98206e7a4afd4aedaff06e687594e089dede3c44');
expect(devices[1].name, 'iPad 1'); expect(devices[1].name, 'iPad 1');
expect(await devices[1].sdkNameAndVersion, 'iOS 10.1 14C54'); expect(await devices[1].sdkNameAndVersion, 'iOS 10.1 14C54');
expect(devices[1].cpuArchitecture, DarwinArch.armv7); expect(devices[1].cpuArchitecture, DarwinArch.armv7);
expect(devices[1].connectionInterface, DeviceConnectionInterface.attached); expect(devices[1].connectionInterface, DeviceConnectionInterface.attached);
expect(devices[1].isConnected, true);
expect(devices[2].id, '234234234234234234345445687594e089dede3c44'); expect(devices[2].id, '234234234234234234345445687594e089dede3c44');
expect(devices[2].name, 'A networked iPad'); expect(devices[2].name, 'A networked iPad');
expect(await devices[2].sdkNameAndVersion, 'iOS 10.1 14C54'); expect(await devices[2].sdkNameAndVersion, 'iOS 10.1 14C54');
expect(devices[2].cpuArchitecture, DarwinArch.arm64); // Defaults to arm64 for unknown architecture. expect(devices[2].cpuArchitecture, DarwinArch.arm64); // Defaults to arm64 for unknown architecture.
expect(devices[2].connectionInterface, DeviceConnectionInterface.wireless); expect(devices[2].connectionInterface, DeviceConnectionInterface.wireless);
expect(devices[2].isConnected, true);
expect(devices[3].id, 'f577a7903cc54959be2e34bc4f7f80b7009efcf4'); expect(devices[3].id, 'f577a7903cc54959be2e34bc4f7f80b7009efcf4');
expect(devices[3].name, 'iPad 2'); expect(devices[3].name, 'iPad 2');
expect(await devices[3].sdkNameAndVersion, 'iOS 10.1 14C54'); expect(await devices[3].sdkNameAndVersion, 'iOS 10.1 14C54');
expect(devices[3].cpuArchitecture, DarwinArch.arm64); // Defaults to arm64 for unknown architecture. expect(devices[3].cpuArchitecture, DarwinArch.arm64); // Defaults to arm64 for unknown architecture.
expect(devices[3].connectionInterface, DeviceConnectionInterface.attached); expect(devices[3].connectionInterface, DeviceConnectionInterface.attached);
expect(devices[3].isConnected, true);
expect(devices[4].id, 'c4ca6f7a53027d1b7e4972e28478e7a28e2faee2');
expect(devices[4].name, 'iPhone');
expect(await devices[4].sdkNameAndVersion, 'iOS 13.3 17C54');
expect(devices[4].cpuArchitecture, DarwinArch.arm64);
expect(devices[4].connectionInterface, DeviceConnectionInterface.attached);
expect(devices[4].isConnected, false);
expect(fakeProcessManager, hasNoRemainingExpectations); expect(fakeProcessManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
......
...@@ -218,10 +218,17 @@ class FakeDeviceManager implements DeviceManager { ...@@ -218,10 +218,17 @@ class FakeDeviceManager implements DeviceManager {
DeviceDiscoveryFilter? filter, DeviceDiscoveryFilter? filter,
}) async => filteredDevices(filter); }) async => filteredDevices(filter);
@override
Future<List<Device>> refreshExtendedWirelessDeviceDiscoverers({
Duration? timeout,
DeviceDiscoveryFilter? filter,
}) async => filteredDevices(filter);
@override @override
Future<List<Device>> getDevicesById( Future<List<Device>> getDevicesById(
String deviceId, { String deviceId, {
DeviceDiscoveryFilter? filter, DeviceDiscoveryFilter? filter,
bool waitForDeviceToConnect = false,
}) async { }) async {
return filteredDevices(filter).where((Device device) { return filteredDevices(filter).where((Device device) {
return device.id == deviceId || device.id.startsWith(deviceId); return device.id == deviceId || device.id.startsWith(deviceId);
...@@ -231,6 +238,7 @@ class FakeDeviceManager implements DeviceManager { ...@@ -231,6 +238,7 @@ class FakeDeviceManager implements DeviceManager {
@override @override
Future<List<Device>> getDevices({ Future<List<Device>> getDevices({
DeviceDiscoveryFilter? filter, DeviceDiscoveryFilter? filter,
bool waitForDeviceToConnect = false,
}) { }) {
return hasSpecifiedDeviceId return hasSpecifiedDeviceId
? getDevicesById(specifiedDeviceId!, filter: filter) ? getDevicesById(specifiedDeviceId!, filter: filter)
......
...@@ -79,6 +79,33 @@ List<FakeDeviceJsonData> fakeDevices = <FakeDeviceJsonData>[ ...@@ -79,6 +79,33 @@ List<FakeDeviceJsonData> fakeDevices = <FakeDeviceJsonData>[
}, },
} }
), ),
FakeDeviceJsonData(
FakeDevice(
'wireless ios',
'wireless-ios',
type:PlatformType.ios,
connectionInterface: DeviceConnectionInterface.wireless,
)
..targetPlatform = Future<TargetPlatform>.value(TargetPlatform.ios)
..sdkNameAndVersion = Future<String>.value('iOS 16'),
<String,Object>{
'name': 'wireless ios',
'id': 'wireless-ios',
'isSupported': true,
'targetPlatform': 'ios',
'emulator': true,
'sdk': 'iOS 16',
'capabilities': <String, Object>{
'hotReload': true,
'hotRestart': true,
'screenshot': false,
'fastStart': false,
'flutterExit': true,
'hardwareRendering': true,
'startPaused': true,
},
},
),
]; ];
/// Fake device to test `devices` command. /// Fake device to test `devices` command.
...@@ -167,7 +194,9 @@ class FakeDeviceJsonData { ...@@ -167,7 +194,9 @@ class FakeDeviceJsonData {
} }
class FakePollingDeviceDiscovery extends PollingDeviceDiscovery { class FakePollingDeviceDiscovery extends PollingDeviceDiscovery {
FakePollingDeviceDiscovery() : super('mock'); FakePollingDeviceDiscovery({
this.requiresExtendedWirelessDeviceDiscovery = false,
}) : super('mock');
final List<Device> _devices = <Device>[]; final List<Device> _devices = <Device>[];
final StreamController<Device> _onAddedController = StreamController<Device>.broadcast(); final StreamController<Device> _onAddedController = StreamController<Device>.broadcast();
...@@ -187,6 +216,9 @@ class FakePollingDeviceDiscovery extends PollingDeviceDiscovery { ...@@ -187,6 +216,9 @@ class FakePollingDeviceDiscovery extends PollingDeviceDiscovery {
@override @override
bool get canListAnything => true; bool get canListAnything => true;
@override
bool requiresExtendedWirelessDeviceDiscovery;
void addDevice(Device device) { void addDevice(Device device) {
_devices.add(device); _devices.add(device);
_onAddedController.add(device); _onAddedController.add(device);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment