Skip to content

Commit b0c98b6

Browse files
authored
Detect USB/network interface from iOS devices (flutter#58257)
1 parent 7d17c53 commit b0c98b6

9 files changed

+102
-27
lines changed

packages/flutter_tools/lib/src/ios/devices.dart

+18-4
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ class IOSDevices extends PollingDeviceDiscovery {
5858
);
5959
}
6060

61-
return await _xcdevice.getAvailableTetheredIOSDevices(timeout: timeout);
61+
return await _xcdevice.getAvailableIOSDevices(timeout: timeout);
6262
}
6363

6464
@override
@@ -73,11 +73,18 @@ class IOSDevices extends PollingDeviceDiscovery {
7373
}
7474
}
7575

76+
enum IOSDeviceInterface {
77+
none,
78+
usb,
79+
network,
80+
}
81+
7682
class IOSDevice extends Device {
7783
IOSDevice(String id, {
7884
@required FileSystem fileSystem,
7985
@required this.name,
8086
@required this.cpuArchitecture,
87+
@required this.interfaceType,
8188
@required String sdkVersion,
8289
@required Platform platform,
8390
@required Artifacts artifacts,
@@ -123,16 +130,21 @@ class IOSDevice extends Device {
123130
}
124131

125132
@override
126-
bool get supportsHotReload => true;
133+
bool get supportsHotReload => interfaceType == IOSDeviceInterface.usb;
127134

128135
@override
129-
bool get supportsHotRestart => true;
136+
bool get supportsHotRestart => interfaceType == IOSDeviceInterface.usb;
137+
138+
@override
139+
bool get supportsFlutterExit => interfaceType == IOSDeviceInterface.usb;
130140

131141
@override
132142
final String name;
133143

134144
final DarwinArch cpuArchitecture;
135145

146+
final IOSDeviceInterface interfaceType;
147+
136148
Map<IOSApp, DeviceLogReader> _logReaders;
137149

138150
DevicePortForwarder _portForwarder;
@@ -178,6 +190,7 @@ class IOSDevice extends Device {
178190
deviceId: id,
179191
bundlePath: bundle.path,
180192
launchArguments: <String>[],
193+
interfaceType: interfaceType,
181194
);
182195
} on ProcessException catch (e) {
183196
_logger.printError(e.message);
@@ -319,6 +332,7 @@ class IOSDevice extends Device {
319332
deviceId: id,
320333
bundlePath: bundle.path,
321334
launchArguments: launchArguments,
335+
interfaceType: interfaceType,
322336
);
323337
if (installationResult != 0) {
324338
_logger.printError('Could not run ${bundle.path} on $id.');
@@ -410,7 +424,7 @@ class IOSDevice extends Device {
410424
void clearLogs() { }
411425

412426
@override
413-
bool get supportsScreenshot => _iMobileDevice.isInstalled;
427+
bool get supportsScreenshot => _iMobileDevice.isInstalled && interfaceType == IOSDeviceInterface.usb;
414428

415429
@override
416430
Future<void> takeScreenshot(File outputFile) async {

packages/flutter_tools/lib/src/ios/ios_deploy.dart

+7-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import '../base/process.dart';
1414
import '../build_info.dart';
1515
import '../cache.dart';
1616
import 'code_signing.dart';
17+
import 'devices.dart';
1718

1819
// Error message patterns from ios-deploy output
1920
const String noProvisioningProfileErrorOne = 'Error 0xe8008015';
@@ -84,14 +85,16 @@ class IOSDeploy {
8485
@required String deviceId,
8586
@required String bundlePath,
8687
@required List<String>launchArguments,
88+
@required IOSDeviceInterface interfaceType,
8789
}) async {
8890
final List<String> launchCommand = <String>[
8991
_binaryPath,
9092
'--id',
9193
deviceId,
9294
'--bundle',
9395
bundlePath,
94-
'--no-wifi',
96+
if (interfaceType != IOSDeviceInterface.network)
97+
'--no-wifi',
9598
if (launchArguments.isNotEmpty) ...<String>[
9699
'--args',
97100
launchArguments.join(' '),
@@ -113,14 +116,16 @@ class IOSDeploy {
113116
@required String deviceId,
114117
@required String bundlePath,
115118
@required List<String> launchArguments,
119+
@required IOSDeviceInterface interfaceType,
116120
}) async {
117121
final List<String> launchCommand = <String>[
118122
_binaryPath,
119123
'--id',
120124
deviceId,
121125
'--bundle',
122126
bundlePath,
123-
'--no-wifi',
127+
if (interfaceType != IOSDeviceInterface.network)
128+
'--no-wifi',
124129
'--justlaunch',
125130
if (launchArguments.isNotEmpty) ...<String>[
126131
'--args',

packages/flutter_tools/lib/src/macos/xcode.dart

+18-6
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ class XCDevice {
290290
List<dynamic> _cachedListResults;
291291

292292
/// [timeout] defaults to 2 seconds.
293-
Future<List<IOSDevice>> getAvailableTetheredIOSDevices({ Duration timeout }) async {
293+
Future<List<IOSDevice>> getAvailableIOSDevices({ Duration timeout }) async {
294294
final List<dynamic> allAvailableDevices = await _getAllDevices(timeout: timeout ?? const Duration(seconds: 2));
295295

296296
if (allAvailableDevices == null) {
@@ -364,15 +364,19 @@ class XCDevice {
364364
}
365365
}
366366

367+
final IOSDeviceInterface interface = _interfaceType(deviceProperties);
368+
367369
// Only support USB devices, skip "network" interface (Xcode > Window > Devices and Simulators > Connect via network).
368-
if (!_isUSBTethered(deviceProperties)) {
370+
// TODO(jmagman): Remove this check once wirelessly detected devices can be observed and attached, https://github.com/flutter/flutter/issues/15072.
371+
if (interface != IOSDeviceInterface.usb) {
369372
continue;
370373
}
371374

372375
devices.add(IOSDevice(
373376
device['identifier'] as String,
374377
name: device['name'] as String,
375378
cpuArchitecture: _cpuArchitecture(deviceProperties),
379+
interfaceType: interface,
376380
sdkVersion: _sdkVersion(deviceProperties),
377381
artifacts: globals.artifacts,
378382
fileSystem: globals.fs,
@@ -409,10 +413,18 @@ class XCDevice {
409413
return null;
410414
}
411415

412-
static bool _isUSBTethered(Map<String, dynamic> deviceProperties) {
413-
// Interface can be "usb", "network", or not present for simulators.
414-
return deviceProperties.containsKey('interface') &&
415-
(deviceProperties['interface'] as String).toLowerCase() == 'usb';
416+
static IOSDeviceInterface _interfaceType(Map<String, dynamic> deviceProperties) {
417+
// Interface can be "usb", "network", or "none" for simulators
418+
// and unknown future interfaces.
419+
if (deviceProperties.containsKey('interface')) {
420+
if ((deviceProperties['interface'] as String).toLowerCase() == 'network') {
421+
return IOSDeviceInterface.network;
422+
} else {
423+
return IOSDeviceInterface.usb;
424+
}
425+
}
426+
427+
return IOSDeviceInterface.none;
416428
}
417429

418430
static String _sdkVersion(Map<String, dynamic> deviceProperties) {

packages/flutter_tools/test/general.shard/ios/devices_test.dart

+16-7
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ void main() {
7272
iMobileDevice: iMobileDevice,
7373
name: 'iPhone 1',
7474
sdkVersion: '13.3',
75-
cpuArchitecture: DarwinArch.arm64
75+
cpuArchitecture: DarwinArch.arm64,
76+
interfaceType: IOSDeviceInterface.usb,
7677
);
7778
});
7879

@@ -87,7 +88,8 @@ void main() {
8788
iMobileDevice: iMobileDevice,
8889
name: 'iPhone 1',
8990
cpuArchitecture: DarwinArch.arm64,
90-
sdkVersion: '1.0.0'
91+
sdkVersion: '1.0.0',
92+
interfaceType: IOSDeviceInterface.usb,
9193
).majorSdkVersion, 1);
9294
expect(IOSDevice(
9395
'device-123',
@@ -99,7 +101,8 @@ void main() {
99101
iMobileDevice: iMobileDevice,
100102
name: 'iPhone 1',
101103
cpuArchitecture: DarwinArch.arm64,
102-
sdkVersion: '13.1.1'
104+
sdkVersion: '13.1.1',
105+
interfaceType: IOSDeviceInterface.usb,
103106
).majorSdkVersion, 13);
104107
expect(IOSDevice(
105108
'device-123',
@@ -111,7 +114,8 @@ void main() {
111114
iMobileDevice: iMobileDevice,
112115
name: 'iPhone 1',
113116
cpuArchitecture: DarwinArch.arm64,
114-
sdkVersion: '10'
117+
sdkVersion: '10',
118+
interfaceType: IOSDeviceInterface.usb,
115119
).majorSdkVersion, 10);
116120
expect(IOSDevice(
117121
'device-123',
@@ -123,7 +127,8 @@ void main() {
123127
iMobileDevice: iMobileDevice,
124128
name: 'iPhone 1',
125129
cpuArchitecture: DarwinArch.arm64,
126-
sdkVersion: '0'
130+
sdkVersion: '0',
131+
interfaceType: IOSDeviceInterface.usb,
127132
).majorSdkVersion, 0);
128133
expect(IOSDevice(
129134
'device-123',
@@ -135,7 +140,8 @@ void main() {
135140
iMobileDevice: iMobileDevice,
136141
name: 'iPhone 1',
137142
cpuArchitecture: DarwinArch.arm64,
138-
sdkVersion: 'bogus'
143+
sdkVersion: 'bogus',
144+
interfaceType: IOSDeviceInterface.usb,
139145
).majorSdkVersion, 0);
140146
});
141147

@@ -154,6 +160,7 @@ void main() {
154160
name: 'iPhone 1',
155161
sdkVersion: '13.3',
156162
cpuArchitecture: DarwinArch.arm64,
163+
interfaceType: IOSDeviceInterface.usb,
157164
);
158165
},
159166
throwsAssertionError,
@@ -237,6 +244,7 @@ void main() {
237244
name: 'iPhone 1',
238245
sdkVersion: '13.3',
239246
cpuArchitecture: DarwinArch.arm64,
247+
interfaceType: IOSDeviceInterface.usb,
240248
);
241249
logReader1 = createLogReader(device, appPackage1, mockProcess1);
242250
logReader2 = createLogReader(device, appPackage2, mockProcess2);
@@ -321,8 +329,9 @@ void main() {
321329
logger: logger,
322330
platform: macPlatform,
323331
fileSystem: MemoryFileSystem.test(),
332+
interfaceType: IOSDeviceInterface.usb,
324333
);
325-
when(mockXcdevice.getAvailableTetheredIOSDevices())
334+
when(mockXcdevice.getAvailableIOSDevices())
326335
.thenAnswer((Invocation invocation) => Future<List<IOSDevice>>.value(<IOSDevice>[device]));
327336

328337
final List<Device> devices = await iosDevices.pollingGetDevices();

packages/flutter_tools/test/general.shard/ios/ios_device_install_test.dart

+33-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const Map<String, String> kDyLdLibEntry = <String, String>{
2525
};
2626

2727
void main() {
28-
testWithoutContext('IOSDevice.installApp calls ios-deploy correctly', () async {
28+
testWithoutContext('IOSDevice.installApp calls ios-deploy correctly with USB', () async {
2929
final FileSystem fileSystem = MemoryFileSystem.test();
3030
final IOSApp iosApp = PrebuiltIOSApp(
3131
projectBundleId: 'app',
@@ -47,6 +47,36 @@ void main() {
4747
final IOSDevice device = setUpIOSDevice(
4848
processManager: processManager,
4949
fileSystem: fileSystem,
50+
interfaceType: IOSDeviceInterface.usb,
51+
);
52+
final bool wasInstalled = await device.installApp(iosApp);
53+
54+
expect(wasInstalled, true);
55+
expect(processManager.hasRemainingExpectations, false);
56+
});
57+
58+
testWithoutContext('IOSDevice.installApp calls ios-deploy correctly with network', () async {
59+
final FileSystem fileSystem = MemoryFileSystem.test();
60+
final IOSApp iosApp = PrebuiltIOSApp(
61+
projectBundleId: 'app',
62+
bundleDir: fileSystem.currentDirectory,
63+
);
64+
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
65+
const FakeCommand(command: <String>[
66+
'ios-deploy',
67+
'--id',
68+
'1234',
69+
'--bundle',
70+
'/',
71+
], environment: <String, String>{
72+
'PATH': '/usr/bin:null',
73+
...kDyLdLibEntry,
74+
})
75+
]);
76+
final IOSDevice device = setUpIOSDevice(
77+
processManager: processManager,
78+
fileSystem: fileSystem,
79+
interfaceType: IOSDeviceInterface.network,
5080
);
5181
final bool wasInstalled = await device.installApp(iosApp);
5282

@@ -237,6 +267,7 @@ IOSDevice setUpIOSDevice({
237267
@required ProcessManager processManager,
238268
FileSystem fileSystem,
239269
Logger logger,
270+
IOSDeviceInterface interfaceType,
240271
}) {
241272
logger ??= BufferLogger.test();
242273
final FakePlatform platform = FakePlatform(
@@ -270,6 +301,7 @@ IOSDevice setUpIOSDevice({
270301
cache: cache,
271302
),
272303
artifacts: artifacts,
304+
interfaceType: interfaceType,
273305
);
274306
}
275307

packages/flutter_tools/test/general.shard/ios/ios_device_project_test.dart

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ IOSDevice setUpIOSDevice(FileSystem fileSystem) {
8989
sdkVersion: '13.3',
9090
cpuArchitecture: DarwinArch.arm64,
9191
artifacts: artifacts,
92+
interfaceType: IOSDeviceInterface.usb,
9293
);
9394
}
9495

packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart

+1
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,7 @@ IOSDevice setUpIOSDevice({
329329
cache: cache,
330330
),
331331
cpuArchitecture: DarwinArch.arm64,
332+
interfaceType: IOSDeviceInterface.usb,
332333
);
333334
}
334335

packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart

+1
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,7 @@ IOSDevice setUpIOSDevice({
398398
cache: cache,
399399
),
400400
cpuArchitecture: DarwinArch.arm64,
401+
interfaceType: IOSDeviceInterface.usb,
401402
);
402403
}
403404

0 commit comments

Comments
 (0)