Skip to content

Commit

Permalink
Better support for wireless devices in IDEs (#123716)
Browse files Browse the repository at this point in the history
Better support for wireless devices in IDEs
  • Loading branch information
vashworth authored Apr 4, 2023
1 parent e3bc8ef commit 34d2c8d
Show file tree
Hide file tree
Showing 4 changed files with 394 additions and 176 deletions.
120 changes: 99 additions & 21 deletions packages/flutter_tools/lib/src/ios/devices.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,31 @@ class IOSDevices extends PollingDeviceDiscovery {
@override
bool get requiresExtendedWirelessDeviceDiscovery => true;

StreamSubscription<Map<XCDeviceEvent, String>>? _observedDeviceEventsSubscription;
StreamSubscription<XCDeviceEventNotification>? _observedDeviceEventsSubscription;

/// Cache for all devices found by `xcdevice list`, including not connected
/// devices. Used to minimize the need to call `xcdevice list`.
///
/// Separate from `deviceNotifier` since `deviceNotifier` should only contain
/// connected devices.
final Map<String, IOSDevice> _cachedPolledDevices = <String, IOSDevice>{};

/// Maps device id to a map of the device's observed connections. When the
/// mapped connection is `true`, that means that observed events indicated
/// the device is connected via that particular interface.
///
/// The device id must be missing from the map or both interfaces must be
/// false for the device to be considered disconnected.
///
/// Example:
/// {
/// device-id: {
/// usb: false,
/// wifi: false,
/// },
/// }
final Map<String, Map<XCDeviceEventInterface, bool>> _observedConnectionsByDeviceId =
<String, Map<XCDeviceEventInterface, bool>>{};

@override
Future<void> startPolling() async {
Expand All @@ -75,16 +99,13 @@ class IOSDevices extends PollingDeviceDiscovery {
deviceNotifier ??= ItemListNotifier<Device>();

// Start by populating all currently attached devices.
final List<Device> devices = await pollingGetDevices();

// Only show connected devices.
final List<Device> filteredDevices = devices.where((Device device) => device.isConnected == true).toList();
deviceNotifier!.updateWithNewList(filteredDevices);
_updateCachedDevices(await pollingGetDevices());
_updateNotifierFromCache();

// cancel any outstanding subscriptions.
await _observedDeviceEventsSubscription?.cancel();
_observedDeviceEventsSubscription = xcdevice.observedDeviceEvents()?.listen(
_onDeviceEvent,
onDeviceEvent,
onError: (Object error, StackTrace stack) {
_logger.printTrace('Process exception running xcdevice observe:\n$error\n$stack');
}, onDone: () {
Expand All @@ -98,32 +119,89 @@ class IOSDevices extends PollingDeviceDiscovery {
);
}

Future<void> _onDeviceEvent(Map<XCDeviceEvent, String> event) async {
final XCDeviceEvent eventType = event.containsKey(XCDeviceEvent.attach) ? XCDeviceEvent.attach : XCDeviceEvent.detach;
final String? deviceIdentifier = event[eventType];
@visibleForTesting
Future<void> onDeviceEvent(XCDeviceEventNotification event) async {
final ItemListNotifier<Device>? notifier = deviceNotifier;
if (notifier == null) {
return;
}
Device? knownDevice;
for (final Device device in notifier.items) {
if (device.id == deviceIdentifier) {
if (device.id == event.deviceIdentifier) {
knownDevice = device;
}
}

// Ignore already discovered devices (maybe populated at the beginning).
if (eventType == XCDeviceEvent.attach && knownDevice == null) {
// There's no way to get details for an individual attached device,
// so repopulate them all.
final List<Device> devices = await pollingGetDevices();
final Map<XCDeviceEventInterface, bool> deviceObservedConnections =
_observedConnectionsByDeviceId[event.deviceIdentifier] ??
<XCDeviceEventInterface, bool>{
XCDeviceEventInterface.usb: false,
XCDeviceEventInterface.wifi: false,
};

if (event.eventType == XCDeviceEvent.attach) {
// Update device's observed connections.
deviceObservedConnections[event.eventInterface] = true;
_observedConnectionsByDeviceId[event.deviceIdentifier] = deviceObservedConnections;

// If device was not already in notifier, add it.
if (knownDevice == null) {
if (_cachedPolledDevices[event.deviceIdentifier] == null) {
// If device is not found in cache, there's no way to get details
// for an individual attached device, so repopulate them all.
_updateCachedDevices(await pollingGetDevices());
}
_updateNotifierFromCache();
}
} else {
// Update device's observed connections.
deviceObservedConnections[event.eventInterface] = false;
_observedConnectionsByDeviceId[event.deviceIdentifier] = deviceObservedConnections;

// If device is in the notifier and does not have other observed
// connections, remove it.
if (knownDevice != null &&
!_deviceHasObservedConnection(deviceObservedConnections)) {
notifier.removeItem(knownDevice);
}
}
}

/// Adds or updates devices in cache. Does not remove devices from cache.
void _updateCachedDevices(List<Device> devices) {
for (final Device device in devices) {
if (device is! IOSDevice) {
continue;
}
_cachedPolledDevices[device.id] = device;
}
}

// Only show connected devices.
final List<Device> filteredDevices = devices.where((Device device) => device.isConnected == true).toList();
notifier.updateWithNewList(filteredDevices);
} else if (eventType == XCDeviceEvent.detach && knownDevice != null) {
notifier.removeItem(knownDevice);
/// Updates notifier with devices found in the cache that are determined
/// to be connected.
void _updateNotifierFromCache() {
final ItemListNotifier<Device>? notifier = deviceNotifier;
if (notifier == null) {
return;
}
// Device is connected if it has either an observed usb or wifi connection
// or it has not been observed but was found as connected in the cache.
final List<Device> connectedDevices = _cachedPolledDevices.values.where((Device device) {
final Map<XCDeviceEventInterface, bool>? deviceObservedConnections =
_observedConnectionsByDeviceId[device.id];
return (deviceObservedConnections != null &&
_deviceHasObservedConnection(deviceObservedConnections)) ||
(deviceObservedConnections == null && device.isConnected);
}).toList();

notifier.updateWithNewList(connectedDevices);
}

bool _deviceHasObservedConnection(
Map<XCDeviceEventInterface, bool> deviceObservedConnections,
) {
return (deviceObservedConnections[XCDeviceEventInterface.usb] ?? false) ||
(deviceObservedConnections[XCDeviceEventInterface.wifi] ?? false);
}

@override
Expand Down
Loading

0 comments on commit 34d2c8d

Please sign in to comment.