Honor legacy opt out status (#80)

* Function added to check dart and flutter legacy analytics files

* Sort members in utils.dart

* Logic updated to change config string if legacy optout

* Update tests for both dart and flutter legacy analytics

* Doc clean up

* On error, assume user has opted out of legacy analytics

* Update CHANGELOG.md

* Alter consent message based on tool using package

* Clean up try blocks

* Update try blocks for add'l exception + ignore lint for http client

* Change home directory location for windows

* Update documentation on http client
diff --git a/pkgs/unified_analytics/CHANGELOG.md b/pkgs/unified_analytics/CHANGELOG.md
index 43aac7d..ba3b576 100644
--- a/pkgs/unified_analytics/CHANGELOG.md
+++ b/pkgs/unified_analytics/CHANGELOG.md
@@ -1,6 +1,7 @@
 ## 1.1.0
 
 - Added a `okToSend` getter so that clients can easily and accurately check the state of the consent mechanism.
+- Initialize the config file with user opted out if user was opted out in legacy Flutter and Dart analytics
 
 ## 1.0.1
 
diff --git a/pkgs/unified_analytics/analysis_options.yaml b/pkgs/unified_analytics/analysis_options.yaml
index a3a02e6..e6794b3 100644
--- a/pkgs/unified_analytics/analysis_options.yaml
+++ b/pkgs/unified_analytics/analysis_options.yaml
@@ -18,6 +18,7 @@
 linter:
   rules:
     - always_declare_return_types
+    - avoid_catches_without_on_clauses
     - camel_case_types
     - prefer_single_quotes
     - unawaited_futures
diff --git a/pkgs/unified_analytics/lib/src/analytics.dart b/pkgs/unified_analytics/lib/src/analytics.dart
index c7b08ce..da67861 100644
--- a/pkgs/unified_analytics/lib/src/analytics.dart
+++ b/pkgs/unified_analytics/lib/src/analytics.dart
@@ -376,8 +376,15 @@
   }
 
   @override
-  String get getConsentMessage =>
-      kToolsMessage.replaceAll('[tool name]', tool.description);
+  String get getConsentMessage {
+    // The command to swap in the consent message
+    final String commandString =
+        tool == DashTool.flutterTool ? 'flutter' : 'dart';
+
+    return kToolsMessage
+        .replaceAll('[tool name]', tool.description)
+        .replaceAll('[dart|flutter]', commandString);
+  }
 
   /// Checking the [telemetryEnabled] boolean reflects what the
   /// config file reflects
diff --git a/pkgs/unified_analytics/lib/src/ga_client.dart b/pkgs/unified_analytics/lib/src/ga_client.dart
index 0fd01f5..8396eb9 100644
--- a/pkgs/unified_analytics/lib/src/ga_client.dart
+++ b/pkgs/unified_analytics/lib/src/ga_client.dart
@@ -47,15 +47,28 @@
 
   /// Receive the payload in Map form and parse
   /// into JSON to send to GA
+  ///
+  /// The [Response] returned from this method can be
+  /// checked to ensure that events have been sent. A response
+  /// status code of `2xx` indicates a successful send event.
+  /// A response status code of `500` indicates an error occured on the send
+  /// can the error message can be found in the [Response.body]
   Future<http.Response> sendData(Map<String, Object?> body) async {
+    final Uri uri = Uri.parse(postUrl);
+
+    /// Using a try catch all since post method can result in several
+    /// errors; clients using this method can check the awaited status
+    /// code to get a specific error message if the status code returned
+    /// is a 500 error status code
     try {
       return await _client.post(
-        Uri.parse(postUrl),
+        uri,
         headers: <String, String>{
           'Content-Type': 'application/json; charset=UTF-8',
         },
         body: jsonEncode(body),
       );
+      // ignore: avoid_catches_without_on_clauses
     } catch (error) {
       return Future<http.Response>.value(http.Response(error.toString(), 500));
     }
diff --git a/pkgs/unified_analytics/lib/src/initializer.dart b/pkgs/unified_analytics/lib/src/initializer.dart
index 981a747..256314f 100644
--- a/pkgs/unified_analytics/lib/src/initializer.dart
+++ b/pkgs/unified_analytics/lib/src/initializer.dart
@@ -60,7 +60,16 @@
     required int toolsMessageVersion,
   }) {
     configFile.createSync(recursive: true);
-    configFile.writeAsStringSync(kConfigString);
+
+    // If the user was previously opted out, then we will
+    // replace the line that assumes automatic opt in with
+    // an opt out from the start
+    if (legacyOptOut(fs: fs, home: homeDirectory)) {
+      configFile.writeAsStringSync(
+          kConfigString.replaceAll('reporting=1', 'reporting=0'));
+    } else {
+      configFile.writeAsStringSync(kConfigString);
+    }
   }
 
   /// Creates that log file that will store the record formatted
diff --git a/pkgs/unified_analytics/lib/src/utils.dart b/pkgs/unified_analytics/lib/src/utils.dart
index e9b5562..2b69d9a 100644
--- a/pkgs/unified_analytics/lib/src/utils.dart
+++ b/pkgs/unified_analytics/lib/src/utils.dart
@@ -2,10 +2,12 @@
 // 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:convert';
 import 'dart:io' as io;
 import 'dart:math' show Random;
 
 import 'package:file/file.dart';
+import 'package:path/path.dart' as p;
 
 import 'enums.dart';
 import 'user_property.dart';
@@ -75,12 +77,88 @@
   } else if (io.Platform.isLinux) {
     home = envVars['HOME'];
   } else if (io.Platform.isWindows) {
-    home = envVars['UserProfile'];
+    home = envVars['AppData'];
   }
 
   return fs.directory(home!);
 }
 
+/// Returns `true` if user has opted out of legacy analytics in Dart or Flutter
+///
+/// Checks legacy opt-out status for the Flutter
+/// and Dart in the following locations
+///
+/// Dart: `$HOME/.dart/dartdev.json`
+///
+/// Flutter: `$HOME/.flutter`
+bool legacyOptOut({
+  required FileSystem fs,
+  required Directory home,
+}) {
+  final File dartLegacyConfigFile =
+      fs.file(p.join(home.path, '.dart', 'dartdev.json'));
+  final File flutterLegacyConfigFile = fs.file(p.join(home.path, '.flutter'));
+
+  // Example of what the file looks like for dart
+  //
+  // {
+  //   "firstRun": false,
+  //   "enabled": false,  <-- THIS USER HAS OPTED OUT
+  //   "disclosureShown": true,
+  //   "clientId": "52710e60-7c70-4335-b3a4-9d922630f12a"
+  // }
+  if (dartLegacyConfigFile.existsSync()) {
+    try {
+      // Read in the json object into a Map and check for
+      // the enabled key being set to false; this means the user
+      // has opted out of analytics for dart
+      final Map<String, Object?> dartObj =
+          jsonDecode(dartLegacyConfigFile.readAsStringSync());
+      if (dartObj.containsKey('enabled') && dartObj['enabled'] == false) {
+        return true;
+      }
+    } on FormatException {
+      // In the case of an error when parsing the json file, return true
+      // which will result in the user being opted out of unified_analytics
+      //
+      // A corrupted file could mean they opted out previously but for some
+      // reason, the file was written incorrectly
+      return true;
+    } on FileSystemException {
+      return true;
+    }
+  }
+
+  // Example of what the file looks like for flutter
+  //
+  // {
+  //   "firstRun": false,
+  //   "clientId": "4c3a3d1e-e545-47e7-b4f8-10129f6ab169",
+  //   "enabled": false  <-- THIS USER HAS OPTED OUT
+  // }
+  if (flutterLegacyConfigFile.existsSync()) {
+    try {
+      // Same process as above for dart
+      final Map<String, Object?> flutterObj =
+          jsonDecode(dartLegacyConfigFile.readAsStringSync());
+      if (flutterObj.containsKey('enabled') && flutterObj['enabled'] == false) {
+        return true;
+      }
+    } on FormatException {
+      // In the case of an error when parsing the json file, return true
+      // which will result in the user being opted out of unified_analytics
+      //
+      // A corrupted file could mean they opted out previously but for some
+      // reason, the file was written incorrectly
+      return true;
+    } on FileSystemException {
+      return true;
+    }
+  }
+
+  return false;
+}
+
 /// A UUID generator.
 ///
 /// This will generate unique IDs in the format:
diff --git a/pkgs/unified_analytics/test/legacy_analytics_test.dart b/pkgs/unified_analytics/test/legacy_analytics_test.dart
new file mode 100644
index 0000000..3a70f0b
--- /dev/null
+++ b/pkgs/unified_analytics/test/legacy_analytics_test.dart
@@ -0,0 +1,234 @@
+// Copyright (c) 2023, 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' as io;
+
+import 'package:file/file.dart';
+import 'package:file/memory.dart';
+import 'package:test/test.dart';
+
+import 'package:unified_analytics/unified_analytics.dart';
+
+void main() {
+  late FileSystem fs;
+  late Directory home;
+  late Analytics analytics;
+
+  const String homeDirName = 'home';
+  const DashTool initialTool = DashTool.flutterTool;
+  const String measurementId = 'measurementId';
+  const String apiSecret = 'apiSecret';
+  const int toolsMessageVersion = 1;
+  const String toolsMessage = 'toolsMessage';
+  const String flutterChannel = 'flutterChannel';
+  const String flutterVersion = 'flutterVersion';
+  const String dartVersion = 'dartVersion';
+  const DevicePlatform platform = DevicePlatform.macos;
+
+  setUp(() {
+    // Setup the filesystem with the home directory
+    final FileSystemStyle fsStyle =
+        io.Platform.isWindows ? FileSystemStyle.windows : FileSystemStyle.posix;
+    fs = MemoryFileSystem.test(style: fsStyle);
+    home = fs.directory(homeDirName);
+  });
+
+  test('Honor legacy dart analytics opt out', () {
+    // Create the file for the dart legacy opt out
+    final File dartLegacyConfigFile =
+        home.childDirectory('.dart').childFile('dartdev.json');
+    dartLegacyConfigFile.createSync(recursive: true);
+    dartLegacyConfigFile.writeAsStringSync('''
+{
+  "firstRun": false,
+  "enabled": false,
+  "disclosureShown": true,
+  "clientId": "52710e60-7c70-4335-b3a4-9d922630f12a"
+}
+''');
+
+    // The main analytics instance, other instances can be spawned within tests
+    // to test how to instances running together work
+    analytics = Analytics.test(
+      tool: initialTool,
+      homeDirectory: home,
+      measurementId: measurementId,
+      apiSecret: apiSecret,
+      flutterChannel: flutterChannel,
+      toolsMessageVersion: toolsMessageVersion,
+      toolsMessage: toolsMessage,
+      flutterVersion: flutterVersion,
+      dartVersion: dartVersion,
+      fs: fs,
+      platform: platform,
+    );
+
+    expect(analytics.telemetryEnabled, false);
+  });
+
+  test('Telemetry enabled if legacy dart analytics is enabled', () {
+    // Create the file for the dart legacy opt out
+    final File dartLegacyConfigFile =
+        home.childDirectory('.dart').childFile('dartdev.json');
+    dartLegacyConfigFile.createSync(recursive: true);
+    dartLegacyConfigFile.writeAsStringSync('''
+{
+  "firstRun": false,
+  "enabled": true,
+  "disclosureShown": true,
+  "clientId": "52710e60-7c70-4335-b3a4-9d922630f12a"
+}
+''');
+
+    // The main analytics instance, other instances can be spawned within tests
+    // to test how to instances running together work
+    analytics = Analytics.test(
+      tool: initialTool,
+      homeDirectory: home,
+      measurementId: measurementId,
+      apiSecret: apiSecret,
+      flutterChannel: flutterChannel,
+      toolsMessageVersion: toolsMessageVersion,
+      toolsMessage: toolsMessage,
+      flutterVersion: flutterVersion,
+      dartVersion: dartVersion,
+      fs: fs,
+      platform: platform,
+    );
+
+    expect(analytics.telemetryEnabled, true);
+  });
+
+  test('Honor legacy flutter analytics opt out', () {
+    // Create the file for the dart legacy opt out
+    final File flutterLegacyConfigFile =
+        home.childDirectory('.dart').childFile('dartdev.json');
+    flutterLegacyConfigFile.createSync(recursive: true);
+    flutterLegacyConfigFile.writeAsStringSync('''
+{
+  "firstRun": false,
+  "clientId": "4c3a3d1e-e545-47e7-b4f8-10129f6ab169",
+  "enabled": false
+}
+''');
+
+    // The main analytics instance, other instances can be spawned within tests
+    // to test how to instances running together work
+    analytics = Analytics.test(
+      tool: initialTool,
+      homeDirectory: home,
+      measurementId: measurementId,
+      apiSecret: apiSecret,
+      flutterChannel: flutterChannel,
+      toolsMessageVersion: toolsMessageVersion,
+      toolsMessage: toolsMessage,
+      flutterVersion: flutterVersion,
+      dartVersion: dartVersion,
+      fs: fs,
+      platform: platform,
+    );
+
+    expect(analytics.telemetryEnabled, false);
+  });
+
+  test('Telemetry enabled if legacy flutter analytics is enabled', () {
+    // Create the file for the dart legacy opt out
+    final File flutterLegacyConfigFile =
+        home.childDirectory('.dart').childFile('dartdev.json');
+    flutterLegacyConfigFile.createSync(recursive: true);
+    flutterLegacyConfigFile.writeAsStringSync('''
+{
+  "firstRun": false,
+  "clientId": "4c3a3d1e-e545-47e7-b4f8-10129f6ab169",
+  "enabled": true
+}
+''');
+
+    // The main analytics instance, other instances can be spawned within tests
+    // to test how to instances running together work
+    analytics = Analytics.test(
+      tool: initialTool,
+      homeDirectory: home,
+      measurementId: measurementId,
+      apiSecret: apiSecret,
+      flutterChannel: flutterChannel,
+      toolsMessageVersion: toolsMessageVersion,
+      toolsMessage: toolsMessage,
+      flutterVersion: flutterVersion,
+      dartVersion: dartVersion,
+      fs: fs,
+      platform: platform,
+    );
+
+    expect(analytics.telemetryEnabled, true);
+  });
+
+  test('Telemetry disabled if dart config file corrupted', () {
+    // Create the file for the dart legacy opt out with text that
+    // is not valid JSON
+    final File dartLegacyConfigFile =
+        home.childDirectory('.dart').childFile('dartdev.json');
+    dartLegacyConfigFile.createSync(recursive: true);
+    dartLegacyConfigFile.writeAsStringSync('''
+NOT VALID JSON
+{
+  "firstRun": false,
+  "clientId": "4c3a3d1e-e545-47e7-b4f8-10129f6ab169",
+  "enabled": true
+}
+''');
+
+    // The main analytics instance, other instances can be spawned within tests
+    // to test how to instances running together work
+    analytics = Analytics.test(
+      tool: initialTool,
+      homeDirectory: home,
+      measurementId: measurementId,
+      apiSecret: apiSecret,
+      flutterChannel: flutterChannel,
+      toolsMessageVersion: toolsMessageVersion,
+      toolsMessage: toolsMessage,
+      flutterVersion: flutterVersion,
+      dartVersion: dartVersion,
+      fs: fs,
+      platform: platform,
+    );
+
+    expect(analytics.telemetryEnabled, false);
+  });
+
+  test('Telemetry disabled if flutter config file corrupted', () {
+    // Create the file for the dart legacy opt out with text that
+    // is not valid JSON
+    final File fluttterLegacyConfigFile =
+        home.childDirectory('.dart').childFile('dartdev.json');
+    fluttterLegacyConfigFile.createSync(recursive: true);
+    fluttterLegacyConfigFile.writeAsStringSync('''
+NOT VALID JSON
+{
+  "firstRun": false,
+  "clientId": "4c3a3d1e-e545-47e7-b4f8-10129f6ab169",
+  "enabled": true
+}
+''');
+
+    // The main analytics instance, other instances can be spawned within tests
+    // to test how to instances running together work
+    analytics = Analytics.test(
+      tool: initialTool,
+      homeDirectory: home,
+      measurementId: measurementId,
+      apiSecret: apiSecret,
+      flutterChannel: flutterChannel,
+      toolsMessageVersion: toolsMessageVersion,
+      toolsMessage: toolsMessage,
+      flutterVersion: flutterVersion,
+      dartVersion: dartVersion,
+      fs: fs,
+      platform: platform,
+    );
+
+    expect(analytics.telemetryEnabled, false);
+  });
+}
diff --git a/pkgs/unified_analytics/test/unified_analytics_test.dart b/pkgs/unified_analytics/test/unified_analytics_test.dart
index 4e555c1..acadae3 100644
--- a/pkgs/unified_analytics/test/unified_analytics_test.dart
+++ b/pkgs/unified_analytics/test/unified_analytics_test.dart
@@ -1095,7 +1095,7 @@
     expect(kGoogleAnalyticsMeasurementId, 'G-04BXPVBCWJ');
   });
 
-  test('Consent message is formatted correctly', () {
+  test('Consent message is formatted correctly for the flutter tool', () {
     // Retrieve the consent message for flutter tools
     final String consentMessage = analytics.getConsentMessage;
 
@@ -1106,7 +1106,41 @@
 Flutter framework, and related tools. Telemetry is not sent on the very first
 run. To disable reporting of telemetry, run this terminal command:
 
-[dart|flutter] --disable-telemetry.
+flutter --disable-telemetry.
+
+If you opt out of telemetry, an opt-out event will be sent, and then no
+further information will be sent. This data is collected in accordance with
+the Google Privacy Policy (https://policies.google.com/privacy).
+'''));
+  });
+
+  test('Consent message is formatted correctly for any tool other than flutter',
+      () {
+    // Create a new instance of the analytics class with the new tool
+    final Analytics secondAnalytics = Analytics.test(
+      tool: secondTool,
+      homeDirectory: home,
+      measurementId: 'measurementId',
+      apiSecret: 'apiSecret',
+      flutterChannel: 'ey-test-channel',
+      toolsMessageVersion: toolsMessageVersion,
+      toolsMessage: toolsMessage,
+      flutterVersion: 'Flutter 3.6.0-7.0.pre.47',
+      dartVersion: 'Dart 2.19.0',
+      fs: fs,
+      platform: platform,
+    );
+
+    // Retrieve the consent message for flutter tools
+    final String consentMessage = secondAnalytics.getConsentMessage;
+    expect(consentMessage, equalsIgnoringWhitespace(r'''
+The Dart CLI developer tool uses Google Analytics to report usage and
+diagnostic data along with package dependencies, and crash reporting to
+send basic crash reports. This data is used to help improve the Dart platform,
+Flutter framework, and related tools. Telemetry is not sent on the very first
+run. To disable reporting of telemetry, run this terminal command:
+
+dart --disable-telemetry.
 
 If you opt out of telemetry, an opt-out event will be sent, and then no
 further information will be sent. This data is collected in accordance with