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 index 808b7adf9dc7..2dd41dcc580f 100644 --- 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 @@ -7,10 +7,8 @@ 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'; 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 index 7bd02e97436d..331f718989ce 100644 --- 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 @@ -1,8 +1,11 @@ +// 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: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. @@ -13,16 +16,15 @@ class MethodChannelDeviceInfo extends DeviceInfoPlatform { // Method channel for Android devices Future androidInfo() async { - return AndroidDeviceInfo.fromMap( - (await channel.invokeMethod('getAndroidDeviceInfo')) - .cast(), - ); + return AndroidDeviceInfo.fromMap((await channel + .invokeMapMethod('getAndroidDeviceInfo')) ?? + {}); } // Method channel for iOS devices Future iosInfo() async { return IosDeviceInfo.fromMap( - (await channel.invokeMethod('getIosDeviceInfo')).cast(), - ); + (await channel.invokeMapMethod('getIosDeviceInfo')) ?? + {}); } } 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 index 4fb940c3effa..c5210ab10f50 100644 --- 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 @@ -38,39 +38,63 @@ class AndroidDeviceInfo { final AndroidBuildVersion version; /// The name of the underlying board, like "goldfish". + /// + /// The value is an empty String if it is not available. final String board; /// The system bootloader version number. + /// + /// The value is an empty String if it is not available. final String bootloader; /// The consumer-visible brand with which the product/hardware will be associated, if any. + /// + /// The value is an empty String if it is not available. final String brand; /// The name of the industrial design. + /// + /// The value is an empty String if it is not available. final String device; /// A build ID string meant for displaying to the user. + /// + /// The value is an empty String if it is not available. final String display; /// A string that uniquely identifies this build. + /// + /// The value is an empty String if it is not available. final String fingerprint; /// The name of the hardware (from the kernel command line or /proc). + /// + /// The value is an empty String if it is not available. final String hardware; /// Hostname. + /// + /// The value is an empty String if it is not available. final String host; /// Either a changelist number, or a label like "M4-rc20". + /// + /// The value is an empty String if it is not available. final String id; /// The manufacturer of the product/hardware. + /// + /// The value is an empty String if it is not available. final String manufacturer; /// The end-user-visible name for the end product. + /// + /// The value is an empty String if it is not available. final String model; /// The name of the overall product. + /// + /// The value is an empty String if it is not available. final String product; /// An ordered list of 32 bit ABIs supported by this device. @@ -83,15 +107,23 @@ class AndroidDeviceInfo { final List supportedAbis; /// Comma-separated tags describing the build, like "unsigned,debug". + /// + /// The value is an empty String if it is not available. final String tags; /// The type of build, like "user" or "eng". + /// + /// The value is an empty String if it is not available. final String type; - /// `false` if the application is running in an emulator, `true` otherwise. + /// The value is `true` if the application is running on a physical device. + /// + /// The value is `false` when the application is running on a emulator, or the value is unavailable. final bool isPhysicalDevice; /// The Android hardware device ID that is unique between the device + user and app signing. + /// + /// The value is an empty String if it is not available. final String androidId; /// Describes what features are available on the current device. @@ -113,35 +145,41 @@ class AndroidDeviceInfo { /// 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']!), + version: AndroidBuildVersion._fromMap(map['version'] != null + ? 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'] ?? false, + androidId: map['androidId'] ?? '', + systemFeatures: _fromList(map['systemFeatures']), ); } /// Deserializes message as List static List _fromList(dynamic message) { - final List list = message; - return List.from(list); + if (message == null) { + return []; + } + assert(message is List); + final List list = List.from(message) + ..removeWhere((value) => value == null); + return list.cast(); } } @@ -173,17 +211,25 @@ class AndroidBuildVersion { final String? securityPatch; /// The current development codename, or the string "REL" if this is a release build. + /// + /// The value is an empty String if it is not available. final String codename; /// The internal value used by the underlying source control to represent this build. + /// + /// The value is an empty String if it is not available. final String incremental; /// The user-visible version string. + /// + /// The value is an empty String if it is not available. 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 + /// + /// The value is -1 if it is unavailable. final int sdkInt; /// Deserializes from the map message received from [_kChannel]. @@ -192,10 +238,10 @@ class AndroidBuildVersion { baseOS: map['baseOS'], previewSdkInt: map['previewSdkInt'], securityPatch: map['securityPatch'], - codename: map['codename']!, - incremental: map['incremental']!, - release: map['release']!, - sdkInt: map['sdkInt']!, + codename: map['codename'] ?? '', + incremental: map['incremental'] ?? '', + release: map['release'] ?? '', + sdkInt: map['sdkInt'] ?? -1, ); } } 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 index eb6e5874073b..20ec8362f66d 100644 --- 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 @@ -19,40 +19,60 @@ class IosDeviceInfo { }); /// Device name. + /// + /// The value is an empty String if it is not available. final String name; /// The name of the current operating system. + /// + /// The value is an empty String if it is not available. final String systemName; /// The current operating system version. + /// + /// The value is an empty String if it is not available. final String systemVersion; /// Device model. + /// + /// The value is an empty String if it is not available. final String model; /// Localized name of the device model. + /// + /// The value is an empty String if it is not available. final String localizedModel; /// Unique UUID value identifying the current device. + /// + /// The value is an empty String if it is not available. final String identifierForVendor; - /// `false` if the application is running in a simulator, `true` otherwise. + /// The value is `true` if the application is running on a physical device. + /// + /// The value is `false` when the application is running on a simulator, or the value is unavailable. final bool isPhysicalDevice; /// Operating system information derived from `sys/utsname.h`. + /// + /// The value is an empty String if it is not available. 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()), + name: map['name'] ?? '', + systemName: map['systemName'] ?? '', + systemVersion: map['systemVersion'] ?? '', + model: map['model'] ?? '', + localizedModel: map['localizedModel'] ?? '', + identifierForVendor: map['identifierForVendor'] ?? '', + isPhysicalDevice: map['isPhysicalDevice'] != null + ? map['isPhysicalDevice'] == 'true' + : false, + utsname: IosUtsname._fromMap(map['utsname'] != null + ? map['utsname'].cast() + : {}), ); } } @@ -69,28 +89,38 @@ class IosUtsname { }); /// Operating system name. + /// + /// The value is an empty String if it is not available. final String sysname; /// Network node name. + /// + /// The value is an empty String if it is not available. final String nodename; /// Release level. + /// + /// The value is an empty String if it is not available. final String release; /// Version level. + /// + /// The value is an empty String if it is not available. final String version; /// Hardware type (e.g. 'iPhone7,1' for iPhone 6 Plus). + /// + /// The value is an empty String if it is not available. 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']!, + 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/test/method_channel_device_info_test.dart b/packages/device_info/device_info_platform_interface/test/method_channel_device_info_test.dart index 15963854ab12..03ff4b53cda9 100644 --- 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 @@ -158,4 +158,220 @@ void main() { expect(result.utsname.machine, "x86_64"); }); }); + + group( + "$MethodChannelDeviceInfo handles null value in the map returned from method channel", + () { + MethodChannelDeviceInfo methodChannelDeviceInfo; + + setUp(() async { + methodChannelDeviceInfo = MethodChannelDeviceInfo(); + + methodChannelDeviceInfo.channel + .setMockMethodCallHandler((MethodCall methodCall) async { + switch (methodCall.method) { + case 'getAndroidDeviceInfo': + return ({ + "version": null, + "board": null, + "bootloader": null, + "brand": null, + "device": null, + "display": null, + "fingerprint": null, + "hardware": null, + "host": null, + "id": null, + "manufacturer": null, + "model": null, + "product": null, + "supported32BitAbis": null, + "supported64BitAbis": null, + "supportedAbis": null, + "tags": null, + "type": null, + "isPhysicalDevice": null, + "androidId": null, + "systemFeatures": null, + }); + case 'getIosDeviceInfo': + return ({ + "name": null, + "systemName": null, + "systemVersion": null, + "model": null, + "localizedModel": null, + "identifierForVendor": null, + "isPhysicalDevice": null, + "utsname": null, + }); + default: + return null; + } + }); + }); + + test("androidInfo hanels null", () async { + final AndroidDeviceInfo result = + await methodChannelDeviceInfo.androidInfo(); + + expect(result.version.securityPatch, null); + expect(result.version.sdkInt, -1); + expect(result.version.release, ''); + expect(result.version.previewSdkInt, null); + expect(result.version.incremental, ''); + expect(result.version.codename, ''); + expect(result.board, ''); + expect(result.bootloader, ''); + expect(result.brand, ''); + expect(result.device, ''); + expect(result.display, ''); + expect(result.fingerprint, ''); + expect(result.hardware, ''); + expect(result.host, ''); + expect(result.id, ''); + expect(result.manufacturer, ''); + expect(result.model, ''); + expect(result.product, ''); + expect(result.supported32BitAbis, []); + expect(result.supported64BitAbis, []); + expect(result.supportedAbis, []); + expect(result.tags, ''); + expect(result.type, ''); + expect(result.isPhysicalDevice, false); + expect(result.androidId, ''); + expect(result.systemFeatures, []); + }); + + test("iosInfo handles null", () async { + final IosDeviceInfo result = await methodChannelDeviceInfo.iosInfo(); + expect(result.name, ''); + expect(result.systemName, ''); + expect(result.systemVersion, ''); + expect(result.model, ''); + expect(result.localizedModel, ''); + expect(result.identifierForVendor, ''); + expect(result.isPhysicalDevice, false); + expect(result.utsname.sysname, ''); + expect(result.utsname.nodename, ''); + expect(result.utsname.release, ''); + expect(result.utsname.version, ''); + expect(result.utsname.machine, ''); + }); + }); + + group("$MethodChannelDeviceInfo handles method channel returns null", () { + MethodChannelDeviceInfo methodChannelDeviceInfo; + + setUp(() async { + methodChannelDeviceInfo = MethodChannelDeviceInfo(); + + methodChannelDeviceInfo.channel + .setMockMethodCallHandler((MethodCall methodCall) async { + switch (methodCall.method) { + case 'getAndroidDeviceInfo': + return null; + case 'getIosDeviceInfo': + return null; + default: + return null; + } + }); + }); + + test("androidInfo handles null", () async { + final AndroidDeviceInfo result = + await methodChannelDeviceInfo.androidInfo(); + + expect(result.version.securityPatch, null); + expect(result.version.sdkInt, -1); + expect(result.version.release, ''); + expect(result.version.previewSdkInt, null); + expect(result.version.incremental, ''); + expect(result.version.codename, ''); + expect(result.board, ''); + expect(result.bootloader, ''); + expect(result.brand, ''); + expect(result.device, ''); + expect(result.display, ''); + expect(result.fingerprint, ''); + expect(result.hardware, ''); + expect(result.host, ''); + expect(result.id, ''); + expect(result.manufacturer, ''); + expect(result.model, ''); + expect(result.product, ''); + expect(result.supported32BitAbis, []); + expect(result.supported64BitAbis, []); + expect(result.supportedAbis, []); + expect(result.tags, ''); + expect(result.type, ''); + expect(result.isPhysicalDevice, false); + expect(result.androidId, ''); + expect(result.systemFeatures, []); + }); + + test("iosInfo handles null", () async { + final IosDeviceInfo result = await methodChannelDeviceInfo.iosInfo(); + expect(result.name, ''); + expect(result.systemName, ''); + expect(result.systemVersion, ''); + expect(result.model, ''); + expect(result.localizedModel, ''); + expect(result.identifierForVendor, ''); + expect(result.isPhysicalDevice, false); + expect(result.utsname.sysname, ''); + expect(result.utsname.nodename, ''); + expect(result.utsname.release, ''); + expect(result.utsname.version, ''); + expect(result.utsname.machine, ''); + }); + }); + + group("$MethodChannelDeviceInfo android handles null values in list", () { + MethodChannelDeviceInfo methodChannelDeviceInfo; + + setUp(() async { + methodChannelDeviceInfo = MethodChannelDeviceInfo(); + + methodChannelDeviceInfo.channel + .setMockMethodCallHandler((MethodCall methodCall) async { + switch (methodCall.method) { + case 'getAndroidDeviceInfo': + return ({ + "supported32BitAbis": ["x86", null], + "supported64BitAbis": ["x86_64", null], + "supportedAbis": ["x86_64", "x86", null], + "systemFeatures": [ + "android.hardware.sensor.proximity", + "android.software.adoptable_storage", + "android.hardware.sensor.accelerometer", + "android.hardware.faketouch", + "android.software.backup", + "android.hardware.touchscreen", + null + ], + }); + default: + return null; + } + }); + }); + + test("androidInfo hanels null in list", () async { + final AndroidDeviceInfo result = + await methodChannelDeviceInfo.androidInfo(); + expect(result.supported32BitAbis, ['x86']); + expect(result.supported64BitAbis, ['x86_64']); + expect(result.supportedAbis, ['x86_64', 'x86']); + expect(result.systemFeatures, [ + "android.hardware.sensor.proximity", + "android.software.adoptable_storage", + "android.hardware.sensor.accelerometer", + "android.hardware.faketouch", + "android.software.backup", + "android.hardware.touchscreen" + ]); + }); + }); }