Separate attached and wireless devices (#122615)

Separate attached and wireless devices
This commit is contained in:
Victoria Ashworth 2023-03-15 11:35:05 -05:00 committed by GitHub
parent 5d10cc28ca
commit 27248d4b64
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 1637 additions and 661 deletions

View File

@ -89,6 +89,14 @@ class AndroidDevice extends Device {
final String modelID;
final String? deviceCodeName;
@override
// Wirelessly paired Android devices should have `adb-tls-connect` in the id.
// Source: https://android.googlesource.com/platform/packages/modules/adb/+/f4ba8d73079b99532069dbe888a58167b8723d6c/adb_mdns.h#30
DeviceConnectionInterface get connectionInterface =>
id.contains('adb-tls-connect')
? DeviceConnectionInterface.wireless
: DeviceConnectionInterface.attached;
late final Future<Map<String, String>> _properties = () async {
Map<String, String> properties = <String, String>{};

View File

@ -265,7 +265,7 @@ class UserMessages {
'for information about installing additional components.';
String flutterNoMatchingDevice(String deviceId) => 'No supported devices found with name or id '
"matching '$deviceId'.";
String get flutterNoDevicesFound => 'No devices found';
String get flutterNoDevicesFound => 'No devices found.';
String get flutterNoSupportedDevices => 'No supported devices connected.';
String flutterMissPlatformProjects(List<String> unsupportedDevicesType) =>
'If you would like your app to run on ${unsupportedDevicesType.join(' or ')}, consider running `flutter create .` to generate projects for these platforms.';

View File

@ -24,7 +24,6 @@ import '../device.dart';
import '../device_port_forwarder.dart';
import '../fuchsia/fuchsia_device.dart';
import '../ios/devices.dart';
import '../ios/iproxy.dart';
import '../ios/simulators.dart';
import '../macos/macos_ipad_device.dart';
import '../mdns_discovery.dart';
@ -287,7 +286,7 @@ known, it can be explicitly provided to attach via the command-line, e.g.
final String ipv6Loopback = InternetAddress.loopbackIPv6.address;
final String ipv4Loopback = InternetAddress.loopbackIPv4.address;
final String hostname = usesIpv6 ? ipv6Loopback : ipv4Loopback;
final bool isNetworkDevice = (device is IOSDevice) && device.interfaceType == IOSDeviceConnectionInterface.network;
final bool isNetworkDevice = (device is IOSDevice) && device.isWirelesslyConnected;
if ((debugPort == null && debugUri == null) || isNetworkDevice) {
if (device is FuchsiaDevice) {

View File

@ -79,13 +79,59 @@ class DevicesCommandOutput {
final Duration? deviceDiscoveryTimeout;
Future<List<Device>> _getAttachedDevices(DeviceManager deviceManager) async {
return deviceManager.getAllDevices(
filter: DeviceDiscoveryFilter(
deviceConnectionInterface: DeviceConnectionInterface.attached,
),
);
}
Future<List<Device>> _getWirelessDevices(DeviceManager deviceManager) async {
return deviceManager.getAllDevices(
filter: DeviceDiscoveryFilter(
deviceConnectionInterface: DeviceConnectionInterface.wireless,
),
);
}
Future<void> findAndOutputAllTargetDevices({required bool machine}) async {
final List<Device> devices = await globals.deviceManager?.refreshAllDevices(timeout: deviceDiscoveryTimeout) ?? <Device>[];
List<Device> attachedDevices = <Device>[];
List<Device> wirelessDevices = <Device>[];
final DeviceManager? deviceManager = globals.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(devices);
await printDevicesAsJson(allDevices);
return;
}
if (allDevices.isEmpty) {
_printNoDevicesDetected();
} else {
if (devices.isEmpty) {
if (attachedDevices.isNotEmpty) {
globals.printStatus('${attachedDevices.length} connected ${pluralize('device', attachedDevices.length)}:\n');
await Device.printDevices(attachedDevices, globals.logger);
}
if (wirelessDevices.isNotEmpty) {
if (attachedDevices.isNotEmpty) {
globals.printStatus('');
}
globals.printStatus('${wirelessDevices.length} wirelessly connected ${pluralize('device', wirelessDevices.length)}:\n');
await Device.printDevices(wirelessDevices, globals.logger);
}
}
await _printDiagnostics();
}
void _printNoDevicesDetected() {
final StringBuffer status = StringBuffer('No devices detected.');
status.writeln();
status.writeln();
@ -98,12 +144,6 @@ class DevicesCommandOutput {
status.write('Visit https://flutter.dev/setup/ for troubleshooting tips.');
globals.printStatus(status.toString());
} else {
globals.printStatus('${devices.length} connected ${pluralize('device', devices.length)}:\n');
await Device.printDevices(devices, globals.logger);
}
await _printDiagnostics();
}
}
Future<void> _printDiagnostics() async {

View File

@ -23,7 +23,6 @@ import '../device.dart';
import '../drive/drive_service.dart';
import '../globals.dart' as globals;
import '../ios/devices.dart';
import '../ios/iproxy.dart';
import '../resident_runner.dart';
import '../runner/flutter_command.dart' show FlutterCommandCategory, FlutterCommandResult, FlutterOptions;
import '../web/web_device.dart';
@ -220,7 +219,7 @@ class DriveCommand extends RunCommandBase {
Future<bool> get disablePortPublication async {
final ArgResults? localArgResults = argResults;
final Device? device = await targetedDevice;
final bool isNetworkDevice = device is IOSDevice && device.interfaceType == IOSDeviceConnectionInterface.network;
final bool isNetworkDevice = device is IOSDevice && device.isWirelesslyConnected;
if (isNetworkDevice && localArgResults != null && !localArgResults.wasParsed('publish-port')) {
_logger.printTrace('Network device is being used. Changing `publish-port` to be enabled.');
return false;

View File

@ -20,7 +20,6 @@ import '../device.dart';
import '../features.dart';
import '../globals.dart' as globals;
import '../ios/devices.dart';
import '../ios/iproxy.dart';
import '../project.dart';
import '../reporting/reporting.dart';
import '../resident_runner.dart';
@ -426,7 +425,7 @@ class RunCommand extends RunCommandBase {
final TargetPlatform platform = await device.targetPlatform;
anyAndroidDevices = platform == TargetPlatform.android;
anyIOSDevices = platform == TargetPlatform.ios;
if (device is IOSDevice && device.interfaceType == IOSDeviceConnectionInterface.network) {
if (device is IOSDevice && device.isWirelesslyConnected) {
anyIOSNetworkDevices = true;
}
deviceType = getNameForTargetPlatform(platform);
@ -440,7 +439,7 @@ class RunCommand extends RunCommandBase {
final TargetPlatform platform = await device.targetPlatform;
anyAndroidDevices = anyAndroidDevices || (platform == TargetPlatform.android);
anyIOSDevices = anyIOSDevices || (platform == TargetPlatform.ios);
if (device is IOSDevice && device.interfaceType == IOSDeviceConnectionInterface.network) {
if (device is IOSDevice && device.isWirelesslyConnected) {
anyIOSNetworkDevices = true;
}
if (anyAndroidDevices && anyIOSDevices) {

View File

@ -17,7 +17,6 @@ import 'base/utils.dart';
import 'build_info.dart';
import 'devfs.dart';
import 'device_port_forwarder.dart';
import 'ios/iproxy.dart';
import 'project.dart';
import 'vmservice.dart';
@ -1098,7 +1097,7 @@ class DebuggingOptions {
String? route,
Map<String, Object?> platformArgs, {
bool ipv6 = false,
IOSDeviceConnectionInterface interfaceType = IOSDeviceConnectionInterface.none
DeviceConnectionInterface interfaceType = DeviceConnectionInterface.attached,
}) {
final String dartVmFlags = computeDartVmFlags(this);
return <String>[
@ -1137,7 +1136,7 @@ class DebuggingOptions {
if (environmentType == EnvironmentType.simulator && hostVmServicePort != null)
'--vm-service-port=$hostVmServicePort',
// Tell the VM service to listen on all interfaces, don't restrict to the loopback.
if (interfaceType == IOSDeviceConnectionInterface.network)
if (interfaceType == DeviceConnectionInterface.wireless)
'--vm-service-host=${ipv6 ? '::0' : '0.0.0.0'}',
if (enableEmbedderApi) '--enable-embedder-api',
];

View File

@ -151,7 +151,7 @@ class IOSDevice extends Device {
required FileSystem fileSystem,
required this.name,
required this.cpuArchitecture,
required this.interfaceType,
required this.connectionInterface,
String? sdkVersion,
required Platform platform,
required IOSDeploy iosDeploy,
@ -199,7 +199,8 @@ class IOSDevice extends Device {
final DarwinArch cpuArchitecture;
final IOSDeviceConnectionInterface interfaceType;
@override
final DeviceConnectionInterface connectionInterface;
final Map<IOSApp?, DeviceLogReader> _logReaders = <IOSApp?, DeviceLogReader>{};
@ -256,7 +257,7 @@ class IOSDevice extends Device {
bundlePath: bundle.path,
appDeltaDirectory: app.appDeltaDirectory,
launchArguments: <String>[],
interfaceType: interfaceType,
interfaceType: connectionInterface,
);
} on ProcessException catch (e) {
_logger.printError(e.message);
@ -311,7 +312,7 @@ class IOSDevice extends Device {
@visibleForTesting Duration? discoveryTimeout,
}) async {
String? packageId;
if (interfaceType == IOSDeviceConnectionInterface.network &&
if (isWirelesslyConnected &&
debuggingOptions.debuggingEnabled &&
debuggingOptions.disablePortPublication) {
throwToolExit('Cannot start app on wirelessly tethered iOS device. Try running again with the --publish-port flag');
@ -351,7 +352,7 @@ class IOSDevice extends Device {
route,
platformArgs,
ipv6: ipv6,
interfaceType: interfaceType,
interfaceType: connectionInterface,
);
Status startAppStatus = _logger.startProgress(
'Installing and launching...',
@ -371,7 +372,7 @@ class IOSDevice extends Device {
bundlePath: bundle.path,
appDeltaDirectory: package.appDeltaDirectory,
launchArguments: launchArguments,
interfaceType: interfaceType,
interfaceType: connectionInterface,
uninstallFirst: debuggingOptions.uninstallFirst,
);
if (deviceLogReader is IOSDeviceLogReader) {
@ -381,7 +382,7 @@ class IOSDevice extends Device {
// Don't port foward if debugging with a network device.
vmServiceDiscovery = ProtocolDiscovery.vmService(
deviceLogReader,
portForwarder: interfaceType == IOSDeviceConnectionInterface.network ? null : portForwarder,
portForwarder: isWirelesslyConnected ? null : portForwarder,
hostPort: debuggingOptions.hostVmServicePort,
devicePort: debuggingOptions.deviceVmServicePort,
ipv6: ipv6,
@ -394,7 +395,7 @@ class IOSDevice extends Device {
bundlePath: bundle.path,
appDeltaDirectory: package.appDeltaDirectory,
launchArguments: launchArguments,
interfaceType: interfaceType,
interfaceType: connectionInterface,
uninstallFirst: debuggingOptions.uninstallFirst,
);
} else {
@ -414,13 +415,13 @@ class IOSDevice extends Device {
_logger.printTrace('Application launched on the device. Waiting for Dart VM Service url.');
final int defaultTimeout = interfaceType == IOSDeviceConnectionInterface.network ? 45 : 30;
final int defaultTimeout = isWirelesslyConnected ? 45 : 30;
final Timer timer = Timer(discoveryTimeout ?? Duration(seconds: defaultTimeout), () {
_logger.printError('The Dart VM Service was not discovered after $defaultTimeout seconds. This is taking much longer than expected...');
// If debugging with a wireless device and the timeout is reached, remind the
// user to allow local network permissions.
if (interfaceType == IOSDeviceConnectionInterface.network) {
if (isWirelesslyConnected) {
_logger.printError(
'\nClick "Allow" to the prompt asking if you would like to find and connect devices on your local network. '
'This is required for wireless debugging. If you selected "Don\'t Allow", '
@ -433,7 +434,7 @@ class IOSDevice extends Device {
});
Uri? localUri;
if (interfaceType == IOSDeviceConnectionInterface.network) {
if (isWirelesslyConnected) {
// Wait for Dart VM Service to start up.
final Uri? serviceURL = await vmServiceDiscovery?.uri;
if (serviceURL == null) {
@ -538,7 +539,7 @@ class IOSDevice extends Device {
@override
Future<void> takeScreenshot(File outputFile) async {
await _iMobileDevice.takeScreenshot(outputFile, id, interfaceType);
await _iMobileDevice.takeScreenshot(outputFile, id, connectionInterface);
}
@override

View File

@ -15,8 +15,8 @@ import '../base/platform.dart';
import '../base/process.dart';
import '../cache.dart';
import '../convert.dart';
import '../device.dart';
import 'code_signing.dart';
import 'iproxy.dart';
// Error message patterns from ios-deploy output
const String noProvisioningProfileErrorOne = 'Error 0xe8008015';
@ -88,7 +88,7 @@ class IOSDeploy {
required String deviceId,
required String bundlePath,
required List<String>launchArguments,
required IOSDeviceConnectionInterface interfaceType,
required DeviceConnectionInterface interfaceType,
Directory? appDeltaDirectory,
}) async {
appDeltaDirectory?.createSync(recursive: true);
@ -102,7 +102,7 @@ class IOSDeploy {
'--app_deltas',
appDeltaDirectory.path,
],
if (interfaceType != IOSDeviceConnectionInterface.network)
if (interfaceType != DeviceConnectionInterface.wireless)
'--no-wifi',
if (launchArguments.isNotEmpty) ...<String>[
'--args',
@ -126,7 +126,7 @@ class IOSDeploy {
required String deviceId,
required String bundlePath,
required List<String> launchArguments,
required IOSDeviceConnectionInterface interfaceType,
required DeviceConnectionInterface interfaceType,
Directory? appDeltaDirectory,
required bool uninstallFirst,
}) {
@ -149,7 +149,7 @@ class IOSDeploy {
if (uninstallFirst)
'--uninstall',
'--debug',
if (interfaceType != IOSDeviceConnectionInterface.network)
if (interfaceType != DeviceConnectionInterface.wireless)
'--no-wifi',
if (launchArguments.isNotEmpty) ...<String>[
'--args',
@ -171,7 +171,7 @@ class IOSDeploy {
required String deviceId,
required String bundlePath,
required List<String> launchArguments,
required IOSDeviceConnectionInterface interfaceType,
required DeviceConnectionInterface interfaceType,
required bool uninstallFirst,
Directory? appDeltaDirectory,
}) async {
@ -186,7 +186,7 @@ class IOSDeploy {
'--app_deltas',
appDeltaDirectory.path,
],
if (interfaceType != IOSDeviceConnectionInterface.network)
if (interfaceType != DeviceConnectionInterface.wireless)
'--no-wifi',
if (uninstallFirst)
'--uninstall',

View File

@ -8,12 +8,6 @@ import '../base/io.dart';
import '../base/logger.dart';
import '../base/process.dart';
enum IOSDeviceConnectionInterface {
none,
usb,
network,
}
/// Wraps iproxy command line tool port forwarding.
///
/// See https://github.com/libimobiledevice/libusbmuxd.

View File

@ -16,6 +16,7 @@ import '../base/project_migrator.dart';
import '../base/utils.dart';
import '../build_info.dart';
import '../cache.dart';
import '../device.dart';
import '../flutter_manifest.dart';
import '../globals.dart' as globals;
import '../macos/cocoapod_utils.dart';
@ -27,7 +28,6 @@ import '../project.dart';
import '../reporting/reporting.dart';
import 'application_package.dart';
import 'code_signing.dart';
import 'iproxy.dart';
import 'migrations/host_app_info_plist_migration.dart';
import 'migrations/ios_deployment_target_migration.dart';
import 'migrations/project_base_configuration_migration.dart';
@ -87,7 +87,7 @@ class IMobileDevice {
Future<void> takeScreenshot(
File outputFile,
String deviceID,
IOSDeviceConnectionInterface interfaceType,
DeviceConnectionInterface interfaceType,
) {
return _processUtils.run(
<String>[
@ -95,7 +95,7 @@ class IMobileDevice {
outputFile.path,
'--udid',
deviceID,
if (interfaceType == IOSDeviceConnectionInterface.network)
if (interfaceType == DeviceConnectionInterface.wireless)
'--network',
],
throwOnError: true,

View File

@ -14,6 +14,7 @@ import '../base/process.dart';
import '../build_info.dart';
import '../cache.dart';
import '../convert.dart';
import '../device.dart';
import '../globals.dart' as globals;
import '../ios/devices.dart';
import '../ios/ios_deploy.dart';
@ -303,8 +304,6 @@ class XCDevice {
}
}
final IOSDeviceConnectionInterface interface = _interfaceType(device);
String? sdkVersion = _sdkVersion(device);
if (sdkVersion != null) {
@ -318,7 +317,7 @@ class XCDevice {
identifier,
name: name,
cpuArchitecture: _cpuArchitecture(device),
interfaceType: interface,
connectionInterface: _interfaceType(device),
sdkVersion: sdkVersion,
iProxy: _iProxy,
fileSystem: globals.fs,
@ -356,19 +355,16 @@ class XCDevice {
return code is int ? code : null;
}
static IOSDeviceConnectionInterface _interfaceType(Map<String, Object?> deviceProperties) {
// Interface can be "usb", "network", or "none" for simulators
// and unknown future interfaces.
static DeviceConnectionInterface _interfaceType(Map<String, Object?> deviceProperties) {
// Interface can be "usb" or "network". It can also be missing
// (e.g. simulators do not have an interface property).
// If the interface is "network", use `DeviceConnectionInterface.wireless`,
// otherwise use `DeviceConnectionInterface.attached.
final Object? interface = deviceProperties['interface'];
if (interface is String) {
if (interface.toLowerCase() == 'network') {
return IOSDeviceConnectionInterface.network;
} else {
return IOSDeviceConnectionInterface.usb;
if (interface is String && interface.toLowerCase() == 'network') {
return DeviceConnectionInterface.wireless;
}
}
return IOSDeviceConnectionInterface.none;
return DeviceConnectionInterface.attached;
}
static String? _sdkVersion(Map<String, Object?> deviceProperties) {

View File

@ -2,14 +2,18 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:meta/meta.dart';
import '../base/common.dart';
import '../base/logger.dart';
import '../base/user_messages.dart';
import '../device.dart';
import '../globals.dart' as globals;
const String _wirelesslyConnectedDevicesMessage = 'Wirelessly connected devices:';
/// This class handles functionality of finding and selecting target devices.
///
/// Target devices are devices that are supported and selectable to run
/// a flutter application on.
class TargetDevices {
TargetDevices({
required DeviceManager deviceManager,
@ -20,10 +24,64 @@ class TargetDevices {
final DeviceManager _deviceManager;
final Logger _logger;
/// Find and return all target [Device]s based upon currently connected
/// devices and criteria entered by the user on the command line.
/// If no device can be found that meets specified criteria,
/// then print an error message and return null.
Future<List<Device>> _getAttachedDevices({
DeviceDiscoverySupportFilter? supportFilter,
}) async {
return _deviceManager.getDevices(
filter: DeviceDiscoveryFilter(
deviceConnectionInterface: DeviceConnectionInterface.attached,
supportFilter: supportFilter,
),
);
}
Future<List<Device>> _getWirelessDevices({
DeviceDiscoverySupportFilter? supportFilter,
}) async {
return _deviceManager.getDevices(
filter: DeviceDiscoveryFilter(
deviceConnectionInterface: DeviceConnectionInterface.wireless,
supportFilter: supportFilter,
),
);
}
Future<List<Device>> _getDeviceById({
bool includeDevicesUnsupportedByProject = false,
}) async {
return _deviceManager.getDevices(
filter: DeviceDiscoveryFilter(
supportFilter: _deviceManager.deviceSupportFilter(
includeDevicesUnsupportedByProject: includeDevicesUnsupportedByProject,
),
),
);
}
DeviceDiscoverySupportFilter _defaultSupportFilter(
bool includeDevicesUnsupportedByProject,
) {
return _deviceManager.deviceSupportFilter(
includeDevicesUnsupportedByProject: includeDevicesUnsupportedByProject,
);
}
/// Find and return all target [Device]s based upon criteria entered by the
/// user on the command line.
///
/// When the user has specified `all` devices, return all devices meeting criteria.
///
/// When the user has specified a device id/name, attempt to find an exact or
/// partial match. If an exact match or a single partial match is found,
/// return it immediately.
///
/// When multiple devices are found and there is a terminal attached to
/// stdin, allow the user to select which device to use. When a terminal
/// with stdin is not available, print a list of available devices and
/// return null.
///
/// When no devices meet user specifications, print a list of unsupported
/// devices and return null.
Future<List<Device>?> findAllTargetDevices({
Duration? deviceDiscoveryTimeout,
bool includeDevicesUnsupportedByProject = false,
@ -32,67 +90,175 @@ class TargetDevices {
_logger.printError(userMessages.flutterNoDevelopmentDevice);
return null;
}
List<Device> devices = await getDevices(
if (deviceDiscoveryTimeout != null) {
// Reset the cache with the specified timeout.
await _deviceManager.refreshAllDevices(timeout: deviceDiscoveryTimeout);
}
if (_deviceManager.hasSpecifiedDeviceId) {
// Must check for device match separately from `_getAttachedDevices` and
// `_getWirelessDevices` because if an exact match is found in one
// and a partial match is found in another, there is no way to distinguish
// between them.
final List<Device> devices = await _getDeviceById(
includeDevicesUnsupportedByProject: includeDevicesUnsupportedByProject,
timeout: deviceDiscoveryTimeout,
);
if (devices.isEmpty) {
if (_deviceManager.hasSpecifiedDeviceId) {
_logger.printStatus(userMessages.flutterNoMatchingDevice(_deviceManager.specifiedDeviceId!));
final List<Device> allDevices = await _deviceManager.getAllDevices();
if (allDevices.isNotEmpty) {
_logger.printStatus('');
_logger.printStatus('The following devices were found:');
await Device.printDevices(allDevices, _logger);
}
return null;
} else if (_deviceManager.hasSpecifiedAllDevices) {
_logger.printStatus(userMessages.flutterNoDevicesFound);
await _printUnsupportedDevice(_deviceManager);
return null;
} else {
_logger.printStatus(userMessages.flutterNoSupportedDevices);
await _printUnsupportedDevice(_deviceManager);
return null;
}
} else if (devices.length > 1) {
if (_deviceManager.hasSpecifiedDeviceId) {
_logger.printStatus(userMessages.flutterFoundSpecifiedDevices(devices.length, _deviceManager.specifiedDeviceId!));
return null;
} else if (!_deviceManager.hasSpecifiedAllDevices) {
if (globals.terminal.stdinHasTerminal) {
// If DeviceManager was not able to prioritize a device. For example, if the user
// has two active Android devices running, then we request the user to
// choose one. If the user has two nonEphemeral devices running, we also
// request input to choose one.
_logger.printStatus(userMessages.flutterMultipleDevicesFound);
await Device.printDevices(devices, _logger);
final Device chosenDevice = await _chooseOneOfAvailableDevices(devices);
// Update the [DeviceManager.specifiedDeviceId] so that we will not be prompted again.
_deviceManager.specifiedDeviceId = chosenDevice.id;
devices = <Device>[chosenDevice];
} else {
// Show an error message asking the user to specify `-d all` if they
// want to run on multiple devices.
final List<Device> allDevices = await _deviceManager.getAllDevices();
_logger.printStatus(userMessages.flutterSpecifyDeviceWithAllOption);
_logger.printStatus('');
await Device.printDevices(allDevices, _logger);
return null;
}
}
}
if (devices.length == 1) {
return devices;
}
}
Future<void> _printUnsupportedDevice(DeviceManager deviceManager) async {
final List<Device> unsupportedDevices = await deviceManager.getDevices();
final List<Device> attachedDevices = await _getAttachedDevices(
supportFilter: _defaultSupportFilter(includeDevicesUnsupportedByProject),
);
final List<Device> wirelessDevices = await _getWirelessDevices(
supportFilter: _defaultSupportFilter(includeDevicesUnsupportedByProject),
);
final List<Device> allDevices = attachedDevices + wirelessDevices;
if (allDevices.isEmpty) {
return _handleNoDevices();
} else if (_deviceManager.hasSpecifiedAllDevices) {
return allDevices;
} else if (allDevices.length > 1) {
return _handleMultipleDevices(attachedDevices, wirelessDevices);
}
return allDevices;
}
/// When no supported devices are found, display a message and list of
/// unsupported devices found.
Future<List<Device>?> _handleNoDevices() async {
// Get connected devices from cache, including unsupported ones.
final List<Device> unsupportedDevices = await _deviceManager.getAllDevices();
if (_deviceManager.hasSpecifiedDeviceId) {
_logger.printStatus(
userMessages.flutterNoMatchingDevice(_deviceManager.specifiedDeviceId!),
);
if (unsupportedDevices.isNotEmpty) {
_logger.printStatus('');
_logger.printStatus('The following devices were found:');
await Device.printDevices(unsupportedDevices, _logger);
}
return null;
}
_logger.printStatus(_deviceManager.hasSpecifiedAllDevices
? userMessages.flutterNoDevicesFound
: userMessages.flutterNoSupportedDevices);
await _printUnsupportedDevice(unsupportedDevices);
return null;
}
/// Determine which device to use when multiple found.
///
/// If user has not specified a device id/name, attempt to prioritize
/// ephemeral devices. If a single ephemeral device is found, return it
/// immediately.
///
/// Otherwise, prompt the user to select a device if there is a terminal
/// with stdin. If there is not a terminal, display the list of devices with
/// instructions to use a device selection flag.
Future<List<Device>?> _handleMultipleDevices(
List<Device> attachedDevices,
List<Device> wirelessDevices,
) async {
final List<Device> allDevices = attachedDevices + wirelessDevices;
final Device? ephemeralDevice = _deviceManager.getSingleEphemeralDevice(allDevices);
if (ephemeralDevice != null) {
return <Device>[ephemeralDevice];
}
if (globals.terminal.stdinHasTerminal) {
return _selectFromMultipleDevices(attachedDevices, wirelessDevices);
} else {
return _printMultipleDevices(attachedDevices, wirelessDevices);
}
}
/// Display a list of found devices. When the user has not specified the
/// device id/name, display devices unsupported by the project as well and
/// give instructions to use a device selection flag.
Future<List<Device>?> _printMultipleDevices(
List<Device> attachedDevices,
List<Device> wirelessDevices,
) async {
List<Device> supportedAttachedDevices = attachedDevices;
List<Device> supportedWirelessDevices = wirelessDevices;
if (_deviceManager.hasSpecifiedDeviceId) {
final int allDeviceLength = supportedAttachedDevices.length + supportedWirelessDevices.length;
_logger.printStatus(userMessages.flutterFoundSpecifiedDevices(
allDeviceLength,
_deviceManager.specifiedDeviceId!,
));
} else {
// Get connected devices from cache, including ones unsupported for the
// project but still supported by Flutter.
supportedAttachedDevices = await _getAttachedDevices(
supportFilter: DeviceDiscoverySupportFilter.excludeDevicesUnsupportedByFlutter(),
);
supportedWirelessDevices = await _getWirelessDevices(
supportFilter: DeviceDiscoverySupportFilter.excludeDevicesUnsupportedByFlutter(),
);
_logger.printStatus(userMessages.flutterSpecifyDeviceWithAllOption);
_logger.printStatus('');
}
await Device.printDevices(supportedAttachedDevices, _logger);
if (supportedWirelessDevices.isNotEmpty) {
if (_deviceManager.hasSpecifiedDeviceId || supportedAttachedDevices.isNotEmpty) {
_logger.printStatus('');
}
_logger.printStatus(_wirelesslyConnectedDevicesMessage);
await Device.printDevices(supportedWirelessDevices, _logger);
}
return null;
}
/// Display a list of selectable devices, prompt the user to choose one, and
/// wait for the user to select a valid option.
Future<List<Device>?> _selectFromMultipleDevices(
List<Device> attachedDevices,
List<Device> wirelessDevices,
) async {
final List<Device> allDevices = attachedDevices + wirelessDevices;
if (_deviceManager.hasSpecifiedDeviceId) {
_logger.printStatus(userMessages.flutterFoundSpecifiedDevices(
allDevices.length,
_deviceManager.specifiedDeviceId!,
));
} else {
_logger.printStatus(userMessages.flutterMultipleDevicesFound);
}
await Device.printDevices(attachedDevices, _logger);
if (wirelessDevices.isNotEmpty) {
_logger.printStatus('');
_logger.printStatus(_wirelesslyConnectedDevicesMessage);
await Device.printDevices(wirelessDevices, _logger);
_logger.printStatus('');
}
final Device chosenDevice = await _chooseOneOfAvailableDevices(allDevices);
// Update the [DeviceManager.specifiedDeviceId] so that the user will not be prompted again.
_deviceManager.specifiedDeviceId = chosenDevice.id;
return <Device>[chosenDevice];
}
Future<void> _printUnsupportedDevice(List<Device> unsupportedDevices) async {
if (unsupportedDevices.isNotEmpty) {
final StringBuffer result = StringBuffer();
result.writeln();
result.writeln(userMessages.flutterFoundButUnsupportedDevices);
result.writeAll(
(await Device.descriptions(unsupportedDevices))
@ -104,7 +270,7 @@ class TargetDevices {
result.writeln(userMessages.flutterMissPlatformProjects(
Device.devicesPlatformTypes(unsupportedDevices),
));
_logger.printStatus(result.toString());
_logger.printStatus(result.toString(), newline: false);
}
}
@ -135,48 +301,4 @@ class TargetDevices {
);
return result;
}
/// Find and return all target [Device]s based upon currently connected
/// devices, the current project, and criteria entered by the user on
/// the command line.
///
/// Returns a list of devices specified by the user.
///
/// * If the user specified '-d all', then return all connected devices which
/// support the current project, except for fuchsia and web.
///
/// * If the user specified a device id, then do nothing as the list is already
/// filtered by [_deviceManager.getDevices].
///
/// * If the user did not specify a device id and there is more than one
/// device connected, then filter out unsupported devices and prioritize
/// ephemeral devices.
@visibleForTesting
Future<List<Device>> getDevices({
bool includeDevicesUnsupportedByProject = false,
Duration? timeout,
}) async {
if (timeout != null) {
// Reset the cache with the specified timeout.
await _deviceManager.refreshAllDevices(timeout: timeout);
}
final List<Device> devices = await _deviceManager.getDevices(
filter: DeviceDiscoveryFilter(
supportFilter: _deviceManager.deviceSupportFilter(
includeDevicesUnsupportedByProject: includeDevicesUnsupportedByProject,
),
),
);
// If there is more than one device, attempt to prioritize ephemeral devices.
if (devices.length > 1) {
final Device? ephemeralDevice = _deviceManager.getSingleEphemeralDevice(devices);
if (ephemeralDevice != null) {
return <Device>[ephemeralDevice];
}
}
return devices;
}
}

View File

@ -23,7 +23,6 @@ import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/device_port_forwarder.dart';
import 'package:flutter_tools/src/ios/application_package.dart';
import 'package:flutter_tools/src/ios/devices.dart';
import 'package:flutter_tools/src/ios/iproxy.dart';
import 'package:flutter_tools/src/macos/macos_ipad_device.dart';
import 'package:flutter_tools/src/mdns_discovery.dart';
import 'package:flutter_tools/src/project.dart';
@ -241,7 +240,7 @@ void main() {
logReader: fakeLogReader,
portForwarder: portForwarder,
majorSdkVersion: 16,
interfaceType: IOSDeviceConnectionInterface.network,
connectionInterface: DeviceConnectionInterface.wireless,
);
testDeviceManager.devices = <Device>[device];
final FakeHotRunner hotRunner = FakeHotRunner();
@ -313,7 +312,7 @@ void main() {
logReader: fakeLogReader,
portForwarder: portForwarder,
majorSdkVersion: 16,
interfaceType: IOSDeviceConnectionInterface.network,
connectionInterface: DeviceConnectionInterface.wireless,
);
testDeviceManager.devices = <Device>[device];
final FakeHotRunner hotRunner = FakeHotRunner();
@ -389,7 +388,7 @@ void main() {
logReader: fakeLogReader,
portForwarder: portForwarder,
majorSdkVersion: 16,
interfaceType: IOSDeviceConnectionInterface.network,
connectionInterface: DeviceConnectionInterface.wireless,
);
testDeviceManager.devices = <Device>[device];
final FakeHotRunner hotRunner = FakeHotRunner();
@ -1237,6 +1236,10 @@ class FakeAndroidDevice extends Fake implements AndroidDevice {
@override
Future<TargetPlatform> get targetPlatform async => TargetPlatform.android_arm;
@override
DeviceConnectionInterface get connectionInterface =>
DeviceConnectionInterface.attached;
@override
bool isSupported() => true;
@ -1291,7 +1294,7 @@ class FakeIOSDevice extends Fake implements IOSDevice {
DevicePortForwarder? portForwarder,
DeviceLogReader? logReader,
this.onGetLogReader,
this.interfaceType = IOSDeviceConnectionInterface.none,
this.connectionInterface = DeviceConnectionInterface.attached,
this.majorSdkVersion = 0,
}) : _portForwarder = portForwarder, _logReader = logReader;
@ -1300,7 +1303,11 @@ class FakeIOSDevice extends Fake implements IOSDevice {
int majorSdkVersion;
@override
final IOSDeviceConnectionInterface interfaceType;
final DeviceConnectionInterface connectionInterface;
@override
bool get isWirelesslyConnected =>
connectionInterface == DeviceConnectionInterface.wireless;
@override
DevicePortForwarder get portForwarder => _portForwarder!;

View File

@ -6,12 +6,12 @@ import 'dart:convert';
import 'package:flutter_tools/src/android/android_sdk.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/devices.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/fake_devices.dart';
import '../../src/test_flutter_command_runner.dart';
@ -23,9 +23,11 @@ void main() {
});
late Cache cache;
late Platform platform;
setUp(() {
cache = Cache.test(processManager: FakeProcessManager.any());
platform = FakePlatform();
});
testUsingContext('returns 0 when called', () async {
@ -39,7 +41,16 @@ void main() {
testUsingContext('no error when no connected devices', () async {
final DevicesCommand command = DevicesCommand();
await createTestCommandRunner(command).run(<String>['devices']);
expect(testLogger.statusText, containsIgnoringWhitespace('No devices detected'));
expect(
testLogger.statusText,
equals('''
No devices detected.
Run "flutter emulators" to list and start any available device emulators.
If you expected your device to be detected, please run "flutter doctor" to diagnose potential issues. You may also try increasing the time to wait for connected devices with the --device-timeout flag. Visit https://flutter.dev/setup/ for troubleshooting tips.
'''),
);
}, overrides: <Type, Generator>{
AndroidSdk: () => null,
DeviceManager: () => NoDevicesManager(),
@ -48,16 +59,27 @@ void main() {
Artifacts: () => Artifacts.test(),
});
group('when includes both attached and wireless devices', () {
List<FakeDeviceJsonData>? deviceList;
setUp(() {
deviceList = <FakeDeviceJsonData>[
fakeDevices[0],
fakeDevices[1],
fakeDevices[2],
];
});
testUsingContext("get devices' platform types", () async {
final List<String> platformTypes = Device.devicesPlatformTypes(
await globals.deviceManager!.getAllDevices(),
);
expect(platformTypes, <String>['android', 'web']);
}, overrides: <Type, Generator>{
DeviceManager: () => _FakeDeviceManager(),
DeviceManager: () => _FakeDeviceManager(devices: deviceList),
ProcessManager: () => FakeProcessManager.any(),
Cache: () => cache,
Artifacts: () => Artifacts.test(),
Platform: () => platform,
});
testUsingContext('Outputs parsable JSON with --machine flag', () async {
@ -66,76 +88,114 @@ void main() {
expect(
json.decode(testLogger.statusText),
<Map<String, Object>>[
<String, Object>{
'name': 'ephemeral',
'id': 'ephemeral',
'isSupported': true,
'targetPlatform': 'android-arm',
'emulator': true,
'sdk': 'Test SDK (1.2.3)',
'capabilities': <String, Object>{
'hotReload': true,
'hotRestart': true,
'screenshot': false,
'fastStart': false,
'flutterExit': true,
'hardwareRendering': true,
'startPaused': true,
},
},
<String,Object>{
'name': 'webby',
'id': 'webby',
'isSupported': true,
'targetPlatform': 'web-javascript',
'emulator': true,
'sdk': 'Web SDK (1.2.4)',
'capabilities': <String, Object>{
'hotReload': true,
'hotRestart': true,
'screenshot': false,
'fastStart': false,
'flutterExit': true,
'hardwareRendering': true,
'startPaused': true,
},
},
fakeDevices[0].json,
fakeDevices[1].json,
fakeDevices[2].json,
],
);
}, overrides: <Type, Generator>{
DeviceManager: () => _FakeDeviceManager(),
DeviceManager: () => _FakeDeviceManager(devices: deviceList),
ProcessManager: () => FakeProcessManager.any(),
Cache: () => cache,
Artifacts: () => Artifacts.test(),
Platform: () => platform,
});
testUsingContext('available devices and diagnostics', () async {
final DevicesCommand command = DevicesCommand();
await createTestCommandRunner(command).run(<String>['devices']);
expect(
testLogger.statusText,
'''
expect(testLogger.statusText, '''
2 connected devices:
ephemeral (mobile) ephemeral android-arm Test SDK (1.2.3) (emulator)
webby (mobile) webby web-javascript Web SDK (1.2.4) (emulator)
1 wirelessly connected device:
wireless android (mobile) wireless-android android-arm Test SDK (1.2.3) (emulator)
Cannot connect to device ABC
''');
}, overrides: <Type, Generator>{
DeviceManager: () => _FakeDeviceManager(devices: deviceList),
ProcessManager: () => FakeProcessManager.any(),
Platform: () => platform,
});
});
group('when includes only attached devices', () {
List<FakeDeviceJsonData>? deviceList;
setUp(() {
deviceList = <FakeDeviceJsonData>[
fakeDevices[0],
fakeDevices[1],
];
});
testUsingContext('available devices and diagnostics', () async {
final DevicesCommand command = DevicesCommand();
await createTestCommandRunner(command).run(<String>['devices']);
expect(testLogger.statusText, '''
2 connected devices:
ephemeral (mobile) ephemeral android-arm Test SDK (1.2.3) (emulator)
webby (mobile) webby web-javascript Web SDK (1.2.4) (emulator)
Cannot connect to device ABC
'''
);
''');
}, overrides: <Type, Generator>{
DeviceManager: () => _FakeDeviceManager(),
DeviceManager: () => _FakeDeviceManager(devices: deviceList),
ProcessManager: () => FakeProcessManager.any(),
Platform: () => platform,
});
});
group('when includes only wireless devices', () {
List<FakeDeviceJsonData>? deviceList;
setUp(() {
deviceList = <FakeDeviceJsonData>[
fakeDevices[2],
];
});
testUsingContext('available devices and diagnostics', () async {
final DevicesCommand command = DevicesCommand();
await createTestCommandRunner(command).run(<String>['devices']);
expect(testLogger.statusText, '''
1 wirelessly connected device:
wireless android (mobile) wireless-android android-arm Test SDK (1.2.3) (emulator)
Cannot connect to device ABC
''');
}, overrides: <Type, Generator>{
DeviceManager: () => _FakeDeviceManager(devices: deviceList),
ProcessManager: () => FakeProcessManager.any(),
Platform: () => platform,
});
});
});
}
class _FakeDeviceManager extends DeviceManager {
_FakeDeviceManager() : super(logger: testLogger);
_FakeDeviceManager({
List<FakeDeviceJsonData>? devices,
}) : fakeDevices = devices ?? <FakeDeviceJsonData>[],
super(logger: testLogger);
List<FakeDeviceJsonData> fakeDevices = <FakeDeviceJsonData>[];
@override
Future<List<Device>> getAllDevices({DeviceDiscoveryFilter? filter}) =>
Future<List<Device>>.value(fakeDevices.map((FakeDeviceJsonData d) => d.dev).toList());
Future<List<Device>> getAllDevices({DeviceDiscoveryFilter? filter}) async {
final List<Device> devices = <Device>[];
for (final FakeDeviceJsonData deviceJson in fakeDevices) {
if (filter?.deviceConnectionInterface == null ||
deviceJson.dev.connectionInterface == filter?.deviceConnectionInterface) {
devices.add(deviceJson.dev);
}
}
return devices;
}
@override
Future<List<Device>> refreshAllDevices({

View File

@ -22,7 +22,6 @@ import 'package:flutter_tools/src/dart/pub.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/drive/drive_service.dart';
import 'package:flutter_tools/src/ios/devices.dart';
import 'package:flutter_tools/src/ios/iproxy.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:package_config/package_config.dart';
import 'package:test/fake.dart';
@ -68,7 +67,7 @@ void main() {
final Device screenshotDevice = ThrowingScreenshotDevice()
..supportsScreenshot = false;
fakeDeviceManager.devices = <Device>[screenshotDevice];
fakeDeviceManager.attachedDevices = <Device>[screenshotDevice];
await expectLater(() => createTestCommandRunner(command).run(
<String>[
@ -104,7 +103,7 @@ void main() {
fileSystem.directory('drive_screenshots').createSync();
final Device screenshotDevice = ThrowingScreenshotDevice();
fakeDeviceManager.devices = <Device>[screenshotDevice];
fakeDeviceManager.attachedDevices = <Device>[screenshotDevice];
await expectLater(() => createTestCommandRunner(command).run(
<String>[
@ -142,7 +141,7 @@ void main() {
fileSystem.directory('drive_screenshots').createSync();
final Device screenshotDevice = ScreenshotDevice();
fakeDeviceManager.devices = <Device>[screenshotDevice];
fakeDeviceManager.attachedDevices = <Device>[screenshotDevice];
await expectLater(() => createTestCommandRunner(command).run(
<String>[
@ -184,7 +183,7 @@ void main() {
fileSystem.file('drive_screenshots').createSync();
final Device screenshotDevice = ThrowingScreenshotDevice();
fakeDeviceManager.devices = <Device>[screenshotDevice];
fakeDeviceManager.attachedDevices = <Device>[screenshotDevice];
await expectLater(() => createTestCommandRunner(command).run(
<String>[
@ -222,7 +221,7 @@ void main() {
fileSystem.directory('drive_screenshots').createSync();
final ScreenshotDevice screenshotDevice = ScreenshotDevice();
fakeDeviceManager.devices = <Device>[screenshotDevice];
fakeDeviceManager.attachedDevices = <Device>[screenshotDevice];
expect(screenshotDevice.screenshots, isEmpty);
bool caughtToolExit = false;
@ -293,7 +292,7 @@ void main() {
fileSystem.directory('drive_screenshots').createSync();
final ScreenshotDevice screenshotDevice = ScreenshotDevice();
fakeDeviceManager.devices = <Device>[screenshotDevice];
fakeDeviceManager.attachedDevices = <Device>[screenshotDevice];
expect(screenshotDevice.screenshots, isEmpty);
@ -423,8 +422,8 @@ void main() {
fileSystem.file('pubspec.yaml').createSync();
final Device networkDevice = FakeIosDevice()
..interfaceType = IOSDeviceConnectionInterface.network;
fakeDeviceManager.devices = <Device>[networkDevice];
..connectionInterface = DeviceConnectionInterface.wireless;
fakeDeviceManager.wirelessDevices = <Device>[networkDevice];
await expectLater(() => createTestCommandRunner(command).run(<String>[
'drive',
@ -456,8 +455,8 @@ void main() {
]), throwsToolExit());
final Device usbDevice = FakeIosDevice()
..interfaceType = IOSDeviceConnectionInterface.usb;
fakeDeviceManager.devices = <Device>[usbDevice];
..connectionInterface = DeviceConnectionInterface.attached;
fakeDeviceManager.attachedDevices = <Device>[usbDevice];
final DebuggingOptions options = await command.createDebuggingOptions(false);
expect(options.disablePortPublication, true);
@ -481,8 +480,8 @@ void main() {
fileSystem.file('pubspec.yaml').createSync();
final Device networkDevice = FakeIosDevice()
..interfaceType = IOSDeviceConnectionInterface.network;
fakeDeviceManager.devices = <Device>[networkDevice];
..connectionInterface = DeviceConnectionInterface.wireless;
fakeDeviceManager.wirelessDevices = <Device>[networkDevice];
await expectLater(() => createTestCommandRunner(command).run(<String>[
'drive',
@ -661,7 +660,11 @@ class FakeProcessSignal extends Fake implements io.ProcessSignal {
// ignore: avoid_implementing_value_types
class FakeIosDevice extends Fake implements IOSDevice {
@override
IOSDeviceConnectionInterface interfaceType = IOSDeviceConnectionInterface.usb;
DeviceConnectionInterface connectionInterface = DeviceConnectionInterface.attached;
@override
bool get isWirelesslyConnected =>
connectionInterface == DeviceConnectionInterface.wireless;
@override
Future<TargetPlatform> get targetPlatform async => TargetPlatform.ios;

View File

@ -36,7 +36,7 @@ void main() {
command.applicationPackages = FakeApplicationPackageFactory(FakeAndroidApk());
final FakeAndroidDevice device = FakeAndroidDevice();
testDeviceManager.addDevice(device);
testDeviceManager.addAttachedDevice(device);
await createTestCommandRunner(command).run(<String>['install']);
}, overrides: <Type, Generator>{
@ -50,7 +50,7 @@ void main() {
command.applicationPackages = FakeApplicationPackageFactory(FakeAndroidApk());
final FakeIOSDevice device = FakeIOSDevice();
testDeviceManager.addDevice(device);
testDeviceManager.addAttachedDevice(device);
expect(() async => createTestCommandRunner(command).run(<String>['install', '--device-user', '10']),
throwsToolExit(message: '--device-user is only supported for Android'));
@ -65,7 +65,7 @@ void main() {
command.applicationPackages = FakeApplicationPackageFactory(FakeIOSApp());
final FakeIOSDevice device = FakeIOSDevice();
testDeviceManager.addDevice(device);
testDeviceManager.addAttachedDevice(device);
await createTestCommandRunner(command).run(<String>['install']);
}, overrides: <Type, Generator>{
@ -79,7 +79,7 @@ void main() {
command.applicationPackages = FakeApplicationPackageFactory(FakeAndroidApk());
final FakeAndroidDevice device = FakeAndroidDevice();
testDeviceManager.addDevice(device);
testDeviceManager.addAttachedDevice(device);
expect(() async => createTestCommandRunner(command).run(<String>['install', '--use-application-binary', 'bogus']),
throwsToolExit(message: 'Prebuilt binary bogus does not exist'));
@ -94,7 +94,7 @@ void main() {
command.applicationPackages = FakeApplicationPackageFactory(FakeAndroidApk());
final FakeAndroidDevice device = FakeAndroidDevice();
testDeviceManager.addDevice(device);
testDeviceManager.addAttachedDevice(device);
fileSystem.file('binary').createSync(recursive: true);
await createTestCommandRunner(command).run(<String>['install', '--use-application-binary', 'binary']);
@ -111,7 +111,7 @@ void main() {
command.applicationPackages = fakeAppFactory;
final FakeIOSDevice device = FakeIOSDevice();
testDeviceManager.addDevice(device);
testDeviceManager.addAttachedDevice(device);
await createTestCommandRunner(command).run(<String>['install', '--flavor', flavor]);
expect(fakeAppFactory.buildInfo, isNotNull);
@ -183,4 +183,7 @@ class FakeAndroidDevice extends Fake implements AndroidDevice {
@override
String get name => 'Android';
@override
bool get ephemeral => true;
}

View File

@ -26,7 +26,6 @@ import 'package:flutter_tools/src/devfs.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/ios/devices.dart';
import 'package:flutter_tools/src/ios/iproxy.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:flutter_tools/src/resident_runner.dart';
@ -711,7 +710,7 @@ void main() {
testUsingContext('with only iOS usb device', () async {
final List<Device> devices = <Device>[
FakeIOSDevice(interfaceType: IOSDeviceConnectionInterface.usb, sdkNameAndVersion: 'iOS 16.2'),
FakeIOSDevice(sdkNameAndVersion: 'iOS 16.2'),
];
final TestRunCommandForUsageValues command = TestRunCommandForUsageValues(devices: devices);
final CommandRunner<void> runner = createTestCommandRunner(command);
@ -752,7 +751,10 @@ void main() {
testUsingContext('with only iOS network device', () async {
final List<Device> devices = <Device>[
FakeIOSDevice(interfaceType: IOSDeviceConnectionInterface.network, sdkNameAndVersion: 'iOS 16.2'),
FakeIOSDevice(
connectionInterface: DeviceConnectionInterface.wireless,
sdkNameAndVersion: 'iOS 16.2',
),
];
final TestRunCommandForUsageValues command = TestRunCommandForUsageValues(devices: devices);
final CommandRunner<void> runner = createTestCommandRunner(command);
@ -793,8 +795,11 @@ void main() {
testUsingContext('with both iOS usb and network devices', () async {
final List<Device> devices = <Device>[
FakeIOSDevice(interfaceType: IOSDeviceConnectionInterface.network, sdkNameAndVersion: 'iOS 16.2'),
FakeIOSDevice(interfaceType: IOSDeviceConnectionInterface.usb, sdkNameAndVersion: 'iOS 16.2'),
FakeIOSDevice(
connectionInterface: DeviceConnectionInterface.wireless,
sdkNameAndVersion: 'iOS 16.2',
),
FakeIOSDevice(sdkNameAndVersion: 'iOS 16.2'),
];
final TestRunCommandForUsageValues command = TestRunCommandForUsageValues(devices: devices);
final CommandRunner<void> runner = createTestCommandRunner(command);
@ -1126,6 +1131,10 @@ class FakeDevice extends Fake implements Device {
@override
bool get isConnected => true;
@override
DeviceConnectionInterface get connectionInterface =>
DeviceConnectionInterface.attached;
bool supported = true;
@override
@ -1209,7 +1218,7 @@ class FakeDevice extends Fake implements Device {
// ignore: avoid_implementing_value_types
class FakeIOSDevice extends Fake implements IOSDevice {
FakeIOSDevice({
this.interfaceType = IOSDeviceConnectionInterface.none,
this.connectionInterface = DeviceConnectionInterface.attached,
bool isLocalEmulator = false,
String sdkNameAndVersion = '',
}): _isLocalEmulator = isLocalEmulator,
@ -1225,7 +1234,11 @@ class FakeIOSDevice extends Fake implements IOSDevice {
Future<String> get sdkNameAndVersion => Future<String>.value(_sdkNameAndVersion);
@override
final IOSDeviceConnectionInterface interfaceType;
final DeviceConnectionInterface connectionInterface;
@override
bool get isWirelesslyConnected =>
connectionInterface == DeviceConnectionInterface.wireless;
@override
Future<TargetPlatform> get targetPlatform async => TargetPlatform.ios;

View File

@ -922,8 +922,15 @@ class _FakeDeviceManager extends DeviceManager {
@override
Future<List<Device>> getAllDevices({
DeviceDiscoveryFilter? filter,
}) async => _connectedDevices;
}) async => filteredDevices(filter);
@override
List<DeviceDiscovery> get deviceDiscoverers => <DeviceDiscovery>[];
List<Device> filteredDevices(DeviceDiscoveryFilter? filter) {
if (filter?.deviceConnectionInterface == DeviceConnectionInterface.wireless) {
return <Device>[];
}
return _connectedDevices;
}
}

View File

@ -87,6 +87,13 @@ class FakeDeviceManager extends Fake implements DeviceManager {
@override
String? specifiedDeviceId;
@override
Future<List<Device>> getAllDevices({
DeviceDiscoveryFilter? filter,
}) async {
return devices;
}
@override
Future<List<Device>> refreshAllDevices({
Duration? timeout,

View File

@ -122,7 +122,7 @@ void main() {
expect(androidDevices.supportsPlatform, false);
});
testWithoutContext('AndroidDevices can parse output for physical devices', () async {
testWithoutContext('AndroidDevices can parse output for physical attached devices', () async {
final AndroidDevices androidDevices = AndroidDevices(
userMessages: UserMessages(),
androidWorkflow: androidWorkflow,
@ -147,6 +147,35 @@ List of devices attached
expect(devices, hasLength(1));
expect(devices.first.name, 'Nexus 7');
expect(devices.first.category, Category.mobile);
expect(devices.first.connectionInterface, DeviceConnectionInterface.attached);
});
testWithoutContext('AndroidDevices can parse output for physical wireless devices', () async {
final AndroidDevices androidDevices = AndroidDevices(
userMessages: UserMessages(),
androidWorkflow: androidWorkflow,
androidSdk: FakeAndroidSdk(),
logger: BufferLogger.test(),
processManager: FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['adb', 'devices', '-l'],
stdout: '''
List of devices attached
05a02bac._adb-tls-connect._tcp. device product:razor model:Nexus_7 device:flo
''',
),
]),
platform: FakePlatform(),
fileSystem: MemoryFileSystem.test(),
);
final List<Device> devices = await androidDevices.pollingGetDevices();
expect(devices, hasLength(1));
expect(devices.first.name, 'Nexus 7');
expect(devices.first.category, Category.mobile);
expect(devices.first.connectionInterface, DeviceConnectionInterface.wireless);
});
testWithoutContext('AndroidDevices can parse output for emulators and short listings', () async {

View File

@ -11,7 +11,6 @@ import 'package:flutter_tools/src/base/utils.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/convert.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/ios/iproxy.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:test/fake.dart';
@ -832,7 +831,7 @@ void main() {
EnvironmentType.physical,
null,
<String, Object?>{},
interfaceType: IOSDeviceConnectionInterface.network,
interfaceType: DeviceConnectionInterface.wireless,
);
expect(
@ -856,7 +855,7 @@ void main() {
null,
<String, Object?>{},
ipv6: true,
interfaceType: IOSDeviceConnectionInterface.network,
interfaceType: DeviceConnectionInterface.wireless,
);
expect(

View File

@ -74,7 +74,7 @@ void main() {
name: 'iPhone 1',
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
interfaceType: IOSDeviceConnectionInterface.usb,
connectionInterface: DeviceConnectionInterface.attached,
);
expect(device.isSupported(), isTrue);
});
@ -90,7 +90,7 @@ void main() {
iMobileDevice: iMobileDevice,
name: 'iPhone 1',
cpuArchitecture: DarwinArch.armv7,
interfaceType: IOSDeviceConnectionInterface.usb,
connectionInterface: DeviceConnectionInterface.attached,
);
expect(device.isSupported(), isFalse);
});
@ -107,7 +107,7 @@ void main() {
name: 'iPhone 1',
cpuArchitecture: DarwinArch.arm64,
sdkVersion: '1.0.0',
interfaceType: IOSDeviceConnectionInterface.usb,
connectionInterface: DeviceConnectionInterface.attached,
).majorSdkVersion, 1);
expect(IOSDevice(
'device-123',
@ -120,7 +120,7 @@ void main() {
name: 'iPhone 1',
cpuArchitecture: DarwinArch.arm64,
sdkVersion: '13.1.1',
interfaceType: IOSDeviceConnectionInterface.usb,
connectionInterface: DeviceConnectionInterface.attached,
).majorSdkVersion, 13);
expect(IOSDevice(
'device-123',
@ -133,7 +133,7 @@ void main() {
name: 'iPhone 1',
cpuArchitecture: DarwinArch.arm64,
sdkVersion: '10',
interfaceType: IOSDeviceConnectionInterface.usb,
connectionInterface: DeviceConnectionInterface.attached,
).majorSdkVersion, 10);
expect(IOSDevice(
'device-123',
@ -146,7 +146,7 @@ void main() {
name: 'iPhone 1',
cpuArchitecture: DarwinArch.arm64,
sdkVersion: '0',
interfaceType: IOSDeviceConnectionInterface.usb,
connectionInterface: DeviceConnectionInterface.attached,
).majorSdkVersion, 0);
expect(IOSDevice(
'device-123',
@ -159,7 +159,7 @@ void main() {
name: 'iPhone 1',
cpuArchitecture: DarwinArch.arm64,
sdkVersion: 'bogus',
interfaceType: IOSDeviceConnectionInterface.usb,
connectionInterface: DeviceConnectionInterface.attached,
).majorSdkVersion, 0);
});
@ -175,7 +175,7 @@ void main() {
name: 'iPhone 1',
sdkVersion: '13.3 17C54',
cpuArchitecture: DarwinArch.arm64,
interfaceType: IOSDeviceConnectionInterface.usb,
connectionInterface: DeviceConnectionInterface.attached,
);
expect(await device.sdkNameAndVersion,'iOS 13.3 17C54');
@ -193,7 +193,7 @@ void main() {
name: 'iPhone 1',
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
interfaceType: IOSDeviceConnectionInterface.usb,
connectionInterface: DeviceConnectionInterface.attached,
);
expect(device.supportsRuntimeMode(BuildMode.debug), true);
@ -217,7 +217,7 @@ void main() {
name: 'iPhone 1',
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
interfaceType: IOSDeviceConnectionInterface.usb,
connectionInterface: DeviceConnectionInterface.attached,
);
},
throwsAssertionError,
@ -307,7 +307,7 @@ void main() {
name: 'iPhone 1',
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
interfaceType: IOSDeviceConnectionInterface.usb,
connectionInterface: DeviceConnectionInterface.attached,
);
logReader1 = createLogReader(device, appPackage1, process1);
logReader2 = createLogReader(device, appPackage2, process2);
@ -368,7 +368,7 @@ void main() {
logger: logger,
platform: macPlatform,
fileSystem: MemoryFileSystem.test(),
interfaceType: IOSDeviceConnectionInterface.usb,
connectionInterface: DeviceConnectionInterface.attached,
);
device2 = IOSDevice(
@ -382,7 +382,7 @@ void main() {
logger: logger,
platform: macPlatform,
fileSystem: MemoryFileSystem.test(),
interfaceType: IOSDeviceConnectionInterface.usb,
connectionInterface: DeviceConnectionInterface.attached,
);
});

View File

@ -12,8 +12,8 @@ import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/ios/ios_deploy.dart';
import 'package:flutter_tools/src/ios/iproxy.dart';
import '../../src/common.dart';
import '../../src/fake_process_manager.dart';
@ -73,7 +73,7 @@ void main () {
bundlePath: '/',
appDeltaDirectory: appDeltaDirectory,
launchArguments: <String>['--enable-dart-profiling'],
interfaceType: IOSDeviceConnectionInterface.network,
interfaceType: DeviceConnectionInterface.wireless,
uninstallFirst: true,
);

View File

@ -10,6 +10,7 @@ import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/ios/application_package.dart';
import 'package:flutter_tools/src/ios/devices.dart';
import 'package:flutter_tools/src/ios/ios_deploy.dart';
@ -62,7 +63,7 @@ void main() {
final IOSDevice device = setUpIOSDevice(
processManager: processManager,
fileSystem: fileSystem,
interfaceType: IOSDeviceConnectionInterface.usb,
interfaceType: DeviceConnectionInterface.attached,
artifacts: artifacts,
);
final bool wasInstalled = await device.installApp(iosApp);
@ -95,7 +96,7 @@ void main() {
final IOSDevice device = setUpIOSDevice(
processManager: processManager,
fileSystem: fileSystem,
interfaceType: IOSDeviceConnectionInterface.network,
interfaceType: DeviceConnectionInterface.wireless,
artifacts: artifacts,
);
final bool wasInstalled = await device.installApp(iosApp);
@ -319,7 +320,7 @@ IOSDevice setUpIOSDevice({
required ProcessManager processManager,
FileSystem? fileSystem,
Logger? logger,
IOSDeviceConnectionInterface? interfaceType,
DeviceConnectionInterface? interfaceType,
Artifacts? artifacts,
}) {
logger ??= BufferLogger.test();
@ -357,6 +358,6 @@ IOSDevice setUpIOSDevice({
cache: cache,
),
iProxy: IProxy.test(logger: logger, processManager: processManager),
interfaceType: interfaceType ?? IOSDeviceConnectionInterface.usb,
connectionInterface: interfaceType ?? DeviceConnectionInterface.attached,
);
}

View File

@ -9,6 +9,7 @@ import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/ios/devices.dart';
import 'package:flutter_tools/src/ios/ios_deploy.dart';
import 'package:flutter_tools/src/ios/iproxy.dart';
@ -98,6 +99,6 @@ IOSDevice setUpIOSDevice(FileSystem fileSystem) {
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
iProxy: IProxy.test(logger: logger, processManager: processManager),
interfaceType: IOSDeviceConnectionInterface.usb,
connectionInterface: DeviceConnectionInterface.attached,
);
}

View File

@ -337,7 +337,7 @@ IOSDevice setUpIOSDevice({
cache: cache,
),
cpuArchitecture: DarwinArch.arm64,
interfaceType: IOSDeviceConnectionInterface.usb,
connectionInterface: DeviceConnectionInterface.attached,
);
}

View File

@ -250,7 +250,7 @@ void main() {
processManager: processManager,
fileSystem: fileSystem,
logger: logger,
interfaceType: IOSDeviceConnectionInterface.network,
interfaceType: DeviceConnectionInterface.wireless,
);
final IOSApp iosApp = PrebuiltIOSApp(
projectBundleId: 'app',
@ -560,7 +560,7 @@ IOSDevice setUpIOSDevice({
Logger? logger,
ProcessManager? processManager,
IOSDeploy? iosDeploy,
IOSDeviceConnectionInterface interfaceType = IOSDeviceConnectionInterface.usb,
DeviceConnectionInterface interfaceType = DeviceConnectionInterface.attached,
}) {
final Artifacts artifacts = Artifacts.test();
final FakePlatform macPlatform = FakePlatform(
@ -598,7 +598,7 @@ IOSDevice setUpIOSDevice({
cache: cache,
),
cpuArchitecture: DarwinArch.arm64,
interfaceType: interfaceType,
connectionInterface: interfaceType,
);
}

View File

@ -10,8 +10,8 @@ import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/process.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/ios/code_signing.dart';
import 'package:flutter_tools/src/ios/iproxy.dart';
import 'package:flutter_tools/src/ios/mac.dart';
import 'package:flutter_tools/src/ios/xcresult.dart';
import 'package:flutter_tools/src/project.dart';
@ -77,7 +77,7 @@ void main() {
expect(() async => iMobileDevice.takeScreenshot(
outputFile,
'1234',
IOSDeviceConnectionInterface.usb,
DeviceConnectionInterface.attached,
), throwsA(anything));
expect(fakeProcessManager, hasNoRemainingExpectations);
});
@ -100,7 +100,7 @@ void main() {
await iMobileDevice.takeScreenshot(
outputFile,
'1234',
IOSDeviceConnectionInterface.usb,
DeviceConnectionInterface.attached,
);
expect(fakeProcessManager, hasNoRemainingExpectations);
});
@ -123,7 +123,7 @@ void main() {
await iMobileDevice.takeScreenshot(
outputFile,
'1234',
IOSDeviceConnectionInterface.network,
DeviceConnectionInterface.wireless,
);
expect(fakeProcessManager, hasNoRemainingExpectations);
});

View File

@ -11,6 +11,7 @@ import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/version.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/ios/devices.dart';
import 'package:flutter_tools/src/ios/iproxy.dart';
import 'package:flutter_tools/src/ios/xcodeproj.dart';
@ -480,22 +481,31 @@ void main() {
));
final List<IOSDevice> devices = await xcdevice.getAvailableIOSDevices();
expect(devices, hasLength(4));
expect(devices[0].id, '00008027-00192736010F802E');
expect(devices[0].name, 'An iPhone (Space Gray)');
expect(await devices[0].sdkNameAndVersion, 'iOS 13.3 17C54');
expect(devices[0].cpuArchitecture, DarwinArch.arm64);
expect(devices[0].connectionInterface, DeviceConnectionInterface.attached);
expect(devices[1].id, '98206e7a4afd4aedaff06e687594e089dede3c44');
expect(devices[1].name, 'iPad 1');
expect(await devices[1].sdkNameAndVersion, 'iOS 10.1 14C54');
expect(devices[1].cpuArchitecture, DarwinArch.armv7);
expect(devices[1].connectionInterface, DeviceConnectionInterface.attached);
expect(devices[2].id, '234234234234234234345445687594e089dede3c44');
expect(devices[2].name, 'A networked iPad');
expect(await devices[2].sdkNameAndVersion, 'iOS 10.1 14C54');
expect(devices[2].cpuArchitecture, DarwinArch.arm64); // Defaults to arm64 for unknown architecture.
expect(devices[2].connectionInterface, DeviceConnectionInterface.wireless);
expect(devices[3].id, 'f577a7903cc54959be2e34bc4f7f80b7009efcf4');
expect(devices[3].name, 'iPad 2');
expect(await devices[3].sdkNameAndVersion, 'iOS 10.1 14C54');
expect(devices[3].cpuArchitecture, DarwinArch.arm64); // Defaults to arm64 for unknown architecture.
expect(devices[3].connectionInterface, DeviceConnectionInterface.attached);
expect(fakeProcessManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{
Platform: () => macPlatform,

View File

@ -705,15 +705,15 @@ void main() {
});
testUsingContext('finds single device', () async {
testDeviceManager.addDevice(device1);
testDeviceManager.addAttachedDevice(device1);
final DummyFlutterCommand flutterCommand = DummyFlutterCommand();
final Device? device = await flutterCommand.findTargetDevice();
expect(device, device1);
});
testUsingContext('finds multiple devices', () async {
testDeviceManager.addDevice(device1);
testDeviceManager.addDevice(device2);
testDeviceManager.addAttachedDevice(device1);
testDeviceManager.addAttachedDevice(device2);
testDeviceManager.specifiedDeviceId = 'all';
final DummyFlutterCommand flutterCommand = DummyFlutterCommand();
final Device? device = await flutterCommand.findTargetDevice();

View File

@ -181,7 +181,8 @@ void _printBufferedErrors(AppContext testContext) {
}
class FakeDeviceManager implements DeviceManager {
List<Device> devices = <Device>[];
List<Device> attachedDevices = <Device>[];
List<Device> wirelessDevices = <Device>[];
String? _specifiedDeviceId;
@ -209,20 +210,20 @@ class FakeDeviceManager implements DeviceManager {
@override
Future<List<Device>> getAllDevices({
DeviceDiscoveryFilter? filter,
}) async => devices;
}) async => filteredDevices(filter);
@override
Future<List<Device>> refreshAllDevices({
Duration? timeout,
DeviceDiscoveryFilter? filter,
}) async => devices;
}) async => filteredDevices(filter);
@override
Future<List<Device>> getDevicesById(
String deviceId, {
DeviceDiscoveryFilter? filter,
}) async {
return devices.where((Device device) {
return filteredDevices(filter).where((Device device) {
return device.id == deviceId || device.id.startsWith(deviceId);
}).toList();
}
@ -236,7 +237,8 @@ class FakeDeviceManager implements DeviceManager {
: getAllDevices(filter: filter);
}
void addDevice(Device device) => devices.add(device);
void addAttachedDevice(Device device) => attachedDevices.add(device);
void addWirelessDevice(Device device) => wirelessDevices.add(device);
@override
bool get canListAnything => true;
@ -257,6 +259,16 @@ class FakeDeviceManager implements DeviceManager {
@override
Device? getSingleEphemeralDevice(List<Device> devices) => null;
List<Device> filteredDevices(DeviceDiscoveryFilter? filter) {
if (filter?.deviceConnectionInterface == DeviceConnectionInterface.attached) {
return attachedDevices;
}
if (filter?.deviceConnectionInterface == DeviceConnectionInterface.wireless) {
return wirelessDevices;
}
return attachedDevices + wirelessDevices;
}
}
class TestDeviceDiscoverySupportFilter extends Fake implements DeviceDiscoverySupportFilter {

View File

@ -54,6 +54,31 @@ List<FakeDeviceJsonData> fakeDevices = <FakeDeviceJsonData>[
},
},
),
FakeDeviceJsonData(
FakeDevice(
'wireless android',
'wireless-android',
type: PlatformType.android,
connectionInterface: DeviceConnectionInterface.wireless,
),
<String, Object>{
'name': 'wireless android',
'id': 'wireless-android',
'isSupported': true,
'targetPlatform': 'android-arm',
'emulator': true,
'sdk': 'Test SDK (1.2.3)',
'capabilities': <String, Object>{
'hotReload': true,
'hotRestart': true,
'screenshot': false,
'fastStart': false,
'flutterExit': true,
'hardwareRendering': true,
'startPaused': true,
},
}
),
];
/// Fake device to test `devices` command.