[device_info_platform_interface] Introduce package (#2929)

This is the platform interface package of the federated device_info plugin.
diff --git a/packages/device_info/device_info_platform_interface/CHANGELOG.md b/packages/device_info/device_info_platform_interface/CHANGELOG.md
new file mode 100644
index 0000000..6fadda9
--- /dev/null
+++ b/packages/device_info/device_info_platform_interface/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 1.0.0
+
+- Initial open-source release.
diff --git a/packages/device_info/device_info_platform_interface/LICENSE b/packages/device_info/device_info_platform_interface/LICENSE
new file mode 100644
index 0000000..c892933
--- /dev/null
+++ b/packages/device_info/device_info_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/device_info/device_info_platform_interface/README.md b/packages/device_info/device_info_platform_interface/README.md
new file mode 100644
index 0000000..1391ffd
--- /dev/null
+++ b/packages/device_info/device_info_platform_interface/README.md
@@ -0,0 +1,26 @@
+# device_info_platform_interface
+
+A common platform interface for the [`device_info`][1] plugin.
+
+This interface allows platform-specific implementations of the `device_info`
+plugin, as well as the plugin itself, to ensure they are supporting the
+same interface.
+
+# Usage
+
+To implement a new platform-specific implementation of `device_info`, extend
+[`DeviceInfoPlatform`][2] with an implementation that performs the
+platform-specific behavior, and when you register your plugin, set the default
+`DeviceInfoPlatform` by calling
+`DeviceInfoPlatform.instance = MyPlatformDeviceInfo()`.
+
+# 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]: ../device_info
+[2]: lib/device_info_platform_interface.dart
diff --git a/packages/device_info/device_info_platform_interface/lib/device_info_platform_interface.dart b/packages/device_info/device_info_platform_interface/lib/device_info_platform_interface.dart
new file mode 100644
index 0000000..253d6d0
--- /dev/null
+++ b/packages/device_info/device_info_platform_interface/lib/device_info_platform_interface.dart
@@ -0,0 +1,55 @@
+// 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_device_info.dart';
+
+import 'model/android_device_info.dart';
+import 'model/ios_device_info.dart';
+
+export 'model/android_device_info.dart';
+export 'model/ios_device_info.dart';
+
+/// The interface that implementations of device_info must implement.
+///
+/// Platform implementations should extend this class rather than implement it as `device_info`
+/// 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
+/// [DeviceInfoPlatform] methods.
+abstract class DeviceInfoPlatform extends PlatformInterface {
+  /// Constructs a UrlLauncherPlatform.
+  DeviceInfoPlatform() : super(token: _token);
+
+  static final Object _token = Object();
+
+  static DeviceInfoPlatform _instance = MethodChannelDeviceInfo();
+
+  /// The default instance of [DeviceInfoPlatform] to use.
+  ///
+  /// Defaults to [MethodChannelDeviceInfo].
+  static DeviceInfoPlatform get instance => _instance;
+
+  /// Platform-specific plugins should set this with their own platform-specific
+  /// class that extends [DeviceInfoPlatform] when they register themselves.
+  static set instance(DeviceInfoPlatform instance) {
+    PlatformInterface.verifyToken(instance, _token);
+    _instance = instance;
+  }
+
+  // Gets the Android device information.
+  // ignore: public_member_api_docs
+  Future<AndroidDeviceInfo> androidInfo() {
+    throw UnimplementedError('androidInfo() has not been implemented.');
+  }
+
+  // Gets the iOS device information.
+  // ignore: public_member_api_docs
+  Future<IosDeviceInfo> iosInfo() {
+    throw UnimplementedError('iosInfo() has not been implemented.');
+  }
+}
diff --git a/packages/device_info/device_info_platform_interface/lib/method_channel/method_channel_device_info.dart b/packages/device_info/device_info_platform_interface/lib/method_channel/method_channel_device_info.dart
new file mode 100644
index 0000000..7bd02e9
--- /dev/null
+++ b/packages/device_info/device_info_platform_interface/lib/method_channel/method_channel_device_info.dart
@@ -0,0 +1,28 @@
+import 'dart:async';
+
+import 'package:flutter/services.dart';
+import 'package:meta/meta.dart';
+
+import 'package:device_info_platform_interface/device_info_platform_interface.dart';
+
+/// An implementation of [DeviceInfoPlatform] that uses method channels.
+class MethodChannelDeviceInfo extends DeviceInfoPlatform {
+  /// The method channel used to interact with the native platform.
+  @visibleForTesting
+  MethodChannel channel = MethodChannel('plugins.flutter.io/device_info');
+
+  // Method channel for Android devices
+  Future<AndroidDeviceInfo> androidInfo() async {
+    return AndroidDeviceInfo.fromMap(
+      (await channel.invokeMethod('getAndroidDeviceInfo'))
+          .cast<String, dynamic>(),
+    );
+  }
+
+  // Method channel for iOS devices
+  Future<IosDeviceInfo> iosInfo() async {
+    return IosDeviceInfo.fromMap(
+      (await channel.invokeMethod('getIosDeviceInfo')).cast<String, dynamic>(),
+    );
+  }
+}
diff --git a/packages/device_info/device_info_platform_interface/lib/model/android_device_info.dart b/packages/device_info/device_info_platform_interface/lib/model/android_device_info.dart
new file mode 100644
index 0000000..5b326cc
--- /dev/null
+++ b/packages/device_info/device_info_platform_interface/lib/model/android_device_info.dart
@@ -0,0 +1,198 @@
+// 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.
+
+/// Information derived from `android.os.Build`.
+///
+/// See: https://developer.android.com/reference/android/os/Build.html
+class AndroidDeviceInfo {
+  /// Android device Info class.
+  AndroidDeviceInfo({
+    this.version,
+    this.board,
+    this.bootloader,
+    this.brand,
+    this.device,
+    this.display,
+    this.fingerprint,
+    this.hardware,
+    this.host,
+    this.id,
+    this.manufacturer,
+    this.model,
+    this.product,
+    List<String> supported32BitAbis,
+    List<String> supported64BitAbis,
+    List<String> supportedAbis,
+    this.tags,
+    this.type,
+    this.isPhysicalDevice,
+    this.androidId,
+    List<String> systemFeatures,
+  })  : supported32BitAbis = List<String>.unmodifiable(supported32BitAbis),
+        supported64BitAbis = List<String>.unmodifiable(supported64BitAbis),
+        supportedAbis = List<String>.unmodifiable(supportedAbis),
+        systemFeatures = List<String>.unmodifiable(systemFeatures);
+
+  /// Android operating system version values derived from `android.os.Build.VERSION`.
+  final AndroidBuildVersion version;
+
+  /// The name of the underlying board, like "goldfish".
+  final String board;
+
+  /// The system bootloader version number.
+  final String bootloader;
+
+  /// The consumer-visible brand with which the product/hardware will be associated, if any.
+  final String brand;
+
+  /// The name of the industrial design.
+  final String device;
+
+  /// A build ID string meant for displaying to the user.
+  final String display;
+
+  /// A string that uniquely identifies this build.
+  final String fingerprint;
+
+  /// The name of the hardware (from the kernel command line or /proc).
+  final String hardware;
+
+  /// Hostname.
+  final String host;
+
+  /// Either a changelist number, or a label like "M4-rc20".
+  final String id;
+
+  /// The manufacturer of the product/hardware.
+  final String manufacturer;
+
+  /// The end-user-visible name for the end product.
+  final String model;
+
+  /// The name of the overall product.
+  final String product;
+
+  /// An ordered list of 32 bit ABIs supported by this device.
+  final List<String> supported32BitAbis;
+
+  /// An ordered list of 64 bit ABIs supported by this device.
+  final List<String> supported64BitAbis;
+
+  /// An ordered list of ABIs supported by this device.
+  final List<String> supportedAbis;
+
+  /// Comma-separated tags describing the build, like "unsigned,debug".
+  final String tags;
+
+  /// The type of build, like "user" or "eng".
+  final String type;
+
+  /// `false` if the application is running in an emulator, `true` otherwise.
+  final bool isPhysicalDevice;
+
+  /// The Android hardware device ID that is unique between the device + user and app signing.
+  final String androidId;
+
+  /// Describes what features are available on the current device.
+  ///
+  /// This can be used to check if the device has, for example, a front-facing
+  /// camera, or a touchscreen. However, in many cases this is not the best
+  /// API to use. For example, if you are interested in bluetooth, this API
+  /// can tell you if the device has a bluetooth radio, but it cannot tell you
+  /// if bluetooth is currently enabled, or if you have been granted the
+  /// necessary permissions to use it. Please *only* use this if there is no
+  /// other way to determine if a feature is supported.
+  ///
+  /// This data comes from Android's PackageManager.getSystemAvailableFeatures,
+  /// and many of the common feature strings to look for are available in
+  /// PackageManager's public documentation:
+  /// https://developer.android.com/reference/android/content/pm/PackageManager
+  final List<String> systemFeatures;
+
+  /// Deserializes from the message received from [_kChannel].
+  static AndroidDeviceInfo fromMap(Map<String, dynamic> map) {
+    return AndroidDeviceInfo(
+      version: AndroidBuildVersion._fromMap(
+          map['version']?.cast<String, dynamic>() ?? {}),
+      board: map['board'],
+      bootloader: map['bootloader'],
+      brand: map['brand'],
+      device: map['device'],
+      display: map['display'],
+      fingerprint: map['fingerprint'],
+      hardware: map['hardware'],
+      host: map['host'],
+      id: map['id'],
+      manufacturer: map['manufacturer'],
+      model: map['model'],
+      product: map['product'],
+      supported32BitAbis: _fromList(map['supported32BitAbis'] ?? []),
+      supported64BitAbis: _fromList(map['supported64BitAbis'] ?? []),
+      supportedAbis: _fromList(map['supportedAbis'] ?? []),
+      tags: map['tags'],
+      type: map['type'],
+      isPhysicalDevice: map['isPhysicalDevice'],
+      androidId: map['androidId'],
+      systemFeatures: _fromList(map['systemFeatures'] ?? []),
+    );
+  }
+
+  /// Deserializes message as List<String>
+  static List<String> _fromList(dynamic message) {
+    final List<dynamic> list = message;
+    return List<String>.from(list);
+  }
+}
+
+/// Version values of the current Android operating system build derived from
+/// `android.os.Build.VERSION`.
+///
+/// See: https://developer.android.com/reference/android/os/Build.VERSION.html
+class AndroidBuildVersion {
+  AndroidBuildVersion._({
+    this.baseOS,
+    this.codename,
+    this.incremental,
+    this.previewSdkInt,
+    this.release,
+    this.sdkInt,
+    this.securityPatch,
+  });
+
+  /// The base OS build the product is based on.
+  final String baseOS;
+
+  /// The current development codename, or the string "REL" if this is a release build.
+  final String codename;
+
+  /// The internal value used by the underlying source control to represent this build.
+  final String incremental;
+
+  /// The developer preview revision of a prerelease SDK.
+  final int previewSdkInt;
+
+  /// The user-visible version string.
+  final String release;
+
+  /// The user-visible SDK version of the framework.
+  ///
+  /// Possible values are defined in: https://developer.android.com/reference/android/os/Build.VERSION_CODES.html
+  final int sdkInt;
+
+  /// The user-visible security patch level.
+  final String securityPatch;
+
+  /// Deserializes from the map message received from [_kChannel].
+  static AndroidBuildVersion _fromMap(Map<String, dynamic> map) {
+    return AndroidBuildVersion._(
+      baseOS: map['baseOS'],
+      codename: map['codename'],
+      incremental: map['incremental'],
+      previewSdkInt: map['previewSdkInt'],
+      release: map['release'],
+      sdkInt: map['sdkInt'],
+      securityPatch: map['securityPatch'],
+    );
+  }
+}
diff --git a/packages/device_info/device_info_platform_interface/lib/model/ios_device_info.dart b/packages/device_info/device_info_platform_interface/lib/model/ios_device_info.dart
new file mode 100644
index 0000000..d412024
--- /dev/null
+++ b/packages/device_info/device_info_platform_interface/lib/model/ios_device_info.dart
@@ -0,0 +1,97 @@
+// 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.
+
+/// Information derived from `UIDevice`.
+///
+/// See: https://developer.apple.com/documentation/uikit/uidevice
+class IosDeviceInfo {
+  /// IOS device info class.
+  IosDeviceInfo({
+    this.name,
+    this.systemName,
+    this.systemVersion,
+    this.model,
+    this.localizedModel,
+    this.identifierForVendor,
+    this.isPhysicalDevice,
+    this.utsname,
+  });
+
+  /// Device name.
+  final String name;
+
+  /// The name of the current operating system.
+  final String systemName;
+
+  /// The current operating system version.
+  final String systemVersion;
+
+  /// Device model.
+  final String model;
+
+  /// Localized name of the device model.
+  final String localizedModel;
+
+  /// Unique UUID value identifying the current device.
+  final String identifierForVendor;
+
+  /// `false` if the application is running in a simulator, `true` otherwise.
+  final bool isPhysicalDevice;
+
+  /// Operating system information derived from `sys/utsname.h`.
+  final IosUtsname utsname;
+
+  /// Deserializes from the map message received from [_kChannel].
+  static IosDeviceInfo fromMap(Map<String, dynamic> map) {
+    return IosDeviceInfo(
+      name: map['name'],
+      systemName: map['systemName'],
+      systemVersion: map['systemVersion'],
+      model: map['model'],
+      localizedModel: map['localizedModel'],
+      identifierForVendor: map['identifierForVendor'],
+      isPhysicalDevice: map['isPhysicalDevice'] == 'true',
+      utsname:
+          IosUtsname._fromMap(map['utsname']?.cast<String, dynamic>() ?? {}),
+    );
+  }
+}
+
+/// Information derived from `utsname`.
+/// See http://pubs.opengroup.org/onlinepubs/7908799/xsh/sysutsname.h.html for details.
+class IosUtsname {
+  IosUtsname._({
+    this.sysname,
+    this.nodename,
+    this.release,
+    this.version,
+    this.machine,
+  });
+
+  /// Operating system name.
+  final String sysname;
+
+  /// Network node name.
+  final String nodename;
+
+  /// Release level.
+  final String release;
+
+  /// Version level.
+  final String version;
+
+  /// Hardware type (e.g. 'iPhone7,1' for iPhone 6 Plus).
+  final String machine;
+
+  /// Deserializes from the map message received from [_kChannel].
+  static IosUtsname _fromMap(Map<String, dynamic> map) {
+    return IosUtsname._(
+      sysname: map['sysname'],
+      nodename: map['nodename'],
+      release: map['release'],
+      version: map['version'],
+      machine: map['machine'],
+    );
+  }
+}
diff --git a/packages/device_info/device_info_platform_interface/pubspec.yaml b/packages/device_info/device_info_platform_interface/pubspec.yaml
new file mode 100644
index 0000000..3adfb93
--- /dev/null
+++ b/packages/device_info/device_info_platform_interface/pubspec.yaml
@@ -0,0 +1,22 @@
+name: device_info_platform_interface
+description: A common platform interface for the device_info plugin.
+homepage: https://github.com/flutter/plugins/tree/master/packages/device_info
+# 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/device_info/device_info_platform_interface/test/method_channel_device_info_test.dart b/packages/device_info/device_info_platform_interface/test/method_channel_device_info_test.dart
new file mode 100644
index 0000000..1da52e2
--- /dev/null
+++ b/packages/device_info/device_info_platform_interface/test/method_channel_device_info_test.dart
@@ -0,0 +1,49 @@
+// 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:device_info_platform_interface/device_info_platform_interface.dart';
+
+import 'package:device_info_platform_interface/method_channel/method_channel_device_info.dart';
+
+void main() {
+  TestWidgetsFlutterBinding.ensureInitialized();
+
+  group("$MethodChannelDeviceInfo", () {
+    MethodChannelDeviceInfo methodChannelDeviceInfo;
+
+    setUp(() async {
+      methodChannelDeviceInfo = MethodChannelDeviceInfo();
+
+      methodChannelDeviceInfo.channel
+          .setMockMethodCallHandler((MethodCall methodCall) async {
+        switch (methodCall.method) {
+          case 'getAndroidDeviceInfo':
+            return ({
+              "brand": "Google",
+            });
+          case 'getIosDeviceInfo':
+            return ({
+              "name": "iPhone 10",
+            });
+          default:
+            return null;
+        }
+      });
+    });
+
+    test("androidInfo", () async {
+      final AndroidDeviceInfo result =
+          await methodChannelDeviceInfo.androidInfo();
+      expect(result.brand, "Google");
+    });
+
+    test("iosInfo", () async {
+      final IosDeviceInfo result = await methodChannelDeviceInfo.iosInfo();
+      expect(result.name, "iPhone 10");
+    });
+  });
+}