Skip to content

Commit

Permalink
[flutter_tool] Update analytics policy, send event on disable (flutte…
Browse files Browse the repository at this point in the history
  • Loading branch information
zanderso authored Nov 5, 2019
1 parent f6eb129 commit 372fe29
Show file tree
Hide file tree
Showing 16 changed files with 323 additions and 37 deletions.
5 changes: 2 additions & 3 deletions packages/flutter_tools/lib/runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -215,9 +215,8 @@ Future<String> _doctorText() async {
}

Future<int> _exit(int code) async {
if (flutterUsage.isFirstRun) {
flutterUsage.printWelcome();
}
// Prints the welcome message if needed.
flutterUsage.printWelcome();

// Send any last analytics calls that are in progress without overly delaying
// the tool's exit (we wait a maximum of 250ms).
Expand Down
8 changes: 1 addition & 7 deletions packages/flutter_tools/lib/src/base/config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@
import '../convert.dart';
import 'context.dart';
import 'file_system.dart';
import 'platform.dart';

class Config {
Config([File configFile]) {
_configFile = configFile ?? fs.file(fs.path.join(_userHomeDir(), '.flutter_settings'));
_configFile = configFile ?? fs.file(fs.path.join(userHomePath(), '.flutter_settings'));
if (_configFile.existsSync()) {
_values = json.decode(_configFile.readAsStringSync());
}
Expand Down Expand Up @@ -44,8 +43,3 @@ class Config {
_configFile.writeAsStringSync(json);
}
}

String _userHomeDir() {
final String envKey = platform.operatingSystem == 'windows' ? 'APPDATA' : 'HOME';
return platform.environment[envKey] ?? '.';
}
8 changes: 8 additions & 0 deletions packages/flutter_tools/lib/src/base/file_system.dart
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,11 @@ class FileNotFoundException implements IOException {
@override
String toString() => 'File not found: $path';
}

/// Reads the process environment to find the current user's home directory.
///
/// If the searched environment variables are not set, '.' is returned instead.
String userHomePath() {
final String envKey = platform.operatingSystem == 'windows' ? 'APPDATA' : 'HOME';
return platform.environment[envKey] ?? '.';
}
1 change: 1 addition & 0 deletions packages/flutter_tools/lib/src/commands/config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ class ConfigCommand extends FlutterCommand {
if (argResults.wasParsed('analytics')) {
final bool value = argResults['analytics'];
flutterUsage.enabled = value;
AnalyticsConfigEvent(enabled: value).send();
printStatus('Analytics reporting ${value ? 'enabled' : 'disabled'}.');
}

Expand Down
17 changes: 13 additions & 4 deletions packages/flutter_tools/lib/src/commands/upgrade.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ import '../base/process.dart';
import '../cache.dart';
import '../dart/pub.dart';
import '../globals.dart';
import '../persistent_tool_state.dart';
import '../runner/flutter_command.dart';
import '../version.dart';
import 'channel.dart';

class UpgradeCommand extends FlutterCommand {
UpgradeCommand() {
UpgradeCommand([UpgradeCommandRunner commandRunner])
: _commandRunner = commandRunner ?? UpgradeCommandRunner() {
argParser
..addFlag(
'force',
Expand All @@ -32,10 +34,14 @@ class UpgradeCommand extends FlutterCommand {
'continue',
hide: true,
negatable: false,
help: 'For the second half of the upgrade flow requiring the new version of Flutter. Should not be invoked manually, but re-entrantly by the standard upgrade command.',
help: 'For the second half of the upgrade flow requiring the new '
'version of Flutter. Should not be invoked manually, but '
're-entrantly by the standard upgrade command.',
);
}

final UpgradeCommandRunner _commandRunner;

@override
final String name = 'upgrade';

Expand All @@ -52,8 +58,7 @@ class UpgradeCommand extends FlutterCommand {

@override
Future<FlutterCommandResult> runCommand() async {
final UpgradeCommandRunner upgradeCommandRunner = UpgradeCommandRunner();
await upgradeCommandRunner.runCommand(
await _commandRunner.runCommand(
argResults['force'],
argResults['continue'],
GitTagVersion.determine(),
Expand Down Expand Up @@ -141,9 +146,13 @@ class UpgradeCommandRunner {
// This method should only be called if the upgrade command is invoked
// re-entrantly with the `--continue` flag
Future<void> runCommandSecondHalf(FlutterVersion flutterVersion) async {
// Make sure the welcome message re-display is delayed until the end.
persistentToolState.redisplayWelcomeMessage = false;
await precacheArtifacts();
await updatePackages(flutterVersion);
await runDoctor();
// Force the welcome message to re-display following the upgrade.
persistentToolState.redisplayWelcomeMessage = true;
}

Future<bool> hasUncomittedChanges() async {
Expand Down
6 changes: 4 additions & 2 deletions packages/flutter_tools/lib/src/context_runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import 'macos/macos_workflow.dart';
import 'macos/xcode.dart';
import 'macos/xcode_validator.dart';
import 'mdns_discovery.dart';
import 'persistent_tool_state.dart';
import 'reporting/reporting.dart';
import 'run_hot.dart';
import 'version.dart';
Expand Down Expand Up @@ -106,9 +107,10 @@ Future<T> runInContext<T>(
MacOSWorkflow: () => const MacOSWorkflow(),
MDnsObservatoryDiscovery: () => MDnsObservatoryDiscovery(),
OperatingSystemUtils: () => OperatingSystemUtils(),
Pub: () => const Pub(),
PersistentToolState: () => PersistentToolState(),
ProcessInfo: () => ProcessInfo(),
ProcessUtils: () => ProcessUtils(),
Pub: () => const Pub(),
Signals: () => Signals(),
SimControl: () => SimControl(),
Stdio: () => const Stdio(),
Expand All @@ -121,8 +123,8 @@ Future<T> runInContext<T>(
WebWorkflow: () => const WebWorkflow(),
WindowsWorkflow: () => const WindowsWorkflow(),
Xcode: () => Xcode(),
XcodeValidator: () => const XcodeValidator(),
XcodeProjectInterpreter: () => XcodeProjectInterpreter(),
XcodeValidator: () => const XcodeValidator(),
},
);
}
41 changes: 41 additions & 0 deletions packages/flutter_tools/lib/src/persistent_tool_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'base/config.dart';
import 'base/context.dart';
import 'base/file_system.dart';

PersistentToolState get persistentToolState => PersistentToolState.instance;

/// A class that represents global (non-project-specific) internal state that
/// must persist across tool invocations.
abstract class PersistentToolState {
factory PersistentToolState([File configFile]) =>
_DefaultPersistentToolState(configFile);

static PersistentToolState get instance => context.get<PersistentToolState>();

/// Whether the welcome message should be redisplayed.
///
/// May give null if the value has not been set.
bool redisplayWelcomeMessage;
}

class _DefaultPersistentToolState implements PersistentToolState {
_DefaultPersistentToolState([File configFile]) :
_config = Config(configFile ?? fs.file(fs.path.join(userHomePath(), _kFileName)));

static const String _kFileName = '.flutter_tool_state';
static const String _kRedisplayWelcomeMessage = 'redisplay-welcome-message';

final Config _config;

@override
bool get redisplayWelcomeMessage => _config.getValue(_kRedisplayWelcomeMessage);

@override
set redisplayWelcomeMessage(bool value) {
_config.setValue(_kRedisplayWelcomeMessage, value);
}
}
12 changes: 12 additions & 0 deletions packages/flutter_tools/lib/src/reporting/events.dart
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,15 @@ class CommandResultEvent extends UsageEvent {
}
}
}

/// An event that reports on changes in the configuration of analytics.
class AnalyticsConfigEvent extends UsageEvent {
AnalyticsConfigEvent({
/// Whether analytics reporting is being enabled (true) or disabled (false).
@required bool enabled,
}) : super(
'analytics',
'enabled',
label: enabled ? 'true' : 'false',
);
}
1 change: 1 addition & 0 deletions packages/flutter_tools/lib/src/reporting/reporting.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import '../base/utils.dart';
import '../doctor.dart';
import '../features.dart';
import '../globals.dart';
import '../persistent_tool_state.dart';
import '../runner/flutter_command.dart';
import '../version.dart';

Expand Down
50 changes: 35 additions & 15 deletions packages/flutter_tools/lib/src/reporting/usage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -323,35 +323,55 @@ class _DefaultUsage implements Usage {
await _analytics.waitForLastPing(timeout: const Duration(milliseconds: 250));
}

@override
void printWelcome() {
// This gets called if it's the first run by the selected command, if any,
// and on exit, in case there was no command.
if (_printedWelcome) {
return;
}
_printedWelcome = true;

void _printWelcome() {
printStatus('');
printStatus('''
╔════════════════════════════════════════════════════════════════════════════╗
║ Welcome to Flutter! - https://flutter.dev ║
║ ║
║ The Flutter tool anonymously reports feature usage statistics and crash ║
║ reports to Google in order to help Google contribute improvements to ║
║ Flutter over time. ║
║ The Flutter tool uses Google Analytics to anonymously report feature usage ║
║ statistics and basic crash reports. This data is used to help improve ║
║ Flutter tools over time. ║
║ ║
║ Flutter tool analytics are not sent on the very first run. To disable ║
║ reporting, type 'flutter config --no-analytics'. To display the current ║
║ setting, type 'flutter config'. If you opt out of analytics, an opt-out ║
║ event will be sent, and then no further information will be sent by the ║
║ Flutter tool. ║
║ ║
║ By downloading the Flutter SDK, you agree to the Google Terms of Service. ║
║ Note: The Google Privacy Policy describes how data is handled in this ║
║ service. ║
║ ║
║ Moreover, Flutter includes the Dart SDK, which may send usage metrics and ║
║ crash reports to Google. ║
║ ║
║ Read about data we send with crash reports: ║
║ https://github.com/flutter/flutter/wiki/Flutter-CLI-crash-reporting ║
║ ║
║ See Google's privacy policy: ║
║ https://www.google.com/intl/en/policies/privacy/ ║
║ ║
║ Use "flutter config --no-analytics" to disable analytics and crash ║
║ reporting. ║
╚════════════════════════════════════════════════════════════════════════════╝
''', emphasis: true);
}

@override
void printWelcome() {
// Only print once per run.
if (_printedWelcome) {
return;
}
if (// Display the welcome message if this is the first run of the tool.
isFirstRun ||
// Display the welcome message if we are not on master, and if the
// persistent tool state instructs that we should.
(!FlutterVersion.instance.isMaster &&
(persistentToolState.redisplayWelcomeMessage ?? true))) {
_printWelcome();
_printedWelcome = true;
persistentToolState.redisplayWelcomeMessage = false;
}
}
}

// An Analytics mock that logs to file. Unimplemented methods goes to stdout.
Expand Down
5 changes: 2 additions & 3 deletions packages/flutter_tools/lib/src/runner/flutter_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -447,9 +447,8 @@ abstract class FlutterCommand extends Command<void> {
name: 'command',
overrides: <Type, Generator>{FlutterCommand: () => this},
body: () async {
if (flutterUsage.isFirstRun) {
flutterUsage.printWelcome();
}
// Prints the welcome message if needed.
flutterUsage.printWelcome();
final String commandPath = await usagePath;
_registerSignalHandlers(commandPath, startTime);
FlutterCommandResult commandResult;
Expand Down
Loading

0 comments on commit 372fe29

Please sign in to comment.