diff --git a/packages/flutter_tools/lib/runner.dart b/packages/flutter_tools/lib/runner.dart index f8462b6dac06e..893626d95ec42 100644 --- a/packages/flutter_tools/lib/runner.dart +++ b/packages/flutter_tools/lib/runner.dart @@ -22,6 +22,7 @@ import 'src/context_runner.dart'; import 'src/doctor.dart'; import 'src/globals.dart' as globals; import 'src/reporting/crash_reporting.dart'; +import 'src/reporting/reporting.dart'; import 'src/runner/flutter_command.dart'; import 'src/runner/flutter_command_runner.dart'; @@ -61,6 +62,44 @@ Future run( StackTrace? firstStackTrace; return runZoned>(() async { try { + // Ensure that the consent message has been displayed + if (globals.analytics.shouldShowMessage) { + globals.logger.printStatus(globals.analytics.getConsentMessage); + + // Invoking this will onboard the flutter tool onto + // the package on the developer's machine and will + // allow for events to be sent to Google Analytics + // on subsequent runs of the flutter tool (ie. no events + // will be sent on the first run to allow developers to + // opt out of collection) + globals.analytics.clientShowedMessage(); + } + + // Disable analytics if user passes in the `--disable-telemetry` option + // `flutter --disable-telemetry` + // + // Same functionality as `flutter config --no-analytics` for disabling + // except with the `value` hard coded as false + if (args.contains('--disable-telemetry')) { + const bool value = false; + // The tool sends the analytics event *before* toggling the flag + // intentionally to be sure that opt-out events are sent correctly. + AnalyticsConfigEvent(enabled: value).send(); + if (!value) { + // Normally, the tool waits for the analytics to all send before the + // tool exits, but only when analytics are enabled. When reporting that + // analytics have been disable, the wait must be done here instead. + await globals.flutterUsage.ensureAnalyticsSent(); + } + globals.flutterUsage.enabled = value; + globals.printStatus('Analytics reporting disabled.'); + + // TODO(eliasyishak): Set the telemetry for the unified_analytics + // package as well, the above will be removed once we have + // fully transitioned to using the new package + await globals.analytics.setTelemetry(value); + } + await runner.run(args); // Triggering [runZoned]'s error callback does not necessarily mean that diff --git a/packages/flutter_tools/lib/src/commands/config.dart b/packages/flutter_tools/lib/src/commands/config.dart index 349490d2b31e2..bb9e26d8a930a 100644 --- a/packages/flutter_tools/lib/src/commands/config.dart +++ b/packages/flutter_tools/lib/src/commands/config.dart @@ -139,6 +139,11 @@ class ConfigCommand extends FlutterCommand { } globals.flutterUsage.enabled = value; globals.printStatus('Analytics reporting ${value ? 'enabled' : 'disabled'}.'); + + // TODO(eliasyishak): Set the telemetry for the unified_analytics + // package as well, the above will be removed once we have + // fully transitioned to using the new package + await globals.analytics.setTelemetry(value); } if (argResults?.wasParsed('android-sdk') ?? false) { diff --git a/packages/flutter_tools/lib/src/globals.dart b/packages/flutter_tools/lib/src/globals.dart index 68904a25ee73a..4ae5f9a4e5c3a 100644 --- a/packages/flutter_tools/lib/src/globals.dart +++ b/packages/flutter_tools/lib/src/globals.dart @@ -2,7 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:intl/date_symbol_data_local.dart'; import 'package:process/process.dart'; +import 'package:unified_analytics/unified_analytics.dart'; import 'android/android_sdk.dart'; import 'android/android_studio.dart'; @@ -86,6 +88,22 @@ final BotDetector _defaultBotDetector = BotDetector( ); Future get isRunningOnBot => botDetector.isRunningOnBot; +// Analytics instance for package:unified_analytics for telemetry +// reporting for all Flutter and Dart related tooling +Analytics get analytics => context.get() ?? getDefaultAnalytics(); +Analytics getDefaultAnalytics() { + + initializeDateFormatting(); + final Analytics defaultAnalytics = Analytics( + tool: DashTool.flutterTool, + flutterChannel: flutterVersion.channel, + flutterVersion: flutterVersion.frameworkVersion, + dartVersion: flutterVersion.dartSdkVersion, + ); + + return defaultAnalytics; +} + /// Currently active implementation of the file system. /// /// By default it uses local disk-based implementation. Override this in tests diff --git a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart index 23623880ed3cf..a0f3454c8fa7c 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart @@ -78,6 +78,9 @@ class FlutterCommandRunner extends CommandRunner { argParser.addFlag('suppress-analytics', negatable: false, help: 'Suppress analytics reporting when this command runs.'); + argParser.addFlag('disable-telemetry', + negatable: false, + help: 'Disable telemetry reporting when this command runs.'); argParser.addOption('packages', hide: !verboseHelp, help: 'Path to your "package_config.json" file.'); @@ -185,6 +188,11 @@ class FlutterCommandRunner extends CommandRunner { Future runCommand(ArgResults topLevelResults) async { final Map contextOverrides = {}; + // If the disable-telemetry flag has been passed, return out + if (topLevelResults.wasParsed('disable-telemetry')) { + return; + } + // Don't set wrapColumns unless the user said to: if it's set, then all // wrapping will occur at this width explicitly, and won't adapt if the // terminal size changes during a run. diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml index a74f3f2894c7c..446c308757155 100644 --- a/packages/flutter_tools/pubspec.yaml +++ b/packages/flutter_tools/pubspec.yaml @@ -48,6 +48,7 @@ dependencies: http_multi_server: 3.2.1 convert: 3.1.1 async: 2.11.0 + unified_analytics: 1.0.1 # We depend on very specific internal implementation details of the # 'test' package, which change between versions, so when upgrading @@ -104,4 +105,4 @@ dartdoc: # Exclude this package from the hosted API docs. nodoc: true -# PUBSPEC CHECKSUM: 7ab7 +# PUBSPEC CHECKSUM: 8913 diff --git a/packages/flutter_tools/test/general.shard/runner/runner_test.dart b/packages/flutter_tools/test/general.shard/runner/runner_test.dart index 43124118b80dc..6fde91ee59dda 100644 --- a/packages/flutter_tools/test/general.shard/runner/runner_test.dart +++ b/packages/flutter_tools/test/general.shard/runner/runner_test.dart @@ -18,6 +18,8 @@ import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/reporting/crash_reporting.dart'; import 'package:flutter_tools/src/reporting/reporting.dart'; import 'package:flutter_tools/src/runner/flutter_command.dart'; +import 'package:test/fake.dart'; +import 'package:unified_analytics/unified_analytics.dart'; import '../../src/common.dart'; import '../../src/context.dart'; @@ -313,6 +315,29 @@ void main() { }); }); }); + + testUsingContext('runner disable telemetry with flag', () async { + io.setExitFunctionForTests((int exitCode) {}); + + expect(globals.analytics.telemetryEnabled, true); + expect(globals.analytics.shouldShowMessage, true); + + await runner.run( + ['--disable-telemetry'], + () => [], + // This flutterVersion disables crash reporting. + flutterVersion: '[user-branch]/', + shutdownHooks: ShutdownHooks(), + ); + + expect(globals.analytics.telemetryEnabled, false); + }, + overrides: { + Analytics: () => FakeAnalytics(), + FileSystem: () => MemoryFileSystem.test(), + ProcessManager: () => FakeProcessManager.any(), + }, + ); } class CrashingFlutterCommand extends FlutterCommand { @@ -451,3 +476,30 @@ class WaitingCrashReporter implements CrashReporter { return _future; } } + +/// A fake [Analytics] that will be used to test +/// the --disable-telemetry flag +class FakeAnalytics extends Fake implements Analytics { + bool _fakeTelemetryStatus = true; + bool _fakeShowMessage = true; + + @override + String get getConsentMessage => 'message'; + + @override + bool get shouldShowMessage => _fakeShowMessage; + + @override + void clientShowedMessage() { + _fakeShowMessage = false; + } + + @override + Future setTelemetry(bool reportingBool) { + _fakeTelemetryStatus = reportingBool; + return Future.value(); + } + + @override + bool get telemetryEnabled => _fakeTelemetryStatus; +}