Update proxied devices to handle connection interface and diagnostics. (#145061)

Also improve the performance of daemon device discovery by parallelizing the calls.
This commit is contained in:
Lau Ching Jun 2024-03-13 15:24:25 -07:00 committed by GitHub
parent 19087442ce
commit 3dba3f2a0f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 159 additions and 4 deletions

View file

@ -1008,6 +1008,7 @@ class DeviceDomain extends Domain {
registerHandler('startDartDevelopmentService', startDartDevelopmentService); registerHandler('startDartDevelopmentService', startDartDevelopmentService);
registerHandler('shutdownDartDevelopmentService', shutdownDartDevelopmentService); registerHandler('shutdownDartDevelopmentService', shutdownDartDevelopmentService);
registerHandler('setExternalDevToolsUriForDartDevelopmentService', setExternalDevToolsUriForDartDevelopmentService); registerHandler('setExternalDevToolsUriForDartDevelopmentService', setExternalDevToolsUriForDartDevelopmentService);
registerHandler('getDiagnostics', getDiagnostics);
// Use the device manager discovery so that client provided device types // Use the device manager discovery so that client provided device types
// are usable via the daemon protocol. // are usable via the daemon protocol.
@ -1059,12 +1060,23 @@ class DeviceDomain extends Domain {
} }
/// Return a list of the current devices, discarding existing cache of devices. /// Return a list of the current devices, discarding existing cache of devices.
Future<List<Map<String, Object?>>> discoverDevices([ Map<String, Object?>? args ]) async { Future<List<Map<String, Object?>>> discoverDevices(Map<String, Object?> args) async {
return <Map<String, Object?>>[ final int? timeoutInMilliseconds = _getIntArg(args, 'timeoutInMilliseconds');
final Duration? timeout = timeoutInMilliseconds != null ? Duration(milliseconds: timeoutInMilliseconds) : null;
// Calling `discoverDevices()` and `_deviceToMap()` in parallel for better performance.
final List<List<Device>> devicesListList = await Future.wait(<Future<List<Device>>>[
for (final PollingDeviceDiscovery discoverer in _discoverers) for (final PollingDeviceDiscovery discoverer in _discoverers)
for (final Device device in await discoverer.discoverDevices()) discoverer.discoverDevices(timeout: timeout),
await _deviceToMap(device), ]);
final List<Device> devices = <Device>[
for (final List<Device> devicesList in devicesListList)
...devicesList,
]; ];
return Future.wait(<Future<Map<String, Object?>>>[
for (final Device device in devices) _deviceToMap(device),
]);
} }
/// Enable device events. /// Enable device events.
@ -1298,6 +1310,21 @@ class DeviceDomain extends Domain {
} }
return null; return null;
} }
/// Gets a list of diagnostic messages pertaining to issues with any connected
/// devices.
Future<List<String>> getDiagnostics(Map<String, Object?> args) async {
// Call `getDiagnostics()` in parallel to improve performance.
final List<List<String>> diagnosticsLists = await Future.wait(<Future<List<String>>>[
for (final PollingDeviceDiscovery discoverer in _discoverers)
discoverer.getDiagnostics(),
]);
return <String>[
for (final List<String> diagnostics in diagnosticsLists)
...diagnostics,
];
}
} }
class DevToolsDomain extends Domain { class DevToolsDomain extends Domain {
@ -1333,6 +1360,8 @@ Future<Map<String, Object?>> _deviceToMap(Device device) async {
'ephemeral': device.ephemeral, 'ephemeral': device.ephemeral,
'emulatorId': await device.emulatorId, 'emulatorId': await device.emulatorId,
'sdk': await device.sdkNameAndVersion, 'sdk': await device.sdkNameAndVersion,
'isConnected': device.isConnected,
'connectionInterface': getNameForDeviceConnectionInterface(device.connectionInterface),
'capabilities': <String, Object>{ 'capabilities': <String, Object>{
'hotReload': device.supportsHotReload, 'hotReload': device.supportsHotReload,
'hotRestart': device.supportsHotRestart, 'hotRestart': device.supportsHotRestart,

View file

@ -586,6 +586,27 @@ enum DeviceConnectionInterface {
wireless, wireless,
} }
/// Returns the `DeviceConnectionInterface` enum based on its string name.
DeviceConnectionInterface getDeviceConnectionInterfaceForName(String name) {
switch (name) {
case 'attached':
return DeviceConnectionInterface.attached;
case 'wireless':
return DeviceConnectionInterface.wireless;
}
throw Exception('Unsupported DeviceConnectionInterface name "$name"');
}
/// Returns a `DeviceConnectionInterface`'s string name.
String getNameForDeviceConnectionInterface(DeviceConnectionInterface connectionInterface) {
switch (connectionInterface) {
case DeviceConnectionInterface.attached:
return 'attached';
case DeviceConnectionInterface.wireless:
return 'wireless';
}
}
/// A device is a physical hardware that can run a Flutter application. /// A device is a physical hardware that can run a Flutter application.
/// ///
/// This may correspond to a connected iOS or Android device, or represent /// This may correspond to a connected iOS or Android device, or represent

View file

@ -102,6 +102,8 @@ class ProxiedDevices extends PollingDeviceDiscovery {
@visibleForTesting @visibleForTesting
ProxiedDevice deviceFromDaemonResult(Map<String, Object?> device) { ProxiedDevice deviceFromDaemonResult(Map<String, Object?> device) {
final Map<String, Object?> capabilities = _cast<Map<String, Object?>>(device['capabilities']); final Map<String, Object?> capabilities = _cast<Map<String, Object?>>(device['capabilities']);
final String? connectionInterfaceName = _cast<String?>(device['connectionInterface']);
final DeviceConnectionInterface? connectionInterface = connectionInterfaceName != null ? getDeviceConnectionInterfaceForName(connectionInterfaceName) : null;
return ProxiedDevice( return ProxiedDevice(
connection, _cast<String>(device['id']), connection, _cast<String>(device['id']),
deltaFileTransfer: _deltaFileTransfer, deltaFileTransfer: _deltaFileTransfer,
@ -110,6 +112,8 @@ class ProxiedDevices extends PollingDeviceDiscovery {
platformType: PlatformType.fromString(_cast<String>(device['platformType'])), platformType: PlatformType.fromString(_cast<String>(device['platformType'])),
targetPlatform: getTargetPlatformForName(_cast<String>(device['platform'])), targetPlatform: getTargetPlatformForName(_cast<String>(device['platform'])),
ephemeral: _cast<bool>(device['ephemeral']), ephemeral: _cast<bool>(device['ephemeral']),
isConnected: _cast<bool?>(device['isConnected']) ?? true,
connectionInterface: connectionInterface ?? DeviceConnectionInterface.attached,
name: 'Proxied ${device['name']}', name: 'Proxied ${device['name']}',
isLocalEmulator: _cast<bool>(device['emulator']), isLocalEmulator: _cast<bool>(device['emulator']),
emulatorId: _cast<String?>(device['emulatorId']), emulatorId: _cast<String?>(device['emulatorId']),
@ -124,6 +128,21 @@ class ProxiedDevices extends PollingDeviceDiscovery {
fileTransfer: _fileTransfer, fileTransfer: _fileTransfer,
); );
} }
@override
Future<List<String>> getDiagnostics() async {
try {
final List<String> diagnostics = _cast<List<dynamic>>(await connection.sendRequest('device.getDiagnostics')).cast<String>();
return diagnostics;
} on String catch (e) { // Daemon actually does throw string types.
if (e.contains('command not understood')) {
_logger.printTrace('The daemon is on an older version that does not support `device.getDiagnostics`.');
// Silently ignore.
return <String>[];
}
rethrow;
}
}
} }
/// A [Device] that acts as a proxy to remotely connected device. /// A [Device] that acts as a proxy to remotely connected device.
@ -143,6 +162,8 @@ class ProxiedDevice extends Device {
required PlatformType? platformType, required PlatformType? platformType,
required TargetPlatform targetPlatform, required TargetPlatform targetPlatform,
required bool ephemeral, required bool ephemeral,
required this.isConnected,
required this.connectionInterface,
required this.name, required this.name,
required bool isLocalEmulator, required bool isLocalEmulator,
required String? emulatorId, required String? emulatorId,
@ -180,6 +201,12 @@ class ProxiedDevice extends Device {
final FileTransfer _fileTransfer; final FileTransfer _fileTransfer;
@override
final bool isConnected;
@override
final DeviceConnectionInterface connectionInterface;
@override @override
final String name; final String name;

View file

@ -459,6 +459,8 @@ void main() {
'ephemeral': false, 'ephemeral': false,
'emulatorId': 'device', 'emulatorId': 'device',
'sdk': 'Android 12', 'sdk': 'Android 12',
'isConnected': true,
'connectionInterface': 'attached',
'capabilities': <String, Object?>{ 'capabilities': <String, Object?>{
'hotReload': true, 'hotReload': true,
'hotRestart': true, 'hotRestart': true,
@ -479,6 +481,8 @@ void main() {
'ephemeral': false, 'ephemeral': false,
'emulatorId': null, 'emulatorId': null,
'sdk': 'preview', 'sdk': 'preview',
'isConnected': true,
'connectionInterface': 'attached',
'capabilities': <String, Object?>{ 'capabilities': <String, Object?>{
'hotReload': true, 'hotReload': true,
'hotRestart': true, 'hotRestart': true,
@ -725,6 +729,31 @@ void main() {
expect(device.dds.shutdownCalled, true); expect(device.dds.shutdownCalled, true);
}); });
testUsingContext('device.getDiagnostics returns correct value', () async {
daemon = Daemon(
daemonConnection,
notifyingLogger: notifyingLogger,
);
final FakePollingDeviceDiscovery discoverer1 = FakePollingDeviceDiscovery();
discoverer1.diagnostics = <String>['fake diagnostic 1', 'fake diagnostic 2'];
final FakePollingDeviceDiscovery discoverer2 = FakePollingDeviceDiscovery();
discoverer2.diagnostics = <String>['fake diagnostic 3', 'fake diagnostic 4'];
daemon.deviceDomain.addDeviceDiscoverer(discoverer1);
daemon.deviceDomain.addDeviceDiscoverer(discoverer2);
daemonStreams.inputs.add(DaemonMessage(<String, Object?>{
'id': 0,
'method': 'device.getDiagnostics',
}));
final DaemonMessage response = await daemonStreams.outputs.stream.firstWhere(_notEvent);
expect(response.data['id'], 0);
expect(response.data['result'], <String>[
'fake diagnostic 1',
'fake diagnostic 2',
'fake diagnostic 3',
'fake diagnostic 4',
]);
});
testUsingContext('emulator.launch without an emulatorId should report an error', () async { testUsingContext('emulator.launch without an emulatorId should report an error', () async {
daemon = Daemon( daemon = Daemon(
daemonConnection, daemonConnection,
@ -1128,6 +1157,9 @@ class FakeAndroidDevice extends Fake implements AndroidDevice {
@override @override
final bool isConnected = true; final bool isConnected = true;
@override
final DeviceConnectionInterface connectionInterface = DeviceConnectionInterface.attached;
@override @override
Future<String> get sdkNameAndVersion async => 'Android 12'; Future<String> get sdkNameAndVersion async => 'Android 12';

View file

@ -274,6 +274,9 @@ class FakeAndroidDevice extends Fake implements AndroidDevice {
@override @override
bool get isConnected => true; bool get isConnected => true;
@override
final DeviceConnectionInterface connectionInterface = DeviceConnectionInterface.attached;
@override @override
Future<String> get sdkNameAndVersion async => 'Android 12'; Future<String> get sdkNameAndVersion async => 'Android 12';

View file

@ -562,6 +562,44 @@ void main() {
expect(devicesAdded[0].id, fakeDevice['id']); expect(devicesAdded[0].id, fakeDevice['id']);
expect(devicesAdded[1].id, fakeDevice2['id']); expect(devicesAdded[1].id, fakeDevice2['id']);
}); });
testWithoutContext('handles getDiagnostics', () async {
bufferLogger = BufferLogger.test();
final ProxiedDevices proxiedDevices = ProxiedDevices(
clientDaemonConnection,
logger: bufferLogger,
);
final Future<List<String>> resultFuture = proxiedDevices.getDiagnostics();
final DaemonMessage message = await serverDaemonConnection.incomingCommands.first;
expect(message.data['id'], isNotNull);
expect(message.data['method'], 'device.getDiagnostics');
serverDaemonConnection.sendResponse(message.data['id']!, <String>['1', '2']);
final List<String> result = await resultFuture;
expect(result, <String>['1', '2']);
});
testWithoutContext('returns empty result when daemon does not understand getDiagnostics', () async {
bufferLogger = BufferLogger.test();
final ProxiedDevices proxiedDevices = ProxiedDevices(
clientDaemonConnection,
logger: bufferLogger,
);
final Future<List<String>> resultFuture = proxiedDevices.getDiagnostics();
final DaemonMessage message = await serverDaemonConnection.incomingCommands.first;
expect(message.data['id'], isNotNull);
expect(message.data['method'], 'device.getDiagnostics');
serverDaemonConnection.sendErrorResponse(message.data['id']!, 'command not understood: device.getDiagnostics', StackTrace.current);
final List<String> result = await resultFuture;
expect(result, isEmpty);
});
}); });
group('ProxiedDartDevelopmentService', () { group('ProxiedDartDevelopmentService', () {

View file

@ -269,6 +269,11 @@ class FakePollingDeviceDiscovery extends PollingDeviceDiscovery {
@override @override
List<String> wellKnownIds = <String>[]; List<String> wellKnownIds = <String>[];
List<String> diagnostics = <String>[];
@override
Future<List<String>> getDiagnostics() => Future<List<String>>.value(diagnostics);
} }
/// A fake implementation of the [DeviceLogReader]. /// A fake implementation of the [DeviceLogReader].