refacor api
diff --git a/.analysis_options b/.analysis_options
index c071edc..c702eea 100644
--- a/.analysis_options
+++ b/.analysis_options
@@ -4,3 +4,4 @@
     enableConditionalDirectives: true
   exclude:
     - example/flutter_example/**
+    - lib/src/usage_impl_flutter.dart
diff --git a/changelog.md b/changelog.md
index 17a1cd6..ff1087a 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,10 @@
 # Changelog
 
+## 2.2.0
+- added `Analytics.firstRun`
+- added `Analytics.enabled`
+- removed `Analytics.optIn`
+
 ## 2.1.0
 - added `Analytics.getSessionValue()`
 - added `Analytics.onSend`
diff --git a/example/example.dart b/example/example.dart
index 5ddb51a..27fb5fc 100644
--- a/example/example.dart
+++ b/example/example.dart
@@ -26,7 +26,6 @@
   if (_analytics == null || _lastUa != _ua()) {
     _lastUa = _ua();
     _analytics = await Analytics.create(_lastUa, 'Test app', '1.0');
-    _analytics.optIn = true;
     _analytics.sendScreenView(window.location.pathname);
   }
 
diff --git a/example/flutter_example/lib/main.dart b/example/flutter_example/lib/main.dart
index 9a34065..ff2d78d 100644
--- a/example/flutter_example/lib/main.dart
+++ b/example/flutter_example/lib/main.dart
@@ -38,7 +38,7 @@
 
   void _handleOptIn(bool value) {
     setState(() {
-      config.ga.optIn = value;
+      config.ga.enabled = value;
     });
   }
 
@@ -54,9 +54,9 @@
             child: new Text("Button pressed $_times times.")
           ),
           new ListItem(
-            onTap: () => _handleOptIn(!config.ga.optIn),
+            onTap: () => _handleOptIn(!config.ga.enabled),
             leading: new Checkbox(
-              value: config.ga.optIn,
+              value: config.ga.enabled,
               onChanged: _handleOptIn
             ),
             title: new Text("Opt in to analytics")
diff --git a/example/ga.dart b/example/ga.dart
index 60e1f07..e905ea6 100644
--- a/example/ga.dart
+++ b/example/ga.dart
@@ -22,7 +22,6 @@
   String ua = args.isEmpty ? DEFAULT_UA : args.first;
 
   Analytics ga = await Analytics.create(ua, 'ga_test', '1.0');
-  ga.optIn = true;
 
   ga.sendScreenView('home').then((_) {
     return ga.sendScreenView('files');
diff --git a/lib/src/usage_impl.dart b/lib/src/usage_impl.dart
index 18191e8..5eb80fb 100644
--- a/lib/src/usage_impl.dart
+++ b/lib/src/usage_impl.dart
@@ -2,8 +2,6 @@
 // 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.
 
-library usage_impl;
-
 import 'dart:async';
 import 'dart:math' as math;
 
@@ -63,7 +61,9 @@
 class AnalyticsImpl implements Analytics {
   static const String _defaultAnalyticsUrl = 'https://www.google-analytics.com/collect';
 
-  /// Tracking ID / Property ID.
+  /**
+   * Tracking ID / Property ID.
+   */
   final String trackingId;
 
   final PersistentProperties properties;
@@ -74,6 +74,8 @@
 
   final List<Future> _futures = [];
 
+  AnalyticsOpt analyticsOpt = AnalyticsOpt.optOut;
+
   String _url;
 
   StreamController<Map<String, dynamic>> _sendController = new StreamController.broadcast(sync: true);
@@ -94,13 +96,31 @@
     _url = analyticsUrl ?? _defaultAnalyticsUrl;
   }
 
-  bool get optIn => properties['optIn'] == true;
+  bool _firstRun;
 
-  set optIn(bool value) {
-    properties['optIn'] = value;
+  bool get firstRun {
+    if (_firstRun == null) {
+      _firstRun = properties['firstRun'] == null;
+      properties['firstRun'] = false;
+    }
+
+    return _firstRun;
   }
 
-  bool get hasSetOptIn => properties['optIn'] != null;
+  /**
+   * Will analytics data be sent?
+   */
+  bool get enabled {
+    bool optIn = analyticsOpt == AnalyticsOpt.optIn;
+    return optIn ? properties['enabled'] == true : properties['enabled'] != false;
+  }
+
+  /**
+   * Enable or disable sending of analytics data.
+   */
+  set enabled(bool value) {
+    properties['enabled'] = value;
+  }
 
   Future sendScreenView(String viewName) {
     Map<String, dynamic> args = {'cd': viewName};
@@ -108,8 +128,6 @@
   }
 
   Future sendEvent(String category, String action, {String label, int value}) {
-    if (!optIn) return new Future.value();
-
     Map<String, dynamic> args = {'ec': category, 'ea': action};
     if (label != null) args['el'] = label;
     if (value != null) args['ev'] = value;
@@ -117,16 +135,11 @@
   }
 
   Future sendSocial(String network, String action, String target) {
-    if (!optIn) return new Future.value();
-
     Map<String, dynamic> args = {'sn': network, 'sa': action, 'st': target};
     return _sendPayload('social', args);
   }
 
-  Future sendTiming(String variableName, int time, {String category,
-        String label}) {
-    if (!optIn) return new Future.value();
-
+  Future sendTiming(String variableName, int time, {String category, String label}) {
     Map<String, dynamic> args = {'utv': variableName, 'utt': time};
     if (label != null) args['utl'] = label;
     if (category != null) args['utc'] = category;
@@ -139,8 +152,6 @@
   }
 
   Future sendException(String description, {bool fatal}) {
-    if (!optIn) return new Future.value();
-
     // In order to ensure that the client of this API is not sending any PII
     // data, we strip out any stack trace that may reference a path on the
     // user's drive (file:/...).
@@ -206,6 +217,8 @@
    * 'transaction', 'item', 'social', 'exception', and 'timing'.
    */
   Future _sendPayload(String hitType, Map<String, dynamic> args) {
+    if (!enabled) return new Future.value();
+
     if (_bucket.removeDrop()) {
       _initClientId();
 
diff --git a/lib/src/usage_impl_default.dart b/lib/src/usage_impl_default.dart
index 37d9300..78b3f46 100644
--- a/lib/src/usage_impl_default.dart
+++ b/lib/src/usage_impl_default.dart
@@ -2,8 +2,6 @@
 // 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.
 
-library usage_impl;
-
 import 'dart:async';
 
 import '../usage.dart';
diff --git a/lib/src/usage_impl_flutter.dart b/lib/src/usage_impl_flutter.dart
index 39067d9..756d726 100644
--- a/lib/src/usage_impl_flutter.dart
+++ b/lib/src/usage_impl_flutter.dart
@@ -2,8 +2,6 @@
 // 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.
 
-library usage_impl_io;
-
 import 'dart:async';
 import 'dart:convert' show JSON;
 import 'dart:io';
diff --git a/lib/src/usage_impl_html.dart b/lib/src/usage_impl_html.dart
index fe9e9a3..6a5922c 100644
--- a/lib/src/usage_impl_html.dart
+++ b/lib/src/usage_impl_html.dart
@@ -2,8 +2,6 @@
 // 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.
 
-library usage_impl_html;
-
 import 'dart:async';
 import 'dart:convert' show JSON;
 import 'dart:html';
diff --git a/lib/src/usage_impl_io.dart b/lib/src/usage_impl_io.dart
index a43862b..5b10994 100644
--- a/lib/src/usage_impl_io.dart
+++ b/lib/src/usage_impl_io.dart
@@ -2,8 +2,6 @@
 // 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.
 
-library usage_impl_io;
-
 import 'dart:async';
 import 'dart:convert' show JSON;
 import 'dart:io';
diff --git a/lib/src/uuid.dart b/lib/src/uuid.dart
index 91b9bbb..66e99ac 100644
--- a/lib/src/uuid.dart
+++ b/lib/src/uuid.dart
@@ -2,7 +2,9 @@
 // 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.
 
-/// A UUID generator library.
+/**
+ * A UUID generator library.
+ */
 library usage.uuid;
 
 import 'dart:math' show Random;
diff --git a/lib/usage.dart b/lib/usage.dart
index e46e819..bf14930 100644
--- a/lib/usage.dart
+++ b/lib/usage.dart
@@ -49,8 +49,14 @@
     String applicationName,
     String applicationVersion, {
     String analyticsUrl
-  }) => impl.createAnalytics(trackingId, applicationName, applicationVersion,
-    analyticsUrl: analyticsUrl);
+  }) {
+    return impl.createAnalytics(
+      trackingId,
+      applicationName,
+      applicationVersion,
+      analyticsUrl: analyticsUrl
+    );
+  }
 
   /**
    * Tracking ID / Property ID.
@@ -58,17 +64,24 @@
   String get trackingId;
 
   /**
-   * Whether the user has opt-ed in to additional analytics.
+   * Is this the first time the tool has run?
    */
-  bool get optIn;
-
-  set optIn(bool value);
+  bool get firstRun;
 
   /**
-   * Whether the [optIn] value has been explicitly set (either `true` or
-   * `false`).
+   * Whether the [Analytics] instance is configured in an opt-in or opt-out manner.
    */
-  bool get hasSetOptIn;
+  AnalyticsOpt analyticsOpt = AnalyticsOpt.optOut;
+
+  /**
+   * Will analytics data be sent.
+   */
+  bool get enabled;
+
+  /**
+   * Enable or disable sending of analytics data.
+   */
+  set enabled(bool value);
 
   /**
    * Sends a screen view hit to Google Analytics.
@@ -151,6 +164,18 @@
   Future waitForLastPing({Duration timeout});
 }
 
+enum AnalyticsOpt {
+  /**
+   * Users must opt-in before any analytics data is sent.
+   */
+  optIn,
+
+  /**
+   * Users must opt-out for analytics data to not be sent.
+   */
+  optOut
+}
+
 /**
  * An object, returned by [Analytics.startTimer], that is used to measure an
  * asynchronous process.
@@ -198,10 +223,10 @@
   String get trackingId => 'UA-0';
   final bool logCalls;
 
-  bool optIn = false;
-  bool hasSetOptIn = true;
 
-  /// Events are never added to this controller for the mock implementation.
+  /**
+   * Events are never added to this controller for the mock implementation.
+   */
   StreamController<Map<String, dynamic>> _sendController = new StreamController.broadcast();
 
   /**
@@ -210,6 +235,12 @@
    */
   AnalyticsMock([this.logCalls = false]);
 
+  bool get firstRun => false;
+
+  AnalyticsOpt analyticsOpt = AnalyticsOpt.optOut;
+
+  bool enabled = true;
+
   Future sendScreenView(String viewName) =>
       _log('screenView', {'viewName': viewName});
 
diff --git a/pubspec.yaml b/pubspec.yaml
index 015bff3..86fcd50 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -3,7 +3,7 @@
 # BSD-style license that can be found in the LICENSE file.
 
 name: usage
-version: 2.1.0
+version: 2.2.0
 description: A Google Analytics wrapper for both command-line, web, and Flutter apps.
 homepage: https://github.com/dart-lang/usage
 author: Dart Team <misc@dartlang.org>
diff --git a/readme.md b/readme.md
index faeb450..4bbac10 100644
--- a/readme.md
+++ b/readme.md
@@ -49,7 +49,6 @@
 final String UA = ...;
 
 Analytics ga = await Analytics.create(UA, 'ga_test', '1.0');
-ga.optIn = true;
 
 ga.sendScreenView('home');
 ga.sendException('foo exception');
@@ -61,21 +60,17 @@
 
 ## When do we send analytics data?
 
-We use an opt-in method for sending analytics information. There are essentially
-three states for when we send information:
+You can use this library in an opt-in manner or an opt-out one. It defaults to
+opt-out - data will be sent to Google Analytics unless the user explicitly
+opts-out. The mode can be adjusted by changing the value of the
+`Analytics.analyticsOpt` field.
 
-*Sending screen views* If the user has not opted in, the library will only send
-information about screen views. This allows tools to do things like version
-checks, but does not send any additional information.
+*Opt-out* In opt-out mode, if the user does not explicitly opt-out of collecting
+analytics (`Analytics.enabled = false`), the usage library will send usage data.
 
-*Opt-in* If the user opts-in to analytics collection the library sends all
-requested analytics info. This includes screen views, events, timing
-information, and exceptions.
-
-*Opt-ing out* In order to not send analytics information, either do not call the
-analytics methods, or create and use the `AnalyticsMock` class. This provides
-an instance you can use in place of a real analytics object but each analytics
-method is a no-op.
+*Opt-in* In opt-in mode, no data will be sent until the user explicitly opt-in
+to collection (`Analytics.enabled = true`). This includes screen views, events,
+timing information, and exceptions.
 
 ## Other info
 
diff --git a/test/src/common.dart b/test/src/common.dart
index 238ce6d..c016086 100644
--- a/test/src/common.dart
+++ b/test/src/common.dart
@@ -9,8 +9,8 @@
 import 'package:test/test.dart';
 import 'package:usage/src/usage_impl.dart';
 
-AnalyticsImplMock createMock({bool setOptIn: true}) =>
-    new AnalyticsImplMock('UA-0', setOptIn: setOptIn);
+AnalyticsImplMock createMock({ Map<String, dynamic> props }) =>
+  new AnalyticsImplMock('UA-0', props: props);
 
 void was(Map m, String type) => expect(m['t'], type);
 void has(Map m, String key) => expect(m[key], isNotNull);
@@ -20,11 +20,13 @@
   MockProperties get mockProperties => properties;
   MockPostHandler get mockPostHandler => postHandler;
 
-  AnalyticsImplMock(String trackingId, {bool setOptIn: true}) :
-      super(trackingId, new MockProperties(), new MockPostHandler(),
-      applicationName: 'Test App', applicationVersion: '0.1') {
-    if (setOptIn) optIn = true;
-  }
+  AnalyticsImplMock(String trackingId, { Map<String, dynamic> props }) : super(
+    trackingId,
+    new MockProperties(props),
+    new MockPostHandler(),
+    applicationName: 'Test App',
+    applicationVersion: '0.1'
+  );
 
   Map<String, dynamic> get last => mockPostHandler.last;
 }
@@ -32,7 +34,9 @@
 class MockProperties extends PersistentProperties {
   Map<String, dynamic> props = {};
 
-  MockProperties() : super('mock');
+  MockProperties([Map<String, dynamic> props]) : super('mock') {
+    if (props != null) this.props.addAll(props);
+  }
 
   dynamic operator[](String key) => props[key];
 
diff --git a/test/usage_impl_test.dart b/test/usage_impl_test.dart
index b8c0f05..267029c 100644
--- a/test/usage_impl_test.dart
+++ b/test/usage_impl_test.dart
@@ -30,17 +30,17 @@
   group('AnalyticsImpl', () {
     test('respects disabled', () {
       AnalyticsImplMock mock = createMock();
-      mock.optIn = false;
+      mock.enabled = false;
       mock.sendException('FooBar exception');
-      expect(mock.optIn, false);
+      expect(mock.enabled, false);
       expect(mock.mockPostHandler.sentValues, isEmpty);
     });
 
-    test('hasSetOptIn', () {
-      AnalyticsImplMock mock = createMock(setOptIn: false);
-      expect(mock.hasSetOptIn, false);
-      mock.optIn = false;
-      expect(mock.hasSetOptIn, true);
+    test('firstRun', () {
+      AnalyticsImplMock mock = createMock();
+      expect(mock.firstRun, true);
+      mock = createMock(props: {'firstRun': false});
+      expect(mock.firstRun, false);
     });
 
     test('setSessionValue', () {