Show warning when attempting to flutter run on an ios device with developer mode turned off (#125710)

This PR adds a warning when a user attempt to `flutter run -d <device id>` on a device without developer mode enabled.
<img width="738" alt="Screenshot 2023-05-09 at 3 53 18 AM" src="https://github.com/flutter/flutter/assets/36148254/6f473a6a-5a0d-438b-9e6f-06d09eb1f3a9">

Also handles multiple partial matches.
<img width="788" alt="Screenshot 2023-05-09 at 3 52 24 AM" src="https://github.com/flutter/flutter/assets/36148254/60c82b3c-d501-4a01-95ad-d6309fe39576">

Fixes https://github.com/flutter/flutter/issues/111988
This commit is contained in:
LouiseHsu 2023-05-22 15:06:15 -07:00 committed by GitHub
parent 487ed57388
commit e345a830ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 99 additions and 4 deletions

View File

@ -261,6 +261,7 @@ class IOSDevice extends Device {
required this.cpuArchitecture, required this.cpuArchitecture,
required this.connectionInterface, required this.connectionInterface,
required this.isConnected, required this.isConnected,
required this.devModeEnabled,
String? sdkVersion, String? sdkVersion,
required Platform platform, required Platform platform,
required IOSDeploy iosDeploy, required IOSDeploy iosDeploy,
@ -323,6 +324,8 @@ class IOSDevice extends Device {
DevicePortForwarder? _portForwarder; DevicePortForwarder? _portForwarder;
bool devModeEnabled = false;
@visibleForTesting @visibleForTesting
IOSDeployDebugger? iosDeployDebugger; IOSDeployDebugger? iosDeployDebugger;

View File

@ -505,7 +505,7 @@ class XCDevice {
if (identifier == null || name == null) { if (identifier == null || name == null) {
continue; continue;
} }
bool devModeEnabled = true;
bool isConnected = true; bool isConnected = true;
final Map<String, Object?>? errorProperties = _errorProperties(device); final Map<String, Object?>? errorProperties = _errorProperties(device);
if (errorProperties != null) { if (errorProperties != null) {
@ -525,6 +525,10 @@ class XCDevice {
if (code != -10) { if (code != -10) {
isConnected = false; isConnected = false;
} }
if (code == 6) {
devModeEnabled = false;
}
} }
String? sdkVersion = _sdkVersion(device); String? sdkVersion = _sdkVersion(device);
@ -549,6 +553,7 @@ class XCDevice {
iosDeploy: _iosDeploy, iosDeploy: _iosDeploy,
iMobileDevice: _iMobileDevice, iMobileDevice: _iMobileDevice,
platform: globals.platform, platform: globals.platform,
devModeEnabled: devModeEnabled
)); ));
} }
} }

View File

@ -29,6 +29,8 @@ String _foundSpecifiedDevicesMessage(int count, String deviceId) =>
'Found $count devices with name or id matching $deviceId:'; 'Found $count devices with name or id matching $deviceId:';
String _noMatchingDeviceMessage(String deviceId) => 'No supported devices found with name or id ' String _noMatchingDeviceMessage(String deviceId) => 'No supported devices found with name or id '
"matching '$deviceId'."; "matching '$deviceId'.";
String flutterSpecifiedDeviceDevModeDisabled(String deviceName) => 'To use '
"'$deviceName' for development, enable Developer Mode in Settings → Privacy & Security.";
/// This class handles functionality of finding and selecting target devices. /// This class handles functionality of finding and selecting target devices.
/// ///
@ -486,18 +488,39 @@ class TargetDevicesWithExtendedWirelessDeviceDiscovery extends TargetDevices {
// If there are multiple matches, continue on to wait for all attached // If there are multiple matches, continue on to wait for all attached
// and wireless devices to load so the user can select between all // and wireless devices to load so the user can select between all
// connected matches. // connected matches.
final List<Device> devices = await _getDeviceById( final List<Device> specifiedDevices = await _getDeviceById(
includeDevicesUnsupportedByProject: includeDevicesUnsupportedByProject, includeDevicesUnsupportedByProject: includeDevicesUnsupportedByProject,
includeDisconnected: true, includeDisconnected: true,
); );
if (devices.length == 1) {
Device? matchedDevice = devices.first; 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.isConnected && matchedDevice is IOSDevice) { if (!matchedDevice.isConnected && matchedDevice is IOSDevice) {
matchedDevice = await _waitForIOSDeviceToConnect(matchedDevice); matchedDevice = await _waitForIOSDeviceToConnect(matchedDevice);
} }
if (matchedDevice != null && matchedDevice.isConnected) { if (matchedDevice != null && matchedDevice.isConnected) {
return <Device>[matchedDevice]; return <Device>[matchedDevice];
} }
} else {
for (final Device device in specifiedDevices) {
// Print warning for every matching device that does not have Developer Mode enabled.
if (device is IOSDevice && !device.devModeEnabled) {
_logger.printStatus(
flutterSpecifiedDeviceDevModeDisabled(device.name)
);
}
}
} }
} }

View File

@ -76,6 +76,7 @@ void main() {
cpuArchitecture: DarwinArch.arm64, cpuArchitecture: DarwinArch.arm64,
connectionInterface: DeviceConnectionInterface.attached, connectionInterface: DeviceConnectionInterface.attached,
isConnected: true, isConnected: true,
devModeEnabled: true,
); );
expect(device.isSupported(), isTrue); expect(device.isSupported(), isTrue);
}); });
@ -93,6 +94,7 @@ void main() {
cpuArchitecture: DarwinArch.armv7, cpuArchitecture: DarwinArch.armv7,
connectionInterface: DeviceConnectionInterface.attached, connectionInterface: DeviceConnectionInterface.attached,
isConnected: true, isConnected: true,
devModeEnabled: true,
); );
expect(device.isSupported(), isFalse); expect(device.isSupported(), isFalse);
}); });
@ -111,6 +113,7 @@ void main() {
sdkVersion: '1.0.0', sdkVersion: '1.0.0',
connectionInterface: DeviceConnectionInterface.attached, connectionInterface: DeviceConnectionInterface.attached,
isConnected: true, isConnected: true,
devModeEnabled: true,
).majorSdkVersion, 1); ).majorSdkVersion, 1);
expect(IOSDevice( expect(IOSDevice(
'device-123', 'device-123',
@ -125,6 +128,7 @@ void main() {
sdkVersion: '13.1.1', sdkVersion: '13.1.1',
connectionInterface: DeviceConnectionInterface.attached, connectionInterface: DeviceConnectionInterface.attached,
isConnected: true, isConnected: true,
devModeEnabled: true,
).majorSdkVersion, 13); ).majorSdkVersion, 13);
expect(IOSDevice( expect(IOSDevice(
'device-123', 'device-123',
@ -139,6 +143,7 @@ void main() {
sdkVersion: '10', sdkVersion: '10',
connectionInterface: DeviceConnectionInterface.attached, connectionInterface: DeviceConnectionInterface.attached,
isConnected: true, isConnected: true,
devModeEnabled: true,
).majorSdkVersion, 10); ).majorSdkVersion, 10);
expect(IOSDevice( expect(IOSDevice(
'device-123', 'device-123',
@ -153,6 +158,7 @@ void main() {
sdkVersion: '0', sdkVersion: '0',
connectionInterface: DeviceConnectionInterface.attached, connectionInterface: DeviceConnectionInterface.attached,
isConnected: true, isConnected: true,
devModeEnabled: true,
).majorSdkVersion, 0); ).majorSdkVersion, 0);
expect(IOSDevice( expect(IOSDevice(
'device-123', 'device-123',
@ -167,6 +173,7 @@ void main() {
sdkVersion: 'bogus', sdkVersion: 'bogus',
connectionInterface: DeviceConnectionInterface.attached, connectionInterface: DeviceConnectionInterface.attached,
isConnected: true, isConnected: true,
devModeEnabled: true,
).majorSdkVersion, 0); ).majorSdkVersion, 0);
}); });
@ -184,6 +191,7 @@ void main() {
cpuArchitecture: DarwinArch.arm64, cpuArchitecture: DarwinArch.arm64,
connectionInterface: DeviceConnectionInterface.attached, connectionInterface: DeviceConnectionInterface.attached,
isConnected: true, isConnected: true,
devModeEnabled: true,
); );
expect(await device.sdkNameAndVersion,'iOS 13.3 17C54'); expect(await device.sdkNameAndVersion,'iOS 13.3 17C54');
@ -203,6 +211,7 @@ void main() {
cpuArchitecture: DarwinArch.arm64, cpuArchitecture: DarwinArch.arm64,
connectionInterface: DeviceConnectionInterface.attached, connectionInterface: DeviceConnectionInterface.attached,
isConnected: true, isConnected: true,
devModeEnabled: true,
); );
expect(device.supportsRuntimeMode(BuildMode.debug), true); expect(device.supportsRuntimeMode(BuildMode.debug), true);
@ -228,6 +237,7 @@ void main() {
cpuArchitecture: DarwinArch.arm64, cpuArchitecture: DarwinArch.arm64,
connectionInterface: DeviceConnectionInterface.attached, connectionInterface: DeviceConnectionInterface.attached,
isConnected: true, isConnected: true,
devModeEnabled: true,
); );
}, },
throwsAssertionError, throwsAssertionError,
@ -319,6 +329,7 @@ void main() {
cpuArchitecture: DarwinArch.arm64, cpuArchitecture: DarwinArch.arm64,
connectionInterface: DeviceConnectionInterface.attached, connectionInterface: DeviceConnectionInterface.attached,
isConnected: true, isConnected: true,
devModeEnabled: true,
); );
logReader1 = createLogReader(device, appPackage1, process1); logReader1 = createLogReader(device, appPackage1, process1);
logReader2 = createLogReader(device, appPackage2, process2); logReader2 = createLogReader(device, appPackage2, process2);
@ -381,6 +392,7 @@ void main() {
fileSystem: MemoryFileSystem.test(), fileSystem: MemoryFileSystem.test(),
connectionInterface: DeviceConnectionInterface.attached, connectionInterface: DeviceConnectionInterface.attached,
isConnected: true, isConnected: true,
devModeEnabled: true,
); );
device2 = IOSDevice( device2 = IOSDevice(
@ -396,6 +408,7 @@ void main() {
fileSystem: MemoryFileSystem.test(), fileSystem: MemoryFileSystem.test(),
connectionInterface: DeviceConnectionInterface.attached, connectionInterface: DeviceConnectionInterface.attached,
isConnected: true, isConnected: true,
devModeEnabled: true,
); );
}); });
@ -687,6 +700,7 @@ void main() {
fileSystem: MemoryFileSystem.test(), fileSystem: MemoryFileSystem.test(),
connectionInterface: DeviceConnectionInterface.attached, connectionInterface: DeviceConnectionInterface.attached,
isConnected: false, isConnected: false,
devModeEnabled: true,
); );
}); });

View File

@ -360,5 +360,6 @@ IOSDevice setUpIOSDevice({
iProxy: IProxy.test(logger: logger, processManager: processManager), iProxy: IProxy.test(logger: logger, processManager: processManager),
connectionInterface: interfaceType ?? DeviceConnectionInterface.attached, connectionInterface: interfaceType ?? DeviceConnectionInterface.attached,
isConnected: true, isConnected: true,
devModeEnabled: true,
); );
} }

View File

@ -101,5 +101,6 @@ IOSDevice setUpIOSDevice(FileSystem fileSystem) {
iProxy: IProxy.test(logger: logger, processManager: processManager), iProxy: IProxy.test(logger: logger, processManager: processManager),
connectionInterface: DeviceConnectionInterface.attached, connectionInterface: DeviceConnectionInterface.attached,
isConnected: true, isConnected: true,
devModeEnabled: true,
); );
} }

View File

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

View File

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

View File

@ -1262,6 +1262,45 @@ target-device (mobile) • xxx • ios • iOS 16 (unsupported)
expect(deviceManager.iosDiscoverer.xcdevice.waitedForDeviceToConnect, isFalse); expect(deviceManager.iosDiscoverer.xcdevice.waitedForDeviceToConnect, isFalse);
}); });
testUsingContext('when only matching device is dev mode disabled', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[FakeIOSDevice(deviceName: 'target-device', devModeEnabled: false)];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
To use 'target-device' for development, enable Developer Mode in Settings Privacy & Security.
'''));
expect(devices, isNull);
});
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)];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
To use 'target-device-1' for development, enable Developer Mode in Settings Privacy & Security.
Checking for wireless devices...
'''));
expect(devices, isNotNull);
});
testUsingContext('when all matching devices are dev mode disabled', () async {
deviceManager.iosDiscoverer.deviceList = <Device>[FakeIOSDevice(deviceName: 'target-device-1', devModeEnabled: false, isConnected: false),
FakeIOSDevice(deviceName: 'target-device-2', devModeEnabled: false, isConnected: false)];
final List<Device>? devices = await targetDevices.findAllTargetDevices();
expect(logger.statusText, equals('''
To use 'target-device-1' for development, enable Developer Mode in Settings Privacy & Security.
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);
});
group('when deviceConnectionInterface does not match', () { group('when deviceConnectionInterface does not match', () {
testUsingContext('filter of wireless', () async { testUsingContext('filter of wireless', () async {
final FakeIOSDevice device1 = FakeIOSDevice.notConnectedWireless(deviceName: 'not-a-match'); final FakeIOSDevice device1 = FakeIOSDevice.notConnectedWireless(deviceName: 'not-a-match');
@ -2691,6 +2730,7 @@ class FakeIOSDevice extends Fake implements IOSDevice {
FakeIOSDevice({ FakeIOSDevice({
String? deviceId, String? deviceId,
String? deviceName, String? deviceName,
bool? devModeEnabled,
bool deviceSupported = true, bool deviceSupported = true,
bool deviceSupportForProject = true, bool deviceSupportForProject = true,
this.ephemeral = true, this.ephemeral = true,
@ -2699,6 +2739,7 @@ class FakeIOSDevice extends Fake implements IOSDevice {
this.connectionInterface = DeviceConnectionInterface.attached, this.connectionInterface = DeviceConnectionInterface.attached,
}) : id = deviceId ?? 'xxx', }) : id = deviceId ?? 'xxx',
name = deviceName ?? 'test', name = deviceName ?? 'test',
devModeEnabled = devModeEnabled ?? true,
_isSupported = deviceSupported, _isSupported = deviceSupported,
_isSupportedForProject = deviceSupportForProject; _isSupportedForProject = deviceSupportForProject;
@ -2710,6 +2751,7 @@ class FakeIOSDevice extends Fake implements IOSDevice {
this.ephemeral = true, this.ephemeral = true,
this.isConnected = false, this.isConnected = false,
this.platformType = PlatformType.ios, this.platformType = PlatformType.ios,
this.devModeEnabled = true,
this.connectionInterface = DeviceConnectionInterface.wireless, this.connectionInterface = DeviceConnectionInterface.wireless,
}) : id = deviceId ?? 'xxx', }) : id = deviceId ?? 'xxx',
name = deviceName ?? 'test', name = deviceName ?? 'test',
@ -2723,6 +2765,7 @@ class FakeIOSDevice extends Fake implements IOSDevice {
bool deviceSupportForProject = true, bool deviceSupportForProject = true,
this.ephemeral = true, this.ephemeral = true,
this.isConnected = true, this.isConnected = true,
this.devModeEnabled = true,
this.platformType = PlatformType.ios, this.platformType = PlatformType.ios,
this.connectionInterface = DeviceConnectionInterface.wireless, this.connectionInterface = DeviceConnectionInterface.wireless,
}) : id = deviceId ?? 'xxx', }) : id = deviceId ?? 'xxx',
@ -2739,6 +2782,9 @@ class FakeIOSDevice extends Fake implements IOSDevice {
@override @override
final bool ephemeral; final bool ephemeral;
@override
final bool devModeEnabled;
@override @override
String id; String id;