[Connectivity] Adds platform interface to connectivity plugin (#2515)

diff --git a/packages/connectivity/connectivity_platform_interface/CHANGELOG.md b/packages/connectivity/connectivity_platform_interface/CHANGELOG.md
new file mode 100644
index 0000000..0d8803f
--- /dev/null
+++ b/packages/connectivity/connectivity_platform_interface/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 1.0.0
+
+* Initial release.
diff --git a/packages/connectivity/connectivity_platform_interface/LICENSE b/packages/connectivity/connectivity_platform_interface/LICENSE
new file mode 100644
index 0000000..0c91662
--- /dev/null
+++ b/packages/connectivity/connectivity_platform_interface/LICENSE
@@ -0,0 +1,27 @@
+// Copyright 2020 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/connectivity/connectivity_platform_interface/README.md b/packages/connectivity/connectivity_platform_interface/README.md
new file mode 100644
index 0000000..ae3be49
--- /dev/null
+++ b/packages/connectivity/connectivity_platform_interface/README.md
@@ -0,0 +1,26 @@
+# connectivity_platform_interface
+
+A common platform interface for the [`connectivity`][1] plugin.
+
+This interface allows platform-specific implementations of the `connectivity`
+plugin, as well as the plugin itself, to ensure they are supporting the
+same interface.
+
+# Usage
+
+To implement a new platform-specific implementation of `connectivity`, extend
+[`ConnectivityPlatform`][2] with an implementation that performs the
+platform-specific behavior, and when you register your plugin, set the default
+`ConnectivityPlatform` by calling
+`ConnectivityPlatform.instance = MyPlatformConnectivity()`.
+
+# 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]: ../connectivity
+[2]: lib/connectivity_platform_interface.dart
diff --git a/packages/connectivity/connectivity_platform_interface/lib/connectivity_platform_interface.dart b/packages/connectivity/connectivity_platform_interface/lib/connectivity_platform_interface.dart
new file mode 100644
index 0000000..b1beb30
--- /dev/null
+++ b/packages/connectivity/connectivity_platform_interface/lib/connectivity_platform_interface.dart
@@ -0,0 +1,72 @@
+// Copyright 2020 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_connectivity.dart';
+
+/// The interface that implementations of connectivity must implement.
+///
+/// Platform implementations should extend this class rather than implement it as `Connectivity`
+/// 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
+/// [ConnectivityPlatform] methods.
+abstract class ConnectivityPlatform extends PlatformInterface {
+  /// Constructs a ConnectivityPlatform.
+  ConnectivityPlatform() : super(token: _token);
+
+  static final Object _token = Object();
+
+  static ConnectivityPlatform _instance = MethodChannelConnectivity();
+
+  /// The default instance of [ConnectivityPlatform] to use.
+  ///
+  /// Defaults to [MethodChannelConnectivity].
+  static ConnectivityPlatform get instance => _instance;
+
+  /// Platform-specific plugins should set this with their own platform-specific
+  /// class that extends [ConnectivityPlatform] when they register themselves.
+  // TODO(amirh): Extract common platform interface logic.
+  // https://github.com/flutter/flutter/issues/43368
+  static set instance(ConnectivityPlatform instance) {
+    PlatformInterface.verifyToken(instance, _token);
+    _instance = instance;
+  }
+
+  /// Checks the connection status of the device.
+  Future<String> checkConnectivity() {
+    throw UnimplementedError('checkConnectivity() has not been implemented.');
+  }
+
+  /// Obtains the wifi name (SSID) of the connected network
+  Future<String> getWifiName() {
+    throw UnimplementedError('getWifiName() has not been implemented.');
+  }
+
+  /// Obtains the wifi BSSID of the connected network.
+  Future<String> getWifiBSSID() {
+    throw UnimplementedError('getWifiBSSID() has not been implemented.');
+  }
+
+  /// Obtains the IP address of the connected wifi network
+  Future<String> getWifiIP() {
+    throw UnimplementedError('getWifiIP() has not been implemented.');
+  }
+
+  /// Request to authorize the location service (Only on iOS).
+  Future<String> requestLocationServiceAuthorization(
+      {bool requestAlwaysLocationUsage = false}) {
+    throw UnimplementedError(
+        'requestLocationServiceAuthorization() has not been implemented.');
+  }
+
+  /// Get the current location service authorization (Only on iOS).
+  Future<String> getLocationServiceAuthorization() {
+    throw UnimplementedError(
+        'getLocationServiceAuthorization() has not been implemented.');
+  }
+}
diff --git a/packages/connectivity/connectivity_platform_interface/lib/method_channel_connectivity.dart b/packages/connectivity/connectivity_platform_interface/lib/method_channel_connectivity.dart
new file mode 100644
index 0000000..0d35b02
--- /dev/null
+++ b/packages/connectivity/connectivity_platform_interface/lib/method_channel_connectivity.dart
@@ -0,0 +1,55 @@
+// Copyright 2020 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:flutter/services.dart';
+import 'package:meta/meta.dart';
+
+import 'connectivity_platform_interface.dart';
+
+/// The method channel used to interact with the native platform.
+@visibleForTesting
+const MethodChannel method_channel =
+    MethodChannel('plugins.flutter.io/connectivity');
+
+/// An implementation of [ConnectivityPlatform] that uses method channels.
+class MethodChannelConnectivity extends ConnectivityPlatform {
+  @override
+  Future<String> checkConnectivity() async {
+    final String result = await method_channel.invokeMethod<String>('check');
+    return result;
+  }
+
+  @override
+  Future<String> getWifiName() async {
+    return method_channel.invokeMethod<String>('wifiName');
+  }
+
+  @override
+  Future<String> getWifiBSSID() async {
+    return await method_channel.invokeMethod<String>('wifiBSSID');
+  }
+
+  @override
+  Future<String> getWifiIP() async {
+    return await method_channel.invokeMethod<String>('wifiIPAddress');
+  }
+
+  @override
+  Future<String> requestLocationServiceAuthorization(
+      {bool requestAlwaysLocationUsage = false}) async {
+    final String result = await method_channel.invokeMethod<String>(
+        'requestLocationServiceAuthorization',
+        <bool>[requestAlwaysLocationUsage]);
+    return result;
+  }
+
+  @override
+  Future<String> getLocationServiceAuthorization() async {
+    final String result = await method_channel
+        .invokeMethod<String>('getLocationServiceAuthorization');
+    return result;
+  }
+}
diff --git a/packages/connectivity/connectivity_platform_interface/pubspec.yaml b/packages/connectivity/connectivity_platform_interface/pubspec.yaml
new file mode 100644
index 0000000..f8f63ec
--- /dev/null
+++ b/packages/connectivity/connectivity_platform_interface/pubspec.yaml
@@ -0,0 +1,21 @@
+name: connectivity_platform_interface
+description: A common platform interface for the connectivity plugin.
+homepage: https://github.com/flutter/plugins/tree/master/packages/connectivity/connectivity_platform_interface
+# 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.0.5
+  plugin_platform_interface: ^1.0.1
+
+dev_dependencies:
+  flutter_test:
+    sdk: flutter
+  mockito: ^4.1.1
+
+environment:
+  sdk: ">=2.0.0-dev.28.0 <3.0.0"
+  flutter: ">=1.10.0 <2.0.0"
diff --git a/packages/connectivity/connectivity_platform_interface/test/method_channel_connectivity_test.dart b/packages/connectivity/connectivity_platform_interface/test/method_channel_connectivity_test.dart
new file mode 100644
index 0000000..d8d9d5b
--- /dev/null
+++ b/packages/connectivity/connectivity_platform_interface/test/method_channel_connectivity_test.dart
@@ -0,0 +1,130 @@
+// Copyright 2020 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:mockito/mockito.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:plugin_platform_interface/plugin_platform_interface.dart';
+
+import 'package:connectivity_platform_interface/method_channel_connectivity.dart';
+import 'package:connectivity_platform_interface/connectivity_platform_interface.dart';
+
+void main() {
+  TestWidgetsFlutterBinding.ensureInitialized();
+
+  group('$ConnectivityPlatform', () {
+    test('$MethodChannelConnectivity() is the default instance', () {
+      expect(ConnectivityPlatform.instance,
+          isInstanceOf<MethodChannelConnectivity>());
+    });
+
+    test('Cannot be implemented with `implements`', () {
+      expect(() {
+        ConnectivityPlatform.instance = ImplementsConnectivityPlatform();
+      }, throwsA(isInstanceOf<AssertionError>()));
+    });
+
+    test('Can be mocked with `implements`', () {
+      final ConnectivityPlatformMock mock = ConnectivityPlatformMock();
+      ConnectivityPlatform.instance = mock;
+    });
+
+    test('Can be extended', () {
+      ConnectivityPlatform.instance = ExtendsConnectivityPlatform();
+    });
+  });
+
+  group('$MethodChannelConnectivity', () {
+    const MethodChannel channel =
+        MethodChannel('plugins.flutter.io/connectivity');
+    final List<MethodCall> log = <MethodCall>[];
+    channel.setMockMethodCallHandler((MethodCall methodCall) async {
+      log.add(methodCall);
+    });
+
+    final MethodChannelConnectivity connectivity = MethodChannelConnectivity();
+
+    tearDown(() {
+      log.clear();
+    });
+
+    test('checkConnectivity', () async {
+      await connectivity.checkConnectivity();
+      expect(
+        log,
+        <Matcher>[isMethodCall('check', arguments: null)],
+      );
+    });
+
+    test('getWifiName', () async {
+      await connectivity.getWifiName();
+      expect(
+        log,
+        <Matcher>[isMethodCall('wifiName', arguments: null)],
+      );
+    });
+
+    test('getWifiBSSID', () async {
+      await connectivity.getWifiBSSID();
+      expect(
+        log,
+        <Matcher>[isMethodCall('wifiBSSID', arguments: null)],
+      );
+    });
+
+    test('getWifiIP', () async {
+      await connectivity.getWifiIP();
+      expect(
+        log,
+        <Matcher>[isMethodCall('wifiIPAddress', arguments: null)],
+      );
+    });
+
+    test(
+        'requestLocationServiceAuthorization requestLocationServiceAuthorization set to false (default)',
+        () async {
+      await connectivity.requestLocationServiceAuthorization();
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('requestLocationServiceAuthorization',
+              arguments: <bool>[false])
+        ],
+      );
+    });
+
+    test(
+        'requestLocationServiceAuthorization requestLocationServiceAuthorization set to true',
+        () async {
+      await connectivity.requestLocationServiceAuthorization(
+          requestAlwaysLocationUsage: true);
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('requestLocationServiceAuthorization',
+              arguments: <bool>[true])
+        ],
+      );
+    });
+
+    test('getLocationServiceAuthorization', () async {
+      await connectivity.getLocationServiceAuthorization();
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('getLocationServiceAuthorization', arguments: null)
+        ],
+      );
+    });
+  });
+}
+
+class ConnectivityPlatformMock extends Mock
+    with MockPlatformInterfaceMixin
+    implements ConnectivityPlatform {}
+
+class ImplementsConnectivityPlatform extends Mock
+    implements ConnectivityPlatform {}
+
+class ExtendsConnectivityPlatform extends ConnectivityPlatform {}