[device_info] add PackageManager's SystemFeatures to AndroidDeviceInfo (#2567)

There's a variety of scenarios where checking at runtime which system
features are available is useful.  System features are device
capabilities that do not change at runtime (for example, FEATURE_BLUETOOTH
is always present if the device has a bluetooth radio, even if Bluetooth
is presently disabled), so DeviceInfo seems like the right place to put
this.
diff --git a/packages/device_info/CHANGELOG.md b/packages/device_info/CHANGELOG.md
index 00dcd29..68aeed8 100644
--- a/packages/device_info/CHANGELOG.md
+++ b/packages/device_info/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.4.2
+
+* Add systemFeatures to AndroidDeviceInfo.
+
 ## 0.4.1+5
 
 * Make the pedantic dev_dependency explicit.
diff --git a/packages/device_info/android/src/main/java/io/flutter/plugins/deviceinfo/DeviceInfoPlugin.java b/packages/device_info/android/src/main/java/io/flutter/plugins/deviceinfo/DeviceInfoPlugin.java
index 8ad0f5d..a0435a4 100644
--- a/packages/device_info/android/src/main/java/io/flutter/plugins/deviceinfo/DeviceInfoPlugin.java
+++ b/packages/device_info/android/src/main/java/io/flutter/plugins/deviceinfo/DeviceInfoPlugin.java
@@ -4,7 +4,7 @@
 
 package io.flutter.plugins.deviceinfo;
 
-import android.content.ContentResolver;
+import android.content.Context;
 import io.flutter.embedding.engine.plugins.FlutterPlugin;
 import io.flutter.plugin.common.BinaryMessenger;
 import io.flutter.plugin.common.MethodChannel;
@@ -18,14 +18,13 @@
   /** Plugin registration. */
   public static void registerWith(Registrar registrar) {
     DeviceInfoPlugin plugin = new DeviceInfoPlugin();
-    plugin.setupMethodChannel(registrar.messenger(), registrar.context().getContentResolver());
+    plugin.setupMethodChannel(registrar.messenger(), registrar.context());
   }
 
   @Override
   public void onAttachedToEngine(FlutterPlugin.FlutterPluginBinding binding) {
     setupMethodChannel(
-        binding.getFlutterEngine().getDartExecutor(),
-        binding.getApplicationContext().getContentResolver());
+        binding.getFlutterEngine().getDartExecutor(), binding.getApplicationContext());
   }
 
   @Override
@@ -33,9 +32,10 @@
     tearDownChannel();
   }
 
-  private void setupMethodChannel(BinaryMessenger messenger, ContentResolver contentResolver) {
+  private void setupMethodChannel(BinaryMessenger messenger, Context context) {
     channel = new MethodChannel(messenger, "plugins.flutter.io/device_info");
-    final MethodCallHandlerImpl handler = new MethodCallHandlerImpl(contentResolver);
+    final MethodCallHandlerImpl handler =
+        new MethodCallHandlerImpl(context.getContentResolver(), context.getPackageManager());
     channel.setMethodCallHandler(handler);
   }
 
diff --git a/packages/device_info/android/src/main/java/io/flutter/plugins/deviceinfo/MethodCallHandlerImpl.java b/packages/device_info/android/src/main/java/io/flutter/plugins/deviceinfo/MethodCallHandlerImpl.java
index 22ea1f0..800ca6d 100644
--- a/packages/device_info/android/src/main/java/io/flutter/plugins/deviceinfo/MethodCallHandlerImpl.java
+++ b/packages/device_info/android/src/main/java/io/flutter/plugins/deviceinfo/MethodCallHandlerImpl.java
@@ -6,6 +6,8 @@
 
 import android.annotation.SuppressLint;
 import android.content.ContentResolver;
+import android.content.pm.FeatureInfo;
+import android.content.pm.PackageManager;
 import android.os.Build;
 import android.provider.Settings;
 import io.flutter.plugin.common.MethodCall;
@@ -20,14 +22,16 @@
  */
 class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler {
 
-  private ContentResolver contentResolver;
+  private final ContentResolver contentResolver;
+  private final PackageManager packageManager;
 
   /** Substitute for missing values. */
   private static final String[] EMPTY_STRING_LIST = new String[] {};
 
-  /** Constructs DeviceInfo. The {@code contentResolver} must not be null. */
-  MethodCallHandlerImpl(ContentResolver contentResolver) {
+  /** Constructs DeviceInfo. {@code contentResolver} and {@code packageManager} must not be null. */
+  MethodCallHandlerImpl(ContentResolver contentResolver, PackageManager packageManager) {
     this.contentResolver = contentResolver;
+    this.packageManager = packageManager;
   }
 
   @Override
@@ -60,6 +64,8 @@
       build.put("isPhysicalDevice", !isEmulator());
       build.put("androidId", getAndroidId());
 
+      build.put("systemFeatures", Arrays.asList(getSystemFeatures()));
+
       Map<String, Object> version = new HashMap<>();
       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
         version.put("baseOS", Build.VERSION.BASE_OS);
@@ -78,6 +84,18 @@
     }
   }
 
+  private String[] getSystemFeatures() {
+    FeatureInfo[] featureInfos = packageManager.getSystemAvailableFeatures();
+    if (featureInfos == null) {
+      return EMPTY_STRING_LIST;
+    }
+    String[] features = new String[featureInfos.length];
+    for (int i = 0; i < featureInfos.length; i++) {
+      features[i] = featureInfos[i].name;
+    }
+    return features;
+  }
+
   /**
    * Returns the Android hardware device ID that is unique between the device + user and app
    * signing. This key will change if the app is uninstalled or its data is cleared. Device factory
diff --git a/packages/device_info/example/lib/main.dart b/packages/device_info/example/lib/main.dart
index 528604b..1c1064a 100644
--- a/packages/device_info/example/lib/main.dart
+++ b/packages/device_info/example/lib/main.dart
@@ -85,6 +85,7 @@
       'type': build.type,
       'isPhysicalDevice': build.isPhysicalDevice,
       'androidId': build.androidId,
+      'systemFeatures': build.systemFeatures,
     };
   }
 
@@ -114,7 +115,6 @@
               Platform.isAndroid ? 'Android Device Info' : 'iOS Device Info'),
         ),
         body: ListView(
-          shrinkWrap: true,
           children: _deviceData.keys.map((String property) {
             return Row(
               children: <Widget>[
@@ -132,6 +132,7 @@
                   padding: const EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 10.0),
                   child: Text(
                     '${_deviceData[property]}',
+                    maxLines: 10,
                     overflow: TextOverflow.ellipsis,
                   ),
                 )),
diff --git a/packages/device_info/lib/device_info.dart b/packages/device_info/lib/device_info.dart
index e775227..25b7d46 100644
--- a/packages/device_info/lib/device_info.dart
+++ b/packages/device_info/lib/device_info.dart
@@ -62,9 +62,11 @@
     this.type,
     this.isPhysicalDevice,
     this.androidId,
+    List<String> systemFeatures,
   })  : supported32BitAbis = List<String>.unmodifiable(supported32BitAbis),
         supported64BitAbis = List<String>.unmodifiable(supported64BitAbis),
-        supportedAbis = List<String>.unmodifiable(supportedAbis);
+        supportedAbis = List<String>.unmodifiable(supportedAbis),
+        systemFeatures = List<String>.unmodifiable(systemFeatures);
 
   /// Android operating system version values derived from `android.os.Build.VERSION`.
   final AndroidBuildVersion version;
@@ -126,6 +128,22 @@
   /// 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._(
@@ -150,6 +168,7 @@
       type: map['type'],
       isPhysicalDevice: map['isPhysicalDevice'],
       androidId: map['androidId'],
+      systemFeatures: _fromList(map['systemFeatures']),
     );
   }
 
diff --git a/packages/device_info/pubspec.yaml b/packages/device_info/pubspec.yaml
index b862a7d..06a530b 100644
--- a/packages/device_info/pubspec.yaml
+++ b/packages/device_info/pubspec.yaml
@@ -2,7 +2,7 @@
 description: Flutter plugin providing detailed information about the device
   (make, model, etc.), and Android or iOS version the app is running on.
 homepage: https://github.com/flutter/plugins/tree/master/packages/device_info
-version: 0.4.1+5
+version: 0.4.2
 
 flutter:
   plugin: