[flutter_tools] Fix crash on null Android apiVersion (#50029)
diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart
index 282465d..7bca5b0 100644
--- a/packages/flutter_tools/lib/src/android/android_device.dart
+++ b/packages/flutter_tools/lib/src/android/android_device.dart
@@ -1040,7 +1040,11 @@
];
// logcat -T is not supported on Android releases before Lollipop.
const int kLollipopVersionCode = 21;
- final int apiVersion = int.tryParse(await device._apiVersion);
+ final int apiVersion = (String v) {
+ // If the API version string isn't found, conservatively assume that the
+ // version is less recent than the one we're looking for.
+ return v == null ? kLollipopVersionCode - 1 : int.tryParse(v);
+ }(await device._apiVersion);
if (apiVersion != null && apiVersion >= kLollipopVersionCode) {
args.addAll(<String>[
'-T',
diff --git a/packages/flutter_tools/test/general.shard/android/android_device_test.dart b/packages/flutter_tools/test/general.shard/android/android_device_test.dart
index b16c196..06f9533 100644
--- a/packages/flutter_tools/test/general.shard/android/android_device_test.dart
+++ b/packages/flutter_tools/test/general.shard/android/android_device_test.dart
@@ -698,67 +698,88 @@
setUp(() {
mockAndroidSdk = MockAndroidSdk();
mockProcessManager = MockProcessManager();
+ });
+ void setupGetprop({int apiVersion}) {
when(mockProcessManager.run(
argThat(contains('getprop')),
stderrEncoding: anyNamed('stderrEncoding'),
stdoutEncoding: anyNamed('stdoutEncoding'),
)).thenAnswer((_) {
- final StringBuffer buf = StringBuffer()
- ..writeln('[ro.build.version.sdk]: [28]');
- final ProcessResult result = ProcessResult(1, 0, buf.toString(), '');
+ final String buf = apiVersion == null
+ ? ''
+ : '[ro.build.version.sdk]: [$apiVersion]\n';
+ final ProcessResult result = ProcessResult(1, 0, buf, '');
return Future<ProcessResult>.value(result);
});
- });
+ }
- testUsingContext('calls adb logcat with expected flags', () async {
- const String kLastLogcatTimestamp = '11-27 15:39:04.506';
- when(mockAndroidSdk.adbPath).thenReturn('adb');
- when(mockProcessManager.runSync(<String>['adb', '-s', '1234', 'shell', '-x', 'logcat', '-v', 'time', '-t', '1']))
- .thenReturn(ProcessResult(0, 0, '$kLastLogcatTimestamp I/flutter: irrelevant', ''));
+ const int kLollipopVersionCode = 21;
+ void callsAdbLogcatCorrectly({int apiVersion}) {
+ testUsingContext('calls adb logcat with expected flags, apiVersion=$apiVersion', () async {
+ const String kLastLogcatTimestamp = '11-27 15:39:04.506';
+ setupGetprop(apiVersion: apiVersion);
+ when(mockAndroidSdk.adbPath).thenReturn('adb');
+ when(mockProcessManager.runSync(<String>[
+ 'adb', '-s', '1234', 'shell', '-x', 'logcat', '-v', 'time', '-t', '1',
+ ]))
+ .thenReturn(ProcessResult(0, 0, '$kLastLogcatTimestamp I/flutter: irrelevant', ''));
- final Completer<void> logcatCompleter = Completer<void>();
- when(mockProcessManager.start(argThat(contains('logcat'))))
- .thenAnswer((_) {
- logcatCompleter.complete();
- return Future<Process>.value(createMockProcess());
- });
+ final Completer<void> logcatCompleter = Completer<void>();
+ when(mockProcessManager.start(argThat(contains('logcat'))))
+ .thenAnswer((_) {
+ logcatCompleter.complete();
+ return Future<Process>.value(createMockProcess());
+ });
- final AndroidDevice device = AndroidDevice('1234');
- final DeviceLogReader logReader = device.getLogReader();
- logReader.logLines.listen((_) {});
- await logcatCompleter.future;
+ final AndroidDevice device = AndroidDevice('1234');
+ final DeviceLogReader logReader = device.getLogReader();
+ logReader.logLines.listen((_) {});
+ await logcatCompleter.future;
- verify(mockProcessManager.start(const <String>['adb', '-s', '1234', 'logcat', '-v', 'time', '-T', kLastLogcatTimestamp]))
- .called(1);
- }, overrides: <Type, Generator>{
- AndroidSdk: () => mockAndroidSdk,
- ProcessManager: () => mockProcessManager,
- });
+ verify(mockProcessManager.start(<String>[
+ 'adb', '-s', '1234', 'logcat', '-v', 'time',
+ if (apiVersion != null && apiVersion >= kLollipopVersionCode)
+ ...<String>['-T', kLastLogcatTimestamp],
+ ])).called(1);
+ }, overrides: <Type, Generator>{
+ AndroidSdk: () => mockAndroidSdk,
+ ProcessManager: () => mockProcessManager,
+ });
- testUsingContext('calls adb logcat with expected flags when the device logs are empty', () async {
- when(mockAndroidSdk.adbPath).thenReturn('adb');
- when(mockProcessManager.runSync(<String>['adb', '-s', '1234', 'shell', '-x', 'logcat', '-v', 'time', '-t', '1']))
- .thenReturn(ProcessResult(0, 0, '', ''));
+ testUsingContext('calls adb logcat with expected flags when the device logs are empty, apiVersion=$apiVersion', () async {
+ setupGetprop(apiVersion: apiVersion);
+ when(mockAndroidSdk.adbPath).thenReturn('adb');
+ when(mockProcessManager.runSync(<String>[
+ 'adb', '-s', '1234', 'shell', '-x', 'logcat', '-v', 'time', '-t', '1',
+ ])).thenReturn(ProcessResult(0, 0, '', ''));
- final Completer<void> logcatCompleter = Completer<void>();
- when(mockProcessManager.start(argThat(contains('logcat'))))
- .thenAnswer((_) {
- logcatCompleter.complete();
- return Future<Process>.value(createMockProcess());
- });
+ final Completer<void> logcatCompleter = Completer<void>();
+ when(mockProcessManager.start(argThat(contains('logcat'))))
+ .thenAnswer((_) {
+ logcatCompleter.complete();
+ return Future<Process>.value(createMockProcess());
+ });
- final AndroidDevice device = AndroidDevice('1234');
- final DeviceLogReader logReader = device.getLogReader();
- logReader.logLines.listen((_) {});
- await logcatCompleter.future;
+ final AndroidDevice device = AndroidDevice('1234');
+ final DeviceLogReader logReader = device.getLogReader();
+ logReader.logLines.listen((_) {});
+ await logcatCompleter.future;
- verify(mockProcessManager.start(const <String>['adb', '-s', '1234', 'logcat', '-v', 'time', '-T', '']))
- .called(1);
- }, overrides: <Type, Generator>{
- AndroidSdk: () => mockAndroidSdk,
- ProcessManager: () => mockProcessManager,
- });
+ verify(mockProcessManager.start(<String>[
+ 'adb', '-s', '1234', 'logcat', '-v', 'time',
+ if (apiVersion != null && apiVersion >= kLollipopVersionCode)
+ ...<String>['-T', ''],
+ ])).called(1);
+ }, overrides: <Type, Generator>{
+ AndroidSdk: () => mockAndroidSdk,
+ ProcessManager: () => mockProcessManager,
+ });
+ }
+
+ callsAdbLogcatCorrectly(apiVersion: kLollipopVersionCode);
+ callsAdbLogcatCorrectly(apiVersion: kLollipopVersionCode - 1);
+ callsAdbLogcatCorrectly();
});
test('Can parse adb shell dumpsys info', () {