[battery_platform_interface] Introduce package. (#2975)

diff --git a/packages/battery/battery_platform_interface/CHANGELOG.md b/packages/battery/battery_platform_interface/CHANGELOG.md
new file mode 100644
index 0000000..6fadda9
--- /dev/null
+++ b/packages/battery/battery_platform_interface/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 1.0.0
+
+- Initial open-source release.
diff --git a/packages/battery/battery_platform_interface/LICENSE b/packages/battery/battery_platform_interface/LICENSE
new file mode 100644
index 0000000..c892933
--- /dev/null
+++ b/packages/battery/battery_platform_interface/LICENSE
@@ -0,0 +1,27 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//    * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/packages/battery/battery_platform_interface/README.md b/packages/battery/battery_platform_interface/README.md
new file mode 100644
index 0000000..e1a4257
--- /dev/null
+++ b/packages/battery/battery_platform_interface/README.md
@@ -0,0 +1,26 @@
+# battery_platform_interface
+
+A common platform interface for the [`battery`][1] plugin.
+
+This interface allows platform-specific implementations of the `battery`
+plugin, as well as the plugin itself, to ensure they are supporting the
+same interface.
+
+# Usage
+
+To implement a new platform-specific implementation of `battery`, extend
+[`BatteryPlatform`][2] with an implementation that performs the
+platform-specific behavior, and when you register your plugin, set the default
+`BatteryPlatform` by calling
+`BatteryPlatform.instance = MyPlatformBattery()`.
+
+# Note on breaking changes
+
+Strongly prefer non-breaking changes (such as adding a method to the interface)
+over breaking changes for this package.
+
+See https://flutter.dev/go/platform-interface-breaking-changes for a discussion
+on why a less-clean interface is preferable to a breaking change.
+
+[1]: ../battery
+[2]: lib/battery_platform_interface.dart
diff --git a/packages/battery/battery_platform_interface/lib/battery_platform_interface.dart b/packages/battery/battery_platform_interface/lib/battery_platform_interface.dart
new file mode 100644
index 0000000..f803c7a
--- /dev/null
+++ b/packages/battery/battery_platform_interface/lib/battery_platform_interface.dart
@@ -0,0 +1,51 @@
+// Copyright 2017 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 'dart:async';
+
+import 'package:plugin_platform_interface/plugin_platform_interface.dart';
+
+import 'method_channel/method_channel_battery.dart';
+import 'enums/battery_state.dart';
+
+export 'enums/battery_state.dart';
+
+/// The interface that implementations of battery must implement.
+///
+/// Platform implementations should extend this class rather than implement it as `battery`
+/// does not consider newly added methods to be breaking changes. Extending this class
+/// (using `extends`) ensures that the subclass will get the default implementation, while
+/// platform implementations that `implements` this interface will be broken by newly added
+/// [BatteryPlatform] methods.
+abstract class BatteryPlatform extends PlatformInterface {
+  /// Constructs a BatteryPlatform.
+  BatteryPlatform() : super(token: _token);
+
+  static final Object _token = Object();
+
+  static BatteryPlatform _instance = MethodChannelBattery();
+
+  /// The default instance of [BatteryPlatform] to use.
+  ///
+  /// Defaults to [MethodChannelBattery].
+  static BatteryPlatform get instance => _instance;
+
+  /// Platform-specific plugins should set this with their own platform-specific
+  /// class that extends [BatteryPlatform] when they register themselves.
+  static set instance(BatteryPlatform instance) {
+    PlatformInterface.verifyToken(instance, _token);
+    _instance = instance;
+  }
+
+  /// Gets the battery level from device.
+  Future<int> batteryLevel() {
+    throw UnimplementedError('batteryLevel() has not been implemented.');
+  }
+
+  /// gets battery state from device.
+  Stream<BatteryState> onBatteryStateChanged() {
+    throw UnimplementedError(
+        'onBatteryStateChanged() has not been implemented.');
+  }
+}
diff --git a/packages/battery/battery_platform_interface/lib/enums/battery_state.dart b/packages/battery/battery_platform_interface/lib/enums/battery_state.dart
new file mode 100644
index 0000000..7dd5e40
--- /dev/null
+++ b/packages/battery/battery_platform_interface/lib/enums/battery_state.dart
@@ -0,0 +1,11 @@
+/// Indicates the current battery state.
+enum BatteryState {
+  /// The battery is completely full of energy.
+  full,
+
+  /// The battery is currently storing energy.
+  charging,
+
+  /// The battery is currently losing energy.
+  discharging
+}
diff --git a/packages/battery/battery_platform_interface/lib/method_channel/method_channel_battery.dart b/packages/battery/battery_platform_interface/lib/method_channel/method_channel_battery.dart
new file mode 100644
index 0000000..4a3365c
--- /dev/null
+++ b/packages/battery/battery_platform_interface/lib/method_channel/method_channel_battery.dart
@@ -0,0 +1,51 @@
+import 'dart:async';
+
+import 'package:flutter/services.dart';
+import 'package:meta/meta.dart';
+
+import 'package:battery_platform_interface/battery_platform_interface.dart';
+
+import '../battery_platform_interface.dart';
+
+/// An implementation of [BatteryPlatform] that uses method channels.
+class MethodChannelBattery extends BatteryPlatform {
+  /// The method channel used to interact with the native platform.
+  @visibleForTesting
+  MethodChannel channel = MethodChannel('plugins.flutter.io/battery');
+
+  /// The event channel used to interact with the native platform.
+  @visibleForTesting
+  EventChannel eventChannel = EventChannel('plugins.flutter.io/charging');
+
+  /// Method channel for getting battery level.
+  Future<int> batteryLevel() async {
+    return (await channel.invokeMethod('getBatteryLevel')).toInt();
+  }
+
+  /// Stream variable for storing battery state.
+  Stream<BatteryState> _onBatteryStateChanged;
+
+  /// Event channel for getting battery change state.
+  Stream<BatteryState> onBatteryStateChanged() {
+    if (_onBatteryStateChanged == null) {
+      _onBatteryStateChanged = eventChannel
+          .receiveBroadcastStream()
+          .map((dynamic event) => _parseBatteryState(event));
+    }
+    return _onBatteryStateChanged;
+  }
+}
+
+/// Method for parsing battery state.
+BatteryState _parseBatteryState(String state) {
+  switch (state) {
+    case 'full':
+      return BatteryState.full;
+    case 'charging':
+      return BatteryState.charging;
+    case 'discharging':
+      return BatteryState.discharging;
+    default:
+      throw ArgumentError('$state is not a valid BatteryState.');
+  }
+}
diff --git a/packages/battery/battery_platform_interface/pubspec.yaml b/packages/battery/battery_platform_interface/pubspec.yaml
new file mode 100644
index 0000000..6c571de
--- /dev/null
+++ b/packages/battery/battery_platform_interface/pubspec.yaml
@@ -0,0 +1,22 @@
+name: battery_platform_interface
+description: A common platform interface for the battery plugin.
+homepage: https://github.com/flutter/plugins/tree/master/packages/battery
+# NOTE: We strongly prefer non-breaking changes, even at the expense of a
+# less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes
+version: 1.0.0
+
+dependencies:
+  flutter:
+    sdk: flutter
+  meta: ^1.1.8
+  plugin_platform_interface: ^1.0.2
+
+dev_dependencies:
+  flutter_test:
+    sdk: flutter
+  mockito: ^4.1.1
+  pedantic: ^1.8.0
+
+environment:
+  sdk: ">=2.7.0 <3.0.0"
+  flutter: ">=1.9.1+hotfix.4 <2.0.0"
diff --git a/packages/battery/battery_platform_interface/test/method_channel_battery_test.dart b/packages/battery/battery_platform_interface/test/method_channel_battery_test.dart
new file mode 100644
index 0000000..65323e4
--- /dev/null
+++ b/packages/battery/battery_platform_interface/test/method_channel_battery_test.dart
@@ -0,0 +1,63 @@
+// Copyright 2017 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 'package:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import 'package:battery_platform_interface/battery_platform_interface.dart';
+
+import 'package:battery_platform_interface/method_channel/method_channel_battery.dart';
+
+void main() {
+  TestWidgetsFlutterBinding.ensureInitialized();
+
+  group("$MethodChannelBattery", () {
+    MethodChannelBattery methodChannelBattery;
+
+    setUp(() async {
+      methodChannelBattery = MethodChannelBattery();
+
+      methodChannelBattery.channel
+          .setMockMethodCallHandler((MethodCall methodCall) async {
+        switch (methodCall.method) {
+          case 'getBatteryLevel':
+            return 90;
+          default:
+            return null;
+        }
+      });
+
+      MethodChannel(methodChannelBattery.eventChannel.name)
+          .setMockMethodCallHandler((MethodCall methodCall) async {
+        switch (methodCall.method) {
+          case 'listen':
+            await ServicesBinding.instance.defaultBinaryMessenger
+                .handlePlatformMessage(
+              methodChannelBattery.eventChannel.name,
+              methodChannelBattery.eventChannel.codec
+                  .encodeSuccessEnvelope('full'),
+              (_) {},
+            );
+            break;
+          case 'cancel':
+          default:
+            return null;
+        }
+      });
+    });
+
+    /// Test for batetry level call.
+    test("getBatteryLevel", () async {
+      final int result = await methodChannelBattery.batteryLevel();
+      expect(result, 90);
+    });
+
+    /// Test for battery changed state call.
+    test("onBatteryChanged", () async {
+      final BatteryState result =
+          await methodChannelBattery.onBatteryStateChanged().first;
+      expect(result, BatteryState.full);
+    });
+  });
+}