Add battery charging status to Platform Channel example (#9147)

diff --git a/examples/platform_channel/android/app/src/main/java/com/example/platformchannel/MainActivity.java b/examples/platform_channel/android/app/src/main/java/com/example/platformchannel/MainActivity.java
index af0825c..6f10be3 100644
--- a/examples/platform_channel/android/app/src/main/java/com/example/platformchannel/MainActivity.java
+++ b/examples/platform_channel/android/app/src/main/java/com/example/platformchannel/MainActivity.java
@@ -4,6 +4,8 @@
 
 package com.example.platformchannel;
 
+import android.content.BroadcastReceiver;
+import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -13,19 +15,41 @@
 import android.os.Bundle;
 
 import io.flutter.app.FlutterActivity;
+import io.flutter.plugin.common.FlutterEventChannel;
+import io.flutter.plugin.common.FlutterEventChannel.EventSink;
+import io.flutter.plugin.common.FlutterEventChannel.StreamHandler;
 import io.flutter.plugin.common.FlutterMethodChannel;
 import io.flutter.plugin.common.FlutterMethodChannel.MethodCallHandler;
 import io.flutter.plugin.common.FlutterMethodChannel.Response;
 import io.flutter.plugin.common.MethodCall;
 
 public class MainActivity extends FlutterActivity {
-  private static final String CHANNEL = "samples.flutter.io/battery";
+  private static final String BATTERY_CHANNEL = "samples.flutter.io/battery";
+  private static final String CHARGING_CHANNEL = "samples.flutter.io/charging";
 
   @Override
   public void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
 
-    new FlutterMethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
+    new FlutterEventChannel(getFlutterView(), CHARGING_CHANNEL).setStreamHandler(
+        new StreamHandler() {
+          private BroadcastReceiver chargingStateChangeReceiver;
+          @Override
+          public void onListen(Object arguments, EventSink events) {
+            chargingStateChangeReceiver = createChargingStateChangeReceiver(events);
+            registerReceiver(
+                chargingStateChangeReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+          }
+
+          @Override
+          public void onCancel(Object arguments) {
+            unregisterReceiver(chargingStateChangeReceiver);
+            chargingStateChangeReceiver = null;
+          }
+        }
+    );
+
+    new FlutterMethodChannel(getFlutterView(), BATTERY_CHANNEL).setMethodCallHandler(
         new MethodCallHandler() {
           @Override
           public void onMethodCall(MethodCall call, Response response) {
@@ -45,6 +69,23 @@
     );
   }
 
+  private BroadcastReceiver createChargingStateChangeReceiver(final EventSink events) {
+    return new BroadcastReceiver() {
+      @Override
+      public void onReceive(Context context, Intent intent) {
+        int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
+
+        if (status == BatteryManager.BATTERY_STATUS_UNKNOWN) {
+          events.error("UNAVAILABLE", "Charging status unavailable", null);
+        } else {
+          boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
+                               status == BatteryManager.BATTERY_STATUS_FULL;
+          events.success(isCharging ? "charging" : "discharging");
+        }
+      }
+    };
+  }
+
   private int getBatteryLevel() {
     int batteryLevel = -1;
     if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
diff --git a/examples/platform_channel/ios/Runner/AppDelegate.h b/examples/platform_channel/ios/Runner/AppDelegate.h
index d81813b..c3197e9 100644
--- a/examples/platform_channel/ios/Runner/AppDelegate.h
+++ b/examples/platform_channel/ios/Runner/AppDelegate.h
@@ -5,6 +5,6 @@
 #import <Flutter/Flutter.h>
 #import <UIKit/UIKit.h>
 
-@interface AppDelegate : FlutterAppDelegate
+@interface AppDelegate : FlutterAppDelegate<FlutterStreamHandler>
 
 @end
diff --git a/examples/platform_channel/ios/Runner/AppDelegate.m b/examples/platform_channel/ios/Runner/AppDelegate.m
index c74eb73..75f1172 100644
--- a/examples/platform_channel/ios/Runner/AppDelegate.m
+++ b/examples/platform_channel/ios/Runner/AppDelegate.m
@@ -5,11 +5,16 @@
 #import "AppDelegate.h"
 #import <Flutter/Flutter.h>
 
-@implementation AppDelegate
+
+@implementation AppDelegate {
+  FlutterEventReceiver _eventReceiver;
+}
+
 - (BOOL)application:(UIApplication*)application
     didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
   FlutterViewController* controller =
       (FlutterViewController*)self.window.rootViewController;
+
   FlutterMethodChannel* batteryChannel = [FlutterMethodChannel
       methodChannelWithName:@"samples.flutter.io/battery"
             binaryMessenger:controller];
@@ -28,6 +33,11 @@
       result(FlutterMethodNotImplemented);
     }
   }];
+
+  FlutterEventChannel* chargingChannel = [FlutterEventChannel
+      eventChannelWithName:@"samples.flutter.io/charging"
+           binaryMessenger:controller];
+  [chargingChannel setStreamHandler:self];
   return YES;
 }
 
@@ -41,4 +51,41 @@
   }
 }
 
+- (FlutterError*)onListenWithArguments:(id)arguments
+                         eventReceiver:(FlutterEventReceiver)eventReceiver {
+  _eventReceiver = eventReceiver;
+  [[UIDevice currentDevice] setBatteryMonitoringEnabled:YES];
+  [[NSNotificationCenter defaultCenter]
+   addObserver:self
+      selector:@selector(onBatteryStateDidChange:)
+          name:UIDeviceBatteryStateDidChangeNotification
+        object:nil];
+  return nil;
+}
+
+- (void)onBatteryStateDidChange:(NSNotification*)notification {
+  if (!_eventReceiver) return;
+  UIDeviceBatteryState state = [[UIDevice currentDevice] batteryState];
+  switch (state) {
+    case UIDeviceBatteryStateFull:
+    case UIDeviceBatteryStateCharging:
+      _eventReceiver(@"charging");
+      break;
+    case UIDeviceBatteryStateUnplugged:
+      _eventReceiver(@"discharging");
+      break;
+    default:
+      _eventReceiver([FlutterError errorWithCode:@"UNAVAILABLE"
+                                          message:@"Charging status unavailable"
+                                          details:nil]);
+      break;
+  }
+}
+
+- (FlutterError*)onCancelWithArguments:(id)arguments {
+  [[NSNotificationCenter defaultCenter] removeObserver:self];
+  _eventReceiver = nil;
+  return nil;
+}
+
 @end
diff --git a/examples/platform_channel/lib/main.dart b/examples/platform_channel/lib/main.dart
index 6c8c161..19f6738 100644
--- a/examples/platform_channel/lib/main.dart
+++ b/examples/platform_channel/lib/main.dart
@@ -1,4 +1,4 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
+// 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.
 
@@ -13,16 +13,21 @@
 }
 
 class _PlatformChannelState extends State<PlatformChannel> {
-  static const PlatformMethodChannel platform = const PlatformMethodChannel('samples.flutter.io/battery');
-  String _batteryLevel = '';
+  static const PlatformMethodChannel methodChannel =
+      const PlatformMethodChannel('samples.flutter.io/battery');
+  static const PlatformEventChannel eventChannel =
+      const PlatformEventChannel('samples.flutter.io/charging');
+
+  String _batteryLevel = 'Battery level: unknown.';
+  String _chargingStatus = 'Battery status: unknown.';
 
   Future<Null> _getBatteryLevel() async {
     String batteryLevel;
     try {
-      final int result = await platform.invokeMethod('getBatteryLevel');
-      batteryLevel = 'Battery level at $result % .';
-    } on PlatformException catch (e) {
-      batteryLevel = "Failed to get battery level: '${e.message}'.";
+      final int result = await methodChannel.invokeMethod('getBatteryLevel');
+      batteryLevel = 'Battery level: $result%.';
+    } on PlatformException {
+      batteryLevel = "Failed to get battery level.";
     }
     setState(() {
       _batteryLevel = batteryLevel;
@@ -30,19 +35,45 @@
   }
 
   @override
+  void initState() {
+    super.initState();
+    eventChannel.receiveBroadcastStream().listen(_onEvent, onError: _onError);
+  }
+
+  void _onEvent(String event) {
+    setState(() {
+      _chargingStatus =
+          "Battery status: ${event == 'charging' ? '' : 'dis'}charging.";
+    });
+  }
+
+  void _onError(PlatformException error) {
+    setState(() {
+      _chargingStatus = "Battery status: unknown.";
+    });
+  }
+
+  @override
   Widget build(BuildContext context) {
     return new Material(
-      child: new Center(
-        child: new Column(
-          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
-          children: <Widget>[
-            new RaisedButton(
-              child: new Text('Get Battery Level'),
-              onPressed: _getBatteryLevel,
-            ),
-            new Text(_batteryLevel, key: new Key('Battery level label')),
-          ],
-        ),
+      child: new Column(
+        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+        children: <Widget>[
+          new Column(
+            mainAxisAlignment: MainAxisAlignment.center,
+            children: <Widget>[
+              new Text(_batteryLevel, key: new Key('Battery level label')),
+              new Padding(
+                padding: new EdgeInsets.all(16.0),
+                child: new RaisedButton(
+                  child: new Text('Refresh'),
+                  onPressed: _getBatteryLevel,
+                ),
+              ),
+            ],
+          ),
+          new Text(_chargingStatus),
+        ],
       ),
     );
   }
diff --git a/examples/platform_channel/test_driver/button_tap_test.dart b/examples/platform_channel/test_driver/button_tap_test.dart
index cf6601e..67ea6b8 100644
--- a/examples/platform_channel/test_driver/button_tap_test.dart
+++ b/examples/platform_channel/test_driver/button_tap_test.dart
@@ -14,23 +14,24 @@
     });
 
     tearDownAll(() async {
-      if (driver != null)
-        driver.close();
+      if (driver != null) driver.close();
     });
 
     test('tap on the button, verify result', () async {
-        final SerializableFinder batteryLevelLabel = find.byValueKey('Battery level label');
-        expect(batteryLevelLabel, isNotNull);
+      final SerializableFinder batteryLevelLabel =
+          find.byValueKey('Battery level label');
+      expect(batteryLevelLabel, isNotNull);
 
-        final SerializableFinder button = find.text('Get Battery Level');
-        await driver.waitFor(button);
-        await driver.tap(button);
+      final SerializableFinder button = find.text('Refresh');
+      await driver.waitFor(button);
+      await driver.tap(button);
 
-        String batteryLevel;
-        while(batteryLevel == null || batteryLevel.isEmpty) {
-          batteryLevel = await driver.getText(batteryLevelLabel);
-        }
-        expect(batteryLevel, isNotEmpty);
+      String batteryLevel;
+      while (batteryLevel == null || batteryLevel.contains('unknown')) {
+        batteryLevel = await driver.getText(batteryLevelLabel);
+      }
+
+      expect(batteryLevel.contains('%'), isTrue);
     });
   });
-}
\ No newline at end of file
+}