Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions packages/flutter_tools/lib/src/android/android_device.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import '../build_info.dart';
import '../convert.dart';
import '../device.dart';
import '../device_port_forwarder.dart';
import '../device_vm_service_discovery_for_attach.dart';
import '../project.dart';
import '../protocol_discovery.dart';
import 'android.dart';
Expand Down Expand Up @@ -800,6 +801,26 @@ class AndroidDevice extends Device {
}
}

@override
VMServiceDiscoveryForAttach getVMServiceDiscoveryForAttach({
String? appId,
String? fuchsiaModule,
int? filterDevicePort,
int? expectedHostPort,
required bool ipv6,
required Logger logger,
}) =>
LogScanningVMServiceDiscoveryForAttach(
// If it's an Android device, attaching relies on past log searching
// to find the service protocol.
Future<DeviceLogReader>.value(getLogReader(includePastLogs: true)),
portForwarder: portForwarder,
ipv6: ipv6,
devicePort: filterDevicePort,
hostPort: expectedHostPort,
logger: logger,
);

@override
late final DevicePortForwarder? portForwarder = () {
final String? adbPath = _androidSdk.adbPath;
Expand Down
136 changes: 33 additions & 103 deletions packages/flutter_tools/lib/src/commands/attach.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,12 @@ import '../compile.dart';
import '../daemon.dart';
import '../device.dart';
import '../device_port_forwarder.dart';
import '../fuchsia/fuchsia_device.dart';
import '../device_vm_service_discovery_for_attach.dart';
import '../ios/devices.dart';
import '../ios/simulators.dart';
import '../macos/macos_ipad_device.dart';
import '../mdns_discovery.dart';
import '../project.dart';
import '../protocol_discovery.dart';
import '../resident_runner.dart';
import '../run_cold.dart';
import '../run_hot.dart';
Expand Down Expand Up @@ -286,116 +285,48 @@ known, it can be explicitly provided to attach via the command-line, e.g.
: null;

Stream<Uri>? vmServiceUri;
bool usesIpv6 = ipv6!;
final bool usesIpv6 = ipv6!;
final String ipv6Loopback = InternetAddress.loopbackIPv6.address;
final String ipv4Loopback = InternetAddress.loopbackIPv4.address;
final String hostname = usesIpv6 ? ipv6Loopback : ipv4Loopback;
final bool isWirelessIOSDevice = (device is IOSDevice) && device.isWirelesslyConnected;

if ((debugPort == null && debugUri == null) || isWirelessIOSDevice) {
if (device is FuchsiaDevice) {
final String? module = stringArg('module');
if (module == null) {
throwToolExit("'--module' is required for attaching to a Fuchsia device");
}
usesIpv6 = device.ipv6;
FuchsiaIsolateDiscoveryProtocol? isolateDiscoveryProtocol;
try {
isolateDiscoveryProtocol = device.getIsolateDiscoveryProtocol(module);
vmServiceUri = Stream<Uri>.value(await isolateDiscoveryProtocol.uri).asBroadcastStream();
} on Exception {
isolateDiscoveryProtocol?.dispose();
final List<ForwardedPort> ports = device.portForwarder.forwardedPorts.toList();
for (final ForwardedPort port in ports) {
await device.portForwarder.unforward(port);
// The device port we expect to have the debug port be listening
final int? devicePort = debugPort ?? debugUri?.port ?? deviceVmservicePort;

final VMServiceDiscoveryForAttach vmServiceDiscovery = device.getVMServiceDiscoveryForAttach(
appId: appId,
fuchsiaModule: stringArg('module'),
filterDevicePort: devicePort,
expectedHostPort: hostVmservicePort,
ipv6: usesIpv6,
logger: _logger,
);

_logger.printStatus('Waiting for a connection from Flutter on ${device.name}...');
final Status discoveryStatus = _logger.startSpinner(
timeout: const Duration(seconds: 30),
slowWarningCallback: () {
// On iOS we rely on mDNS to find Dart VM Service. Remind the user to allow local network permissions on the device.
if (_isIOSDevice(device)) {
return 'The Dart VM Service was not discovered after 30 seconds. This is taking much longer than expected...\n\n'
'Click "Allow" to the prompt on your device asking if you would like to find and connect devices on your local network. '
'If you selected "Don\'t Allow", you can turn it on in Settings > Your App Name > Local Network. '
"If you don't see your app in the Settings, uninstall the app and rerun to see the prompt again.\n";
}
rethrow;
}
} else if (_isIOSDevice(device)) {
// Protocol Discovery relies on logging. On iOS earlier than 13, logging is gathered using syslog.
// syslog is not available for iOS 13+. For iOS 13+, Protocol Discovery gathers logs from the VMService.
// Since we don't have access to the VMService yet, Protocol Discovery cannot be used for iOS 13+.
// Also, wireless devices must be found using mDNS and cannot use Protocol Discovery.
final bool compatibleWithProtocolDiscovery = (device is IOSDevice) &&
device.majorSdkVersion < IOSDeviceLogReader.minimumUniversalLoggingSdkVersion &&
!isWirelessIOSDevice;

_logger.printStatus('Waiting for a connection from Flutter on ${device.name}...');
final Status discoveryStatus = _logger.startSpinner(
timeout: const Duration(seconds: 30),
slowWarningCallback: () {
// If relying on mDNS to find Dart VM Service, remind the user to allow local network permissions.
if (!compatibleWithProtocolDiscovery) {
return 'The Dart VM Service was not discovered after 30 seconds. This is taking much longer than expected...\n\n'
'Click "Allow" to the prompt asking if you would like to find and connect devices on your local network. '
'If you selected "Don\'t Allow", you can turn it on in Settings > Your App Name > Local Network. '
"If you don't see your app in the Settings, uninstall the app and rerun to see the prompt again.\n";
}

return 'The Dart VM Service was not discovered after 30 seconds. This is taking much longer than expected...\n';
},
);

int? devicePort;
if (debugPort != null) {
devicePort = debugPort;
} else if (debugUri != null) {
devicePort = debugUri?.port;
} else if (deviceVmservicePort != null) {
devicePort = deviceVmservicePort;
}
return 'The Dart VM Service was not discovered after 30 seconds. This is taking much longer than expected...\n';
},
);

final Future<Uri?> mDNSDiscoveryFuture = MDnsVmServiceDiscovery.instance!.getVMServiceUriForAttach(
appId,
device,
usesIpv6: usesIpv6,
useDeviceIPAsHost: isWirelessIOSDevice,
deviceVmservicePort: devicePort,
);
vmServiceUri = vmServiceDiscovery.uris;

Future<Uri?>? protocolDiscoveryFuture;
if (compatibleWithProtocolDiscovery) {
final ProtocolDiscovery vmServiceDiscovery = ProtocolDiscovery.vmService(
device.getLogReader(),
portForwarder: device.portForwarder,
ipv6: ipv6!,
devicePort: devicePort,
hostPort: hostVmservicePort,
logger: _logger,
);
protocolDiscoveryFuture = vmServiceDiscovery.uri;
}

final Uri? foundUrl;
if (protocolDiscoveryFuture == null) {
foundUrl = await mDNSDiscoveryFuture;
} else {
foundUrl = await Future.any(
<Future<Uri?>>[mDNSDiscoveryFuture, protocolDiscoveryFuture]
);
}
// Stop the timer once we receive the first uri.
vmServiceUri = vmServiceUri.map((Uri uri) {
discoveryStatus.stop();

vmServiceUri = foundUrl == null
? null
: Stream<Uri>.value(foundUrl).asBroadcastStream();
}
// If MDNS discovery fails or we're not on iOS, fallback to ProtocolDiscovery.
if (vmServiceUri == null) {
final ProtocolDiscovery vmServiceDiscovery =
ProtocolDiscovery.vmService(
// If it's an Android device, attaching relies on past log searching
// to find the service protocol.
await device.getLogReader(includePastLogs: device is AndroidDevice),
portForwarder: device.portForwarder,
ipv6: ipv6!,
devicePort: deviceVmservicePort,
hostPort: hostVmservicePort,
logger: _logger,
);
_logger.printStatus('Waiting for a connection from Flutter on ${device.name}...');
vmServiceUri = vmServiceDiscovery.uris;
}
return uri;
});
} else {
vmServiceUri = Stream<Uri>
.fromFuture(
Expand Down Expand Up @@ -559,8 +490,7 @@ known, it can be explicitly provided to attach via the command-line, e.g.
Future<void> _validateArguments() async { }

bool _isIOSDevice(Device device) {
return (device is IOSDevice) ||
(device is IOSSimulator) ||
return (device.platformType == PlatformType.ios) ||
(device is MacOSDesignedForIPadDevice);
}
}
Expand Down
38 changes: 38 additions & 0 deletions packages/flutter_tools/lib/src/commands/daemon.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import '../convert.dart';
import '../daemon.dart';
import '../device.dart';
import '../device_port_forwarder.dart';
import '../device_vm_service_discovery_for_attach.dart';
import '../emulator.dart';
import '../features.dart';
import '../globals.dart' as globals;
Expand Down Expand Up @@ -1009,6 +1010,8 @@ class DeviceDomain extends Domain {
registerHandler('shutdownDartDevelopmentService', shutdownDartDevelopmentService);
registerHandler('setExternalDevToolsUriForDartDevelopmentService', setExternalDevToolsUriForDartDevelopmentService);
registerHandler('getDiagnostics', getDiagnostics);
registerHandler('startVMServiceDiscoveryForAttach', startVMServiceDiscoveryForAttach);
registerHandler('stopVMServiceDiscoveryForAttach', stopVMServiceDiscoveryForAttach);

// Use the device manager discovery so that client provided device types
// are usable via the daemon protocol.
Expand Down Expand Up @@ -1325,6 +1328,41 @@ class DeviceDomain extends Domain {
...diagnostics,
];
}

final Map<String, StreamSubscription<Uri>> _vmServiceDiscoverySubscriptions = <String, StreamSubscription<Uri>>{};

Future<String> startVMServiceDiscoveryForAttach(Map<String, Object?> args) async {
final String? deviceId = _getStringArg(args, 'deviceId', required: true);
final String? appId = _getStringArg(args, 'appId');
final String? fuchsiaModule = _getStringArg(args, 'fuchsiaModule');
final int? filterDevicePort = _getIntArg(args, 'filterDevicePort');
final bool? ipv6 = _getBoolArg(args, 'ipv6');

final Device? device = await daemon.deviceDomain._getDevice(deviceId);
if (device == null) {
throw DaemonException("device '$deviceId' not found");
}

final String id = '${_id++}';

final VMServiceDiscoveryForAttach discovery = device.getVMServiceDiscoveryForAttach(
appId: appId,
fuchsiaModule: fuchsiaModule,
filterDevicePort: filterDevicePort,
ipv6: ipv6 ?? false,
logger: globals.logger
);
_vmServiceDiscoverySubscriptions[id] = discovery.uris.listen(
(Uri uri) => sendEvent('device.VMServiceDiscoveryForAttach.$id', uri.toString()),
);

return id;
}

Future<void> stopVMServiceDiscoveryForAttach(Map<String, Object?> args) async {
final String? id = _getStringArg(args, 'id', required: true);
await _vmServiceDiscoverySubscriptions.remove(id)?.cancel();
}
}

class DevToolsDomain extends Domain {
Expand Down
30 changes: 30 additions & 0 deletions packages/flutter_tools/lib/src/device.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import 'base/utils.dart';
import 'build_info.dart';
import 'devfs.dart';
import 'device_port_forwarder.dart';
import 'device_vm_service_discovery_for_attach.dart';
import 'project.dart';
import 'vmservice.dart';
import 'web/compile.dart';
Expand Down Expand Up @@ -737,6 +738,35 @@ abstract class Device {
/// Clear the device's logs.
void clearLogs();

/// Get the [VMServiceDiscoveryForAttach] instance for this device, which
/// discovers, and forwards any necessary ports to the vm service uri of a
/// running app on the device.
///
/// If `appId` is specified, on supported platforms, the service discovery
/// will only return the VM service URI from the given app.
///
/// If `fuchsiaModule` is specified, this will only return the VM service uri
/// from the specified Fuchsia module.
///
/// If `filterDevicePort` is specified, this will only return the VM service
/// uri that matches the given port on the device.
VMServiceDiscoveryForAttach getVMServiceDiscoveryForAttach({
String? appId,
String? fuchsiaModule,
int? filterDevicePort,
int? expectedHostPort,
required bool ipv6,
required Logger logger,
}) =>
LogScanningVMServiceDiscoveryForAttach(
Future<DeviceLogReader>.value(getLogReader()),
portForwarder: portForwarder,
devicePort: filterDevicePort,
hostPort: expectedHostPort,
ipv6: ipv6,
logger: logger,
);

/// Start an app package on the current device.
///
/// [platformArgs] allows callers to pass platform-specific arguments to the
Expand Down
Loading