Print warning and exit when iOS device is unpaired (#144551)

Explicitly handle the case where the iOS device is not paired.  On `flutter run` show an error and bail instead of trying and failing to launch on the device.

On this PR:
```
$ flutter run -d 00008110-0009588C2651401E
'iPhone' is not paired. Open Xcode and trust this computer when prompted.
$
```

Fixes https://github.com/flutter/flutter/issues/144447
Closes https://github.com/flutter/flutter/pull/144095
This commit is contained in:
Jenn Magder 2024-03-04 15:01:11 -08:00 committed by GitHub
parent d9eea7963d
commit 9b442b2749
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 109 additions and 17 deletions

View file

@ -267,6 +267,7 @@ class IOSDevice extends Device {
required this.cpuArchitecture,
required this.connectionInterface,
required this.isConnected,
required this.isPaired,
required this.devModeEnabled,
required this.isCoreDevice,
String? sdkVersion,
@ -336,6 +337,11 @@ class IOSDevice extends Device {
@override
bool isConnected;
bool devModeEnabled = false;
/// Device has trusted this computer and paired.
bool isPaired = false;
/// CoreDevice is a device connectivity stack introduced in Xcode 15. Devices
/// with iOS 17 or greater are CoreDevices.
final bool isCoreDevice;
@ -344,8 +350,6 @@ class IOSDevice extends Device {
DevicePortForwarder? _portForwarder;
bool devModeEnabled = false;
@visibleForTesting
IOSDeployDebugger? iosDeployDebugger;

View file

@ -544,6 +544,7 @@ class XCDevice {
}
bool devModeEnabled = true;
bool isConnected = true;
bool isPaired = true;
final Map<String, Object?>? errorProperties = _errorProperties(device);
if (errorProperties != null) {
final String? errorMessage = _parseErrorMessage(errorProperties);
@ -564,6 +565,10 @@ class XCDevice {
if (code != -10) {
isConnected = false;
}
// Error: iPhone is not paired with your computer. To use iPhone with Xcode, unlock it and choose to trust this computer when prompted. (code -9)
if (code == -9) {
isPaired = false;
}
if (code == 6) {
devModeEnabled = false;
@ -635,6 +640,7 @@ class XCDevice {
xcodeDebug: _xcodeDebug,
platform: globals.platform,
devModeEnabled: devModeEnabled,
isPaired: isPaired,
isCoreDevice: coreDevice != null,
);
}

View file

@ -30,6 +30,8 @@ String _noMatchingDeviceMessage(String deviceId) => 'No supported devices found
"matching '$deviceId'.";
String flutterSpecifiedDeviceDevModeDisabled(String deviceName) => 'To use '
"'$deviceName' for development, enable Developer Mode in Settings → Privacy & Security.";
String flutterSpecifiedDeviceUnpaired(String deviceName) => "'$deviceName' is not paired. "
'Open Xcode and trust this computer when prompted.';
/// This class handles functionality of finding and selecting target devices.
///
@ -494,17 +496,24 @@ class TargetDevicesWithExtendedWirelessDeviceDiscovery extends TargetDevices {
if (specifiedDevices.length == 1) {
Device? matchedDevice = specifiedDevices.first;
// If the only matching device does not have Developer Mode enabled,
// print a warning
if (matchedDevice is IOSDevice && !matchedDevice.devModeEnabled) {
_logger.printStatus(
flutterSpecifiedDeviceDevModeDisabled(matchedDevice.name)
);
return null;
}
if (matchedDevice is IOSDevice) {
// If the only matching device is not paired, print a warning
if (!matchedDevice.isPaired) {
_logger.printStatus(flutterSpecifiedDeviceUnpaired(matchedDevice.name));
return null;
}
// If the only matching device does not have Developer Mode enabled,
// print a warning
if (!matchedDevice.devModeEnabled) {
_logger.printStatus(
flutterSpecifiedDeviceDevModeDisabled(matchedDevice.name)
);
return null;
}
if (!matchedDevice.isConnected && matchedDevice is IOSDevice) {
matchedDevice = await _waitForIOSDeviceToConnect(matchedDevice);
if (!matchedDevice.isConnected) {
matchedDevice = await _waitForIOSDeviceToConnect(matchedDevice);
}
}
if (matchedDevice != null && matchedDevice.isConnected) {
@ -512,9 +521,14 @@ class TargetDevicesWithExtendedWirelessDeviceDiscovery extends TargetDevices {
}
} else {
for (final Device device in specifiedDevices) {
for (final IOSDevice device in specifiedDevices.whereType<IOSDevice>()) {
// Print warning for every matching unpaired device.
if (!device.isPaired) {
_logger.printStatus(flutterSpecifiedDeviceUnpaired(device.name));
}
// Print warning for every matching device that does not have Developer Mode enabled.
if (device is IOSDevice && !device.devModeEnabled) {
if (!device.devModeEnabled) {
_logger.printStatus(
flutterSpecifiedDeviceDevModeDisabled(device.name)
);

View file

@ -85,6 +85,7 @@ void main() {
cpuArchitecture: DarwinArch.arm64,
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
isPaired: true,
devModeEnabled: true,
isCoreDevice: false,
);
@ -106,6 +107,7 @@ void main() {
cpuArchitecture: DarwinArch.armv7,
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
isPaired: true,
devModeEnabled: true,
isCoreDevice: false,
);
@ -128,6 +130,7 @@ void main() {
sdkVersion: '1.0.0',
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
isPaired: true,
devModeEnabled: true,
isCoreDevice: false,
).majorSdkVersion, 1);
@ -146,6 +149,7 @@ void main() {
sdkVersion: '13.1.1',
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
isPaired: true,
devModeEnabled: true,
isCoreDevice: false,
).majorSdkVersion, 13);
@ -164,6 +168,7 @@ void main() {
sdkVersion: '10',
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
isPaired: true,
devModeEnabled: true,
isCoreDevice: false,
).majorSdkVersion, 10);
@ -182,6 +187,7 @@ void main() {
sdkVersion: '0',
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
isPaired: true,
devModeEnabled: true,
isCoreDevice: false,
).majorSdkVersion, 0);
@ -200,6 +206,7 @@ void main() {
sdkVersion: 'bogus',
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
isPaired: true,
devModeEnabled: true,
isCoreDevice: false,
).majorSdkVersion, 0);
@ -221,6 +228,7 @@ void main() {
sdkVersion: '13.3.1',
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
isPaired: true,
devModeEnabled: true,
isCoreDevice: false,
).sdkVersion;
@ -244,6 +252,7 @@ void main() {
sdkVersion: '13.3.1 (20ADBC)',
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
isPaired: true,
devModeEnabled: true,
isCoreDevice: false,
).sdkVersion;
@ -267,6 +276,7 @@ void main() {
sdkVersion: '16.4.1(a) (20ADBC)',
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
isPaired: true,
devModeEnabled: true,
isCoreDevice: false,
).sdkVersion;
@ -290,6 +300,7 @@ void main() {
sdkVersion: '0',
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
isPaired: true,
devModeEnabled: true,
isCoreDevice: false,
).sdkVersion;
@ -312,6 +323,7 @@ void main() {
cpuArchitecture: DarwinArch.arm64,
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
isPaired: true,
devModeEnabled: true,
isCoreDevice: false,
).sdkVersion;
@ -332,6 +344,7 @@ void main() {
sdkVersion: 'bogus',
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
isPaired: true,
devModeEnabled: true,
isCoreDevice: false,
).sdkVersion;
@ -354,6 +367,7 @@ void main() {
cpuArchitecture: DarwinArch.arm64,
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
isPaired: true,
devModeEnabled: true,
isCoreDevice: false,
);
@ -377,6 +391,7 @@ void main() {
cpuArchitecture: DarwinArch.arm64,
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
isPaired: true,
devModeEnabled: true,
isCoreDevice: false,
);
@ -406,6 +421,7 @@ void main() {
cpuArchitecture: DarwinArch.arm64,
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
isPaired: true,
devModeEnabled: true,
isCoreDevice: false,
);
@ -501,6 +517,7 @@ void main() {
cpuArchitecture: DarwinArch.arm64,
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
isPaired: true,
devModeEnabled: true,
isCoreDevice: false,
);
@ -571,6 +588,7 @@ void main() {
fileSystem: MemoryFileSystem.test(),
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
isPaired: true,
devModeEnabled: true,
isCoreDevice: false,
);
@ -590,6 +608,7 @@ void main() {
fileSystem: MemoryFileSystem.test(),
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
isPaired: true,
devModeEnabled: true,
isCoreDevice: false,
);
@ -889,6 +908,7 @@ void main() {
fileSystem: MemoryFileSystem.test(),
connectionInterface: DeviceConnectionInterface.attached,
isConnected: false,
isPaired: true,
devModeEnabled: true,
isCoreDevice: false,
);

View file

@ -434,6 +434,7 @@ IOSDevice setUpIOSDevice({
iProxy: IProxy.test(logger: logger, processManager: processManager),
connectionInterface: interfaceType ?? DeviceConnectionInterface.attached,
isConnected: true,
isPaired: true,
devModeEnabled: true,
isCoreDevice: isCoreDevice,
);

View file

@ -106,6 +106,7 @@ IOSDevice setUpIOSDevice(FileSystem fileSystem) {
iProxy: IProxy.test(logger: logger, processManager: processManager),
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
isPaired: true,
devModeEnabled: true,
isCoreDevice: false,
);

View file

@ -869,6 +869,7 @@ IOSDevice setUpIOSDevice({
cpuArchitecture: DarwinArch.arm64,
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
isPaired: true,
devModeEnabled: true,
isCoreDevice: isCoreDevice,
);

View file

@ -1094,6 +1094,7 @@ IOSDevice setUpIOSDevice({
cpuArchitecture: DarwinArch.arm64,
connectionInterface: interfaceType,
isConnected: true,
isPaired: true,
devModeEnabled: true,
isCoreDevice: isCoreDevice,
);

View file

@ -1275,7 +1275,7 @@ To use 'target-device' for development, enable Developer Mode in Settings → Pr
testUsingContext('when one of the matching devices has dev mode disabled', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[FakeIOSDevice(deviceName: 'target-device-1', devModeEnabled: false, isConnected: false),
FakeIOSDevice(deviceName: 'target-device-2', devModeEnabled: true)];
FakeIOSDevice(deviceName: 'target-device-2')];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
@ -1296,6 +1296,45 @@ To use 'target-device-1' for development, enable Developer Mode in Settings →
To use 'target-device-2' for development, enable Developer Mode in Settings Privacy & Security.
No devices found yet. Checking for wireless devices...
No supported devices found with name or id matching 'target-device'.
'''));
expect(devices, isNull);
});
testUsingContext('when only matching device is unpaired', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[FakeIOSDevice(deviceName: 'target-device', isPaired: false)];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
'target-device' is not paired. Open Xcode and trust this computer when prompted.
'''));
expect(devices, isNull);
});
testUsingContext('when one of the matching devices is unpaired', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[FakeIOSDevice(deviceName: 'target-device-1', isPaired: false, isConnected: false),
FakeIOSDevice(deviceName: 'target-device-2')];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, contains('''
'target-device-1' is not paired. Open Xcode and trust this computer when prompted.
Checking for wireless devices...
'''));
expect(devices, isNotNull);
});
testUsingContext('when all matching devices are unpaired', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[FakeIOSDevice(deviceName: 'target-device-1', isPaired: false, isConnected: false),
FakeIOSDevice(deviceName: 'target-device-2', isPaired: false, isConnected: false)];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, contains('''
'target-device-1' is not paired. Open Xcode and trust this computer when prompted.
'target-device-2' is not paired. Open Xcode and trust this computer when prompted.
No devices found yet. Checking for wireless devices...
No supported devices found with name or id matching 'target-device'.
'''));
expect(devices, isNull);
@ -2777,16 +2816,16 @@ class FakeIOSDevice extends Fake implements IOSDevice {
FakeIOSDevice({
String? deviceId,
String? deviceName,
bool? devModeEnabled,
bool deviceSupported = true,
bool deviceSupportForProject = true,
this.ephemeral = true,
this.isConnected = true,
this.devModeEnabled = true,
this.isPaired = true,
this.platformType = PlatformType.ios,
this.connectionInterface = DeviceConnectionInterface.attached,
}) : id = deviceId ?? 'xxx',
name = deviceName ?? 'test',
devModeEnabled = devModeEnabled ?? true,
_isSupported = deviceSupported,
_isSupportedForProject = deviceSupportForProject;
@ -2799,6 +2838,7 @@ class FakeIOSDevice extends Fake implements IOSDevice {
this.isConnected = false,
this.platformType = PlatformType.ios,
this.devModeEnabled = true,
this.isPaired = true,
this.connectionInterface = DeviceConnectionInterface.wireless,
}) : id = deviceId ?? 'xxx',
name = deviceName ?? 'test',
@ -2813,6 +2853,7 @@ class FakeIOSDevice extends Fake implements IOSDevice {
this.ephemeral = true,
this.isConnected = true,
this.devModeEnabled = true,
this.isPaired = true,
this.platformType = PlatformType.ios,
this.connectionInterface = DeviceConnectionInterface.wireless,
}) : id = deviceId ?? 'xxx',
@ -2832,6 +2873,9 @@ class FakeIOSDevice extends Fake implements IOSDevice {
@override
final bool devModeEnabled;
@override
final bool isPaired;
@override
String id;