[sensors] Documentation and test improvements (#2272)

- Add missing dartdoc coverage and expand on existing documentation.
-  Add an analyzer warning for this package so that public doc coverage
  doesn't regress.
- Improve unit test coverage.
- Expand on the existing README.
diff --git a/packages/sensors/CHANGELOG.md b/packages/sensors/CHANGELOG.md
index ea48858..aecf8d4 100644
--- a/packages/sensors/CHANGELOG.md
+++ b/packages/sensors/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.4.1+3
+
+* Improve documentation and add unit test coverage.
+
 ## 0.4.1+2
 
 * Remove AndroidX warnings.
diff --git a/packages/sensors/README.md b/packages/sensors/README.md
index fdd4502..4bb9f4f 100644
--- a/packages/sensors/README.md
+++ b/packages/sensors/README.md
@@ -5,7 +5,22 @@
 
 ## Usage
 
-To use this plugin, add `sensors` as a [dependency in your pubspec.yaml file](https://flutter.io/platform-plugins/).
+To use this plugin, add `sensors` as a [dependency in your pubspec.yaml
+file](https://flutter.io/platform-plugins/).
+
+This will expose three classes of sensor events, through three different
+streams.
+
+- `AccelerometerEvent`s describe the velocity of the device, including the
+  effects of gravity. Put simply, you can use accelerometer readings to tell if
+  the device is moving in a particular direction.
+- `UserAccelerometerEvent`s also describe the velocity of the device, but don't
+  include gravity. They can also be thought of as just the user's affect on the
+  device.
+- `GyroscopeEvent`s describe the rotation of the device.
+
+Each of these is exposed through a `BroadcastStream`: `accelerometerEvents`,
+`userAccelerometerEvents`, and `gyroscopeEvents`, respectively.
 
 
 ### Example
@@ -14,10 +29,21 @@
 import 'package:sensors/sensors.dart';
 
 accelerometerEvents.listen((AccelerometerEvent event) {
- // Do something with the event.
+  print(event);
 });
+// [AccelerometerEvent (x: 0.0, y: 9.8, z: 0.0)]
+
+userAccelerometerEvents.listen((AccelerometerEvent event) {
+  print(event);
+});
+// [UserAccelerometerEvent (x: 0.0, y: 0.0, z: 0.0)]
 
 gyroscopeEvents.listen((GyroscopeEvent event) {
- // Do something with the event.
+  print(event);
 });
-```
\ No newline at end of file
+// [GyroscopeEvent (x: 0.0, y: 0.0, z: 0.0)]
+
+```
+
+Also see the `example` subdirectory for an example application that uses the
+sensor data.
diff --git a/packages/sensors/analysis_options.yaml b/packages/sensors/analysis_options.yaml
new file mode 100644
index 0000000..4d3c53a
--- /dev/null
+++ b/packages/sensors/analysis_options.yaml
@@ -0,0 +1,11 @@
+# This exists to add a lint for missing API docs just on this specific package,
+# since not all packages have coverage for all their public members yet and
+# adding it in would be non-trivial. `public_member_api_docs` should be applied
+# to new packages going forward, and ideally the main `analysis_options.yaml`
+# file as soon as possible.
+
+include: ../../analysis_options.yaml
+
+linter:
+  rules:
+    - public_member_api_docs
diff --git a/packages/sensors/example/lib/main.dart b/packages/sensors/example/lib/main.dart
index e574a64..575e049 100644
--- a/packages/sensors/example/lib/main.dart
+++ b/packages/sensors/example/lib/main.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// ignore_for_file: public_member_api_docs
+
 import 'dart:async';
 import 'package:flutter/material.dart';
 import 'package:sensors/sensors.dart';
diff --git a/packages/sensors/example/lib/snake.dart b/packages/sensors/example/lib/snake.dart
index b870791..2b7cad4 100644
--- a/packages/sensors/example/lib/snake.dart
+++ b/packages/sensors/example/lib/snake.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// ignore_for_file: public_member_api_docs
+
 import 'dart:async';
 import 'dart:math' as math;
 
diff --git a/packages/sensors/lib/sensors.dart b/packages/sensors/lib/sensors.dart
index 7b41a49..0b6f1b5 100644
--- a/packages/sensors/lib/sensors.dart
+++ b/packages/sensors/lib/sensors.dart
@@ -14,48 +14,95 @@
 const EventChannel _gyroscopeEventChannel =
     EventChannel('plugins.flutter.io/sensors/gyroscope');
 
+/// Discrete reading from an accelerometer. Accelerometers measure the velocity
+/// of the device. Note that these readings include the effects of gravity. Put
+/// simply, you can use accelerometer readings to tell if the device is moving in
+/// a particular direction.
 class AccelerometerEvent {
+  /// Contructs an instance with the given [x], [y], and [z] values.
   AccelerometerEvent(this.x, this.y, this.z);
 
   /// Acceleration force along the x axis (including gravity) measured in m/s^2.
+  ///
+  /// When the device is held upright facing the user, positive values mean the
+  /// device is moving to the right and negative mean it is moving to the left.
   final double x;
 
   /// Acceleration force along the y axis (including gravity) measured in m/s^2.
+  ///
+  /// When the device is held upright facing the user, positive values mean the
+  /// device is moving towards the sky and negative mean it is moving towards
+  /// the ground.
   final double y;
 
   /// Acceleration force along the z axis (including gravity) measured in m/s^2.
+  ///
+  /// This uses a right-handed coordinate system. So when the device is held
+  /// upright and facing the user, positive values mean the device is moving
+  /// towards the user and negative mean it is moving away from them.
   final double z;
 
   @override
   String toString() => '[AccelerometerEvent (x: $x, y: $y, z: $z)]';
 }
 
+/// Discrete reading from a gyroscope. Gyroscopes measure the rate or rotation of
+/// the device in 3D space.
 class GyroscopeEvent {
+  /// Contructs an instance with the given [x], [y], and [z] values.
   GyroscopeEvent(this.x, this.y, this.z);
 
   /// Rate of rotation around the x axis measured in rad/s.
+  ///
+  /// When the device is held upright, this can also be thought of as describing
+  /// "pitch". The top of the device will tilt towards or away from the
+  /// user as this value changes.
   final double x;
 
   /// Rate of rotation around the y axis measured in rad/s.
+  ///
+  /// When the device is held upright, this can also be thought of as describing
+  /// "yaw". The lengthwise edge of the device will rotate towards or away from
+  /// the user as this value changes.
   final double y;
 
   /// Rate of rotation around the z axis measured in rad/s.
+  ///
+  /// When the device is held upright, this can also be thought of as describing
+  /// "roll". When this changes the face of the device should remain facing
+  /// forward, but the orientation will change from portrait to landscape and so
+  /// on.
   final double z;
 
   @override
   String toString() => '[GyroscopeEvent (x: $x, y: $y, z: $z)]';
 }
 
+/// Like [AccelerometerEvent], this is a discrete reading from an accelerometer
+/// and measures the velocity of the device. However, unlike
+/// [AccelerometerEvent], this event does not include the effects of gravity.
 class UserAccelerometerEvent {
+  /// Contructs an instance with the given [x], [y], and [z] values.
   UserAccelerometerEvent(this.x, this.y, this.z);
 
   /// Acceleration force along the x axis (excluding gravity) measured in m/s^2.
+  ///
+  /// When the device is held upright facing the user, positive values mean the
+  /// device is moving to the right and negative mean it is moving to the left.
   final double x;
 
   /// Acceleration force along the y axis (excluding gravity) measured in m/s^2.
+  ///
+  /// When the device is held upright facing the user, positive values mean the
+  /// device is moving towards the sky and negative mean it is moving towards
+  /// the ground.
   final double y;
 
   /// Acceleration force along the z axis (excluding gravity) measured in m/s^2.
+  ///
+  /// This uses a right-handed coordinate system. So when the device is held
+  /// upright and facing the user, positive values mean the device is moving
+  /// towards the user and negative mean it is moving away from them.
   final double z;
 
   @override
diff --git a/packages/sensors/pubspec.yaml b/packages/sensors/pubspec.yaml
index 38e6982..b750d93 100644
--- a/packages/sensors/pubspec.yaml
+++ b/packages/sensors/pubspec.yaml
@@ -3,7 +3,7 @@
   gyroscope sensors.
 author: Flutter Team <flutter-dev@googlegroups.com>
 homepage: https://github.com/flutter/plugins/tree/master/packages/sensors
-version: 0.4.1+2
+version: 0.4.1+3
 
 flutter:
   plugin:
@@ -20,6 +20,7 @@
   flutter_test:
     sdk: flutter
   e2e: ^0.2.0
+  mockito: ^4.1.1
 
 environment:
   sdk: ">=2.0.0-dev.28.0 <3.0.0"
diff --git a/packages/sensors/test/sensors_test.dart b/packages/sensors/test/sensors_test.dart
index 603f805..1485d58 100644
--- a/packages/sensors/test/sensors_test.dart
+++ b/packages/sensors/test/sensors_test.dart
@@ -2,7 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:async';
 import 'dart:typed_data';
 
 import 'package:flutter/services.dart';
@@ -16,44 +15,67 @@
   test('$accelerometerEvents are streamed', () async {
     const String channelName = 'plugins.flutter.io/sensors/accelerometer';
     const List<double> sensorData = <double>[1.0, 2.0, 3.0];
+    _initializeFakeSensorChannel(channelName, sensorData);
 
-    const StandardMethodCodec standardMethod = StandardMethodCodec();
+    final AccelerometerEvent event = await accelerometerEvents.first;
 
-    void emitEvent(ByteData event) {
-      // TODO(hterkelsen): Remove this when defaultBinaryMessages is in stable.
-      // https://github.com/flutter/flutter/issues/33446
-      // ignore: deprecated_member_use
-      BinaryMessages.handlePlatformMessage(
-        channelName,
-        event,
-        (ByteData reply) {},
-      );
-    }
+    expect(event.x, sensorData[0]);
+    expect(event.y, sensorData[1]);
+    expect(event.z, sensorData[2]);
+  });
 
-    bool isCanceled = false;
+  test('$gyroscopeEvents are streamed', () async {
+    const String channelName = 'plugins.flutter.io/sensors/gyroscope';
+    const List<double> sensorData = <double>[3.0, 4.0, 5.0];
+    _initializeFakeSensorChannel(channelName, sensorData);
+
+    final GyroscopeEvent event = await gyroscopeEvents.first;
+
+    expect(event.x, sensorData[0]);
+    expect(event.y, sensorData[1]);
+    expect(event.z, sensorData[2]);
+  });
+
+  test('$userAccelerometerEvents are streamed', () async {
+    const String channelName = 'plugins.flutter.io/sensors/user_accel';
+    const List<double> sensorData = <double>[6.0, 7.0, 8.0];
+    _initializeFakeSensorChannel(channelName, sensorData);
+
+    final UserAccelerometerEvent event = await userAccelerometerEvents.first;
+
+    expect(event.x, sensorData[0]);
+    expect(event.y, sensorData[1]);
+    expect(event.z, sensorData[2]);
+  });
+}
+
+void _initializeFakeSensorChannel(String channelName, List<double> sensorData) {
+  const StandardMethodCodec standardMethod = StandardMethodCodec();
+
+  void _emitEvent(ByteData event) {
     // TODO(hterkelsen): Remove this when defaultBinaryMessages is in stable.
     // https://github.com/flutter/flutter/issues/33446
     // ignore: deprecated_member_use
-    BinaryMessages.setMockMessageHandler(channelName, (ByteData message) async {
-      final MethodCall methodCall = standardMethod.decodeMethodCall(message);
-      if (methodCall.method == 'listen') {
-        emitEvent(standardMethod.encodeSuccessEnvelope(sensorData));
-        emitEvent(null);
-        return standardMethod.encodeSuccessEnvelope(null);
-      } else if (methodCall.method == 'cancel') {
-        isCanceled = true;
-        return standardMethod.encodeSuccessEnvelope(null);
-      } else {
-        fail('Expected listen or cancel');
-      }
-    });
+    BinaryMessages.handlePlatformMessage(
+      channelName,
+      event,
+      (ByteData reply) {},
+    );
+  }
 
-    final AccelerometerEvent event = await accelerometerEvents.first;
-    expect(event.x, 1.0);
-    expect(event.y, 2.0);
-    expect(event.z, 3.0);
-
-    await Future<void>.delayed(Duration.zero);
-    expect(isCanceled, isTrue);
+  // TODO(hterkelsen): Remove this when defaultBinaryMessages is in stable.
+  // https://github.com/flutter/flutter/issues/33446
+  // ignore: deprecated_member_use
+  BinaryMessages.setMockMessageHandler(channelName, (ByteData message) async {
+    final MethodCall methodCall = standardMethod.decodeMethodCall(message);
+    if (methodCall.method == 'listen') {
+      _emitEvent(standardMethod.encodeSuccessEnvelope(sensorData));
+      _emitEvent(null);
+      return standardMethod.encodeSuccessEnvelope(null);
+    } else if (methodCall.method == 'cancel') {
+      return standardMethod.encodeSuccessEnvelope(null);
+    } else {
+      fail('Expected listen or cancel');
+    }
   });
 }