Do not use logcat -T on pre-lollipop Android
diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart
index 22dbcd9..f222c1d 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_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';
@@ -854,22 +854,36 @@
@override
String get name => device.name;
- void _start() {
+ Future<void> _start() async {
+ final String lastTimestamp = device.lastLogcatTimestamp;
// Start the adb logcat process and filter logs by the "flutter" tag.
- final List<String> args = <String>['shell', '-x', 'logcat', '-v', 'time', '-s', 'flutter'];
- processUtils.start(device.adbCommandForDevice(args)).then<void>((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<String>(decoder).transform<String>(const LineSplitter()).listen(_onLine);
- _process.stderr.transform<String>(decoder).transform<String>(const LineSplitter()).listen(_onLine);
- _process.exitCode.whenComplete(() {
- if (_linesController.hasListener) {
- _linesController.close();
- }
- });
- });
+ final List<String> args = <String>[
+ 'logcat',
+ '-v',
+ 'time',
+ ];
+ // 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(<String>[
+ '-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<String>(decoder).transform<String>(const LineSplitter()).listen(_onLine);
+ _process.stderr.transform<String>(decoder).transform<String>(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 a765a5b..c33089c 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
@@ -679,6 +679,76 @@
ProcessManager: () => mockProcessManager,
});
});
+
+ group('logReader', () {
+ ProcessManager mockProcessManager;
+ AndroidSdk mockAndroidSdk;
+
+ 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<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', ''));
+
+ 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;
+
+ verify(mockProcessManager.start(const <String>['adb', '-s', '1234', 'logcat', '-v', 'time', '-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, '', ''));
+
+ 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;
+
+ verify(mockProcessManager.start(const <String>['adb', '-s', '1234', 'logcat', '-v', 'time', '-T', '']))
+ .called(1);
+ }, overrides: <Type, Generator>{
+ AndroidSdk: () => mockAndroidSdk,
+ ProcessManager: () => mockProcessManager,
+ });
+ });
}
class MockProcessManager extends Mock implements ProcessManager {}