| // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'dart:io'; |
| |
| import 'package:meta/meta.dart'; |
| import 'package:path/path.dart' as path; |
| // ignore: implementation_imports |
| import 'package:usage/src/usage_impl.dart'; |
| // ignore: implementation_imports |
| import 'package:usage/src/usage_impl_io.dart'; |
| // ignore: implementation_imports |
| import 'package:usage/src/usage_impl_io.dart' as usage_io show getDartVersion; |
| import 'package:usage/usage.dart'; |
| import 'package:usage/usage_io.dart'; |
| |
| export 'package:usage/usage.dart' show Analytics; |
| |
| // TODO(devoncarew): Don't show the UI until we're ready to ship. |
| final bool showAnalyticsUI = false; |
| |
| final String _dartDirectoryName = '.dart'; |
| final String _settingsFileName = 'analytics.json'; |
| |
| /// Dart SDK tools with analytics should display this notice. |
| /// |
| /// In addition, they should support displaying the analytics' status, and have |
| /// a flag to toggle analytics. This may look something like: |
| /// |
| /// `Analytics are currently enabled (and can be disabled with --no-analytics).` |
| final String analyticsNotice = |
| "Dart SDK tools anonymously report feature usage statistics and basic crash\n" |
| "reports to help improve Dart tools over time. See Google's privacy policy:\n" |
| "https://www.google.com/intl/en/policies/privacy/."; |
| |
| /// Return a customized message for command-line tools to display about the |
| /// state of analytics, and how users can enabled or disable analytics. |
| /// |
| /// An example return value might be `'Analytics are currently enabled (and can |
| /// be disabled with --no-analytics).'` |
| String createAnalyticsStatusMessage( |
| bool enabled, { |
| String command = 'analytics', |
| }) { |
| String currentState = enabled ? 'enabled' : 'disabled'; |
| String toggleState = enabled ? 'disabled' : 'enabled'; |
| String commandToggle = enabled ? 'no-$command' : command; |
| |
| return 'Analytics are currently $currentState ' |
| '(and can be $toggleState with --$commandToggle).'; |
| } |
| |
| /// Create an [Analytics] instance with the given trackingID and |
| /// applicationName. |
| /// |
| /// This analytics instance will share a common enablement state with the rest |
| /// of the Dart SDK tools. |
| Analytics createAnalyticsInstance( |
| String trackingId, |
| String applicationName, { |
| bool disableForSession = false, |
| bool forceEnabled = false, |
| }) { |
| final dir = getDartStorageDirectory(); |
| if (dir == null) { |
| // Some systems don't support user home directories; for those, fail |
| // gracefully by returning a disabled analytics object. |
| return _DisabledAnalytics(trackingId, applicationName); |
| } |
| |
| if (!dir.existsSync()) { |
| try { |
| dir.createSync(); |
| } catch (e) { |
| // If we can't create the directory for the analytics settings, fail |
| // gracefully by returning a disabled analytics object. |
| return _DisabledAnalytics(trackingId, applicationName); |
| } |
| } |
| |
| File settingsFile = File(path.join(dir.path, _settingsFileName)); |
| return _TelemetryAnalytics( |
| trackingId, |
| applicationName, |
| getDartVersion(), |
| settingsFile, |
| disableForSession: disableForSession, |
| forceEnabled: forceEnabled, |
| ); |
| } |
| |
| /// The directory used to store the analytics settings file. |
| /// |
| /// Typically, the directory is `~/.dart/` (and the settings file is |
| /// `analytics.json`). |
| /// |
| /// This can return null under some conditions, including when the user's home |
| /// directory does not exist. |
| @visibleForTesting |
| Directory? getDartStorageDirectory() { |
| Directory homeDirectory = Directory(userHomeDir()); |
| if (!homeDirectory.existsSync()) return null; |
| |
| return Directory(path.join(homeDirectory.path, _dartDirectoryName)); |
| } |
| |
| /// Return the version of the Dart SDK. |
| String getDartVersion() => usage_io.getDartVersion(); |
| |
| class _TelemetryAnalytics extends AnalyticsImpl { |
| final bool disableForSession; |
| final bool forceEnabled; |
| |
| _TelemetryAnalytics( |
| String trackingId, |
| String applicationName, |
| String applicationVersion, |
| File settingsFile, { |
| required this.disableForSession, |
| required this.forceEnabled, |
| }) : super( |
| trackingId, |
| IOPersistentProperties.fromFile(settingsFile), |
| IOPostHandler(), |
| applicationName: applicationName, |
| applicationVersion: applicationVersion, |
| ) { |
| final locale = getPlatformLocale(); |
| if (locale != null) { |
| setSessionValue('ul', locale); |
| } |
| } |
| |
| @override |
| bool get enabled { |
| if (disableForSession || isRunningOnBot()) { |
| return false; |
| } |
| |
| // This is only used in special cases. |
| if (forceEnabled) { |
| return true; |
| } |
| |
| // If there's no explicit setting (enabled or disabled) then we don't send. |
| return (properties['enabled'] as bool?) ?? false; |
| } |
| } |
| |
| class _DisabledAnalytics extends AnalyticsMock { |
| @override |
| final String trackingId; |
| @override |
| final String applicationName; |
| |
| _DisabledAnalytics(this.trackingId, this.applicationName); |
| |
| @override |
| bool get enabled => false; |
| } |
| |
| /// Detect whether we're running on a bot or in a continuous testing |
| /// environment. |
| /// |
| /// We should periodically keep this code up to date with: |
| /// https://github.com/flutter/flutter/blob/master/packages/flutter_tools/lib/src/base/bot_detector.dart#L30 |
| /// and |
| /// https://github.com/flutter/flutter/blob/master/packages/flutter_tools/lib/src/reporting/usage.dart#L200. |
| bool isRunningOnBot() { |
| final Map<String, String> env = Platform.environment; |
| |
| if ( |
| // Explicitly stated to not be a bot. |
| env['BOT'] == 'false' |
| // Set by the IDEs to the IDE name, so a strong signal that this is not a bot. |
| || |
| env.containsKey('FLUTTER_HOST') |
| // When set, GA logs to a local file (normally for tests) so we don't need to filter. |
| || |
| env.containsKey('FLUTTER_ANALYTICS_LOG_FILE')) { |
| return false; |
| } |
| |
| return env.containsKey('BOT') |
| // https://docs.travis-ci.com/user/environment-variables/ |
| // Example .travis.yml file: https://github.com/flutter/devtools/blob/master/.travis.yml |
| || |
| env['TRAVIS'] == 'true' || |
| env['CONTINUOUS_INTEGRATION'] == 'true' || |
| env.containsKey('CI') // Travis and AppVeyor |
| |
| // https://www.appveyor.com/docs/environment-variables/ |
| || |
| env.containsKey('APPVEYOR') |
| |
| // https://cirrus-ci.org/guide/writing-tasks/#environment-variables |
| || |
| env.containsKey('CIRRUS_CI') |
| |
| // https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-env-vars.html |
| || |
| (env.containsKey('AWS_REGION') && env.containsKey('CODEBUILD_INITIATOR')) |
| |
| // https://wiki.jenkins.io/display/JENKINS/Building+a+software+project#Buildingasoftwareproject-belowJenkinsSetEnvironmentVariables |
| || |
| env.containsKey('JENKINS_URL') |
| |
| // https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables |
| || |
| env.containsKey('GITHUB_ACTIONS') |
| |
| // Properties on Flutter's Chrome Infra bots. |
| || |
| env['CHROME_HEADLESS'] == '1' || |
| env.containsKey('BUILDBOT_BUILDERNAME') || |
| env.containsKey('SWARMING_TASK_ID') |
| |
| // Property when running on borg. |
| || |
| env.containsKey('BORG_ALLOC_DIR'); |
| |
| // TODO(jwren): Azure detection -- each call for this detection requires an |
| // http connection, the flutter cli tool captures the result on the first |
| // run, we should consider the same caching here. |
| } |