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 000000000000..6fadda91b380 --- /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 000000000000..c89293372cf3 --- /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 000000000000..1391ffded5ee --- /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 000000000000..253d6d036123 --- /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 androidInfo() { + throw UnimplementedError('androidInfo() has not been implemented.'); + } + + // Gets the iOS device information. + // ignore: public_member_api_docs + Future 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 000000000000..7bd02e97436d --- /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 androidInfo() async { + return AndroidDeviceInfo.fromMap( + (await channel.invokeMethod('getAndroidDeviceInfo')) + .cast(), + ); + } + + // Method channel for iOS devices + Future iosInfo() async { + return IosDeviceInfo.fromMap( + (await channel.invokeMethod('getIosDeviceInfo')).cast(), + ); + } +} 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 000000000000..5b326cc5350a --- /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 supported32BitAbis, + List supported64BitAbis, + List supportedAbis, + this.tags, + this.type, + this.isPhysicalDevice, + this.androidId, + List systemFeatures, + }) : supported32BitAbis = List.unmodifiable(supported32BitAbis), + supported64BitAbis = List.unmodifiable(supported64BitAbis), + supportedAbis = List.unmodifiable(supportedAbis), + systemFeatures = List.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 supported32BitAbis; + + /// An ordered list of 64 bit ABIs supported by this device. + final List supported64BitAbis; + + /// An ordered list of ABIs supported by this device. + final List 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 systemFeatures; + + /// Deserializes from the message received from [_kChannel]. + static AndroidDeviceInfo fromMap(Map map) { + return AndroidDeviceInfo( + version: AndroidBuildVersion._fromMap( + map['version']?.cast() ?? {}), + 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 + static List _fromList(dynamic message) { + final List list = message; + return List.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 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 000000000000..d41202492101 --- /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 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() ?? {}), + ); + } +} + +/// 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 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 000000000000..3adfb93fa27a --- /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 000000000000..1da52e2cf39f --- /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"); + }); + }); +}