diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart index 384e3b09eb..ef1499a2bf 100644 --- a/packages/flutter_tools/lib/src/android/android_device.dart +++ b/packages/flutter_tools/lib/src/android/android_device.dart @@ -10,7 +10,7 @@ import '../android/android_builder.dart'; import '../android/android_sdk.dart'; import '../android/android_workflow.dart'; import '../application_package.dart'; -import '../base/common.dart' show throwToolExit; +import '../base/common.dart' show throwToolExit, unawaited; import '../base/file_system.dart'; import '../base/io.dart'; import '../base/logger.dart'; @@ -1014,29 +1014,36 @@ class _AdbLogReader extends DeviceLogReader { @override String get name => device.name; - void _start() { + Future _start() async { final String lastTimestamp = device.lastLogcatTimestamp; // Start the adb logcat process and filter the most recent logs since `lastTimestamp`. final List args = [ 'logcat', '-v', 'time', - '-T', - lastTimestamp ?? '', // Empty `-T` means the timestamp of the logcat command invocation. ]; - processUtils.start(device.adbCommandForDevice(args)).then((Process process) { - _process = process; - // We expect logcat streams to occasionally contain invalid utf-8, - // see: https://github.com/flutter/flutter/pull/8864. - const Utf8Decoder decoder = Utf8Decoder(reportErrors: false); - _process.stdout.transform(decoder).transform(const LineSplitter()).listen(_onLine); - _process.stderr.transform(decoder).transform(const LineSplitter()).listen(_onLine); - _process.exitCode.whenComplete(() { - if (_linesController.hasListener) { - _linesController.close(); - } - }); - }); + // logcat -T is not supported on Android releases before Lollipop. + const int kLollipopVersionCode = 21; + final int apiVersion = int.tryParse(await device._apiVersion); + if (apiVersion != null && apiVersion >= kLollipopVersionCode) { + args.addAll([ + '-T', + lastTimestamp ?? '', // Empty `-T` means the timestamp of the logcat command invocation. + ]); + } + + _process = await processUtils.start(device.adbCommandForDevice(args)); + + // We expect logcat streams to occasionally contain invalid utf-8, + // see: https://github.com/flutter/flutter/pull/8864. + const Utf8Decoder decoder = Utf8Decoder(reportErrors: false); + _process.stdout.transform(decoder).transform(const LineSplitter()).listen(_onLine); + _process.stderr.transform(decoder).transform(const LineSplitter()).listen(_onLine); + unawaited(_process.exitCode.whenComplete(() { + if (_linesController.hasListener) { + _linesController.close(); + } + })); } // 'W/ActivityManager(pid): ' 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 d2d82a78be..c6ade4c9c1 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 @@ -717,21 +717,38 @@ flutter: setUp(() { mockAndroidSdk = MockAndroidSdk(); mockProcessManager = MockProcessManager(); + + 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(), ''); + return Future.value(result); + }); }); testUsingContext('calls adb logcat with expected flags', () async { - const String klastLocatcatTimestamp = '11-27 15:39:04.506'; + const String kLastLogcatTimestamp = '11-27 15:39:04.506'; when(mockAndroidSdk.adbPath).thenReturn('adb'); when(mockProcessManager.runSync(['adb', '-s', '1234', 'shell', '-x', 'logcat', '-v', 'time', '-t', '1'])) - .thenReturn(ProcessResult(0, 0, '$klastLocatcatTimestamp I/flutter: irrelevant', '')); + .thenReturn(ProcessResult(0, 0, '$kLastLogcatTimestamp I/flutter: irrelevant', '')); + + final Completer logcatCompleter = Completer(); when(mockProcessManager.start(argThat(contains('logcat')))) - .thenAnswer((_) => Future.value(createMockProcess())); + .thenAnswer((_) { + logcatCompleter.complete(); + return Future.value(createMockProcess()); + }); final AndroidDevice device = AndroidDevice('1234'); final DeviceLogReader logReader = device.getLogReader(); logReader.logLines.listen((_) {}); + await logcatCompleter.future; - verify(mockProcessManager.start(const ['adb', '-s', '1234', 'logcat', '-v', 'time', '-T', klastLocatcatTimestamp])) + verify(mockProcessManager.start(const ['adb', '-s', '1234', 'logcat', '-v', 'time', '-T', kLastLogcatTimestamp])) .called(1); }, overrides: { AndroidSdk: () => mockAndroidSdk, @@ -742,12 +759,18 @@ flutter: when(mockAndroidSdk.adbPath).thenReturn('adb'); when(mockProcessManager.runSync(['adb', '-s', '1234', 'shell', '-x', 'logcat', '-v', 'time', '-t', '1'])) .thenReturn(ProcessResult(0, 0, '', '')); + + final Completer logcatCompleter = Completer(); when(mockProcessManager.start(argThat(contains('logcat')))) - .thenAnswer((_) => Future.value(createMockProcess())); + .thenAnswer((_) { + logcatCompleter.complete(); + return Future.value(createMockProcess()); + }); final AndroidDevice device = AndroidDevice('1234'); final DeviceLogReader logReader = device.getLogReader(); logReader.logLines.listen((_) {}); + await logcatCompleter.future; verify(mockProcessManager.start(const ['adb', '-s', '1234', 'logcat', '-v', 'time', '-T', ''])) .called(1);