Add support for binding to IPv6 localhost in `flutter run` (#13136)

This allows `flutter run` to work on hosts that are IPv6-only.
diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart
index 49872b3..44cd03f 100644
--- a/packages/flutter_tools/lib/src/android/android_device.dart
+++ b/packages/flutter_tools/lib/src/android/android_device.dart
@@ -345,6 +345,7 @@
     bool prebuiltApplication: false,
     bool applicationNeedsRebuild: false,
     bool usesTerminalUi: true,
+    bool ipv6: false,
   }) async {
     if (!await _checkForSupportedAdbVersion() || !await _checkForSupportedAndroidVersion())
       return new LaunchResult.failed();
@@ -381,7 +382,11 @@
       // TODO(devoncarew): Remember the forwarding information (so we can later remove the
       // port forwarding or set it up again when adb fails on us).
       observatoryDiscovery = new ProtocolDiscovery.observatory(
-        getLogReader(), portForwarder: portForwarder, hostPort: debuggingOptions.observatoryPort);
+        getLogReader(),
+        portForwarder: portForwarder,
+        hostPort: debuggingOptions.observatoryPort,
+        ipv6: ipv6,
+      );
     }
 
     List<String> cmd;
diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart
index f297f3f..dccace1 100644
--- a/packages/flutter_tools/lib/src/commands/run.dart
+++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -28,6 +28,12 @@
         negatable: true,
         defaultsTo: false,
         help: 'Start tracing during startup.');
+    argParser.addFlag('ipv6',
+        hide: true,
+        negatable: false,
+        defaultsTo: false,
+        help: 'Binds to IPv6 localhost instead of IPv4 when the flutter tool\n'
+              'forwards the host port to a device port.');
     argParser.addOption('route',
         help: 'Which route to load when running the app.');
     usesTargetOption();
@@ -36,6 +42,7 @@
   }
 
   bool get traceStartup => argResults['trace-startup'];
+  bool get ipv6 => argResults['ipv6'];
   String get route => argResults['route'];
 
   void usesPortOptions() {
@@ -304,6 +311,7 @@
         packagesFilePath: argResults['packages'],
         projectAssets: argResults['project-assets'],
         stayResident: stayResident,
+        ipv6: ipv6,
       );
     } else {
       runner = new ColdRunner(
@@ -314,6 +322,7 @@
         applicationBinary: argResults['use-application-binary'],
         previewDart2: argResults['preview-dart-2'],
         stayResident: stayResident,
+        ipv6: ipv6,
       );
     }
 
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index e79c73f..aabe18a 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -227,6 +227,7 @@
     bool prebuiltApplication: false,
     bool applicationNeedsRebuild: false,
     bool usesTerminalUi: true,
+    bool ipv6: false,
   });
 
   /// Does this device implement support for hot reloading / restarting?
diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
index 08af8c7..cf105f2 100644
--- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
+++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
@@ -68,6 +68,7 @@
     bool previewDart2: false,
     bool applicationNeedsRebuild: false,
     bool usesTerminalUi: false,
+    bool ipv6: false,
   }) => new Future<Null>.error('unimplemented');
 
   @override
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart
index e79da6d..861cc25 100644
--- a/packages/flutter_tools/lib/src/ios/devices.dart
+++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -163,6 +163,7 @@
     bool previewDart2: false,
     bool applicationNeedsRebuild: false,
     bool usesTerminalUi: true,
+    bool ipv6: false,
   }) async {
     if (!prebuiltApplication) {
       // TODO(chinmaygarde): Use mainPath, route.
@@ -255,7 +256,11 @@
       // TODO(danrubel): The Android device class does something similar to this code below.
       // The various Device subclasses should be refactored and common code moved into the superclass.
       final ProtocolDiscovery observatoryDiscovery = new ProtocolDiscovery.observatory(
-        getLogReader(app: app), portForwarder: portForwarder, hostPort: debuggingOptions.observatoryPort);
+        getLogReader(app: app),
+        portForwarder: portForwarder,
+        hostPort: debuggingOptions.observatoryPort,
+        ipv6: ipv6,
+      );
 
       final Future<Uri> forwardObservatoryUri = observatoryDiscovery.uri;
 
diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart
index c9a748c..7831b65 100644
--- a/packages/flutter_tools/lib/src/ios/simulators.dart
+++ b/packages/flutter_tools/lib/src/ios/simulators.dart
@@ -312,6 +312,7 @@
     bool prebuiltApplication: false,
     bool applicationNeedsRebuild: false,
     bool usesTerminalUi: true,
+    bool ipv6: false,
   }) async {
     if (!prebuiltApplication) {
       printTrace('Building ${app.name} for $id.');
@@ -352,7 +353,8 @@
 
     ProtocolDiscovery observatoryDiscovery;
     if (debuggingOptions.debuggingEnabled)
-      observatoryDiscovery = new ProtocolDiscovery.observatory(getLogReader(app: app));
+      observatoryDiscovery = new ProtocolDiscovery.observatory(
+          getLogReader(app: app), ipv6: ipv6);
 
     // Launch the updated application in the simulator.
     try {
diff --git a/packages/flutter_tools/lib/src/protocol_discovery.dart b/packages/flutter_tools/lib/src/protocol_discovery.dart
index 1bc35d3..4cd0509 100644
--- a/packages/flutter_tools/lib/src/protocol_discovery.dart
+++ b/packages/flutter_tools/lib/src/protocol_discovery.dart
@@ -5,6 +5,7 @@
 import 'dart:async';
 
 import 'base/common.dart';
+import 'base/io.dart';
 import 'base/port_scanner.dart';
 import 'device.dart';
 import 'globals.dart';
@@ -18,6 +19,7 @@
     this.portForwarder,
     this.hostPort,
     this.defaultHostPort,
+    this.ipv6,
   }) : assert(logReader != null),
        assert(portForwarder == null || defaultHostPort != null),
        _prefix = '$serviceName listening on ' {
@@ -28,6 +30,7 @@
     DeviceLogReader logReader, {
     DevicePortForwarder portForwarder,
     int hostPort,
+    bool ipv6: false,
   }) {
     const String kObservatoryService = 'Observatory';
     return new ProtocolDiscovery._(
@@ -35,6 +38,7 @@
       portForwarder: portForwarder,
       hostPort: hostPort,
       defaultHostPort: kDefaultObservatoryPort,
+      ipv6: ipv6,
     );
   }
 
@@ -43,6 +47,7 @@
   final DevicePortForwarder portForwarder;
   final int hostPort;
   final int defaultHostPort;
+  final bool ipv6;
 
   final String _prefix;
   final Completer<Uri> _completer = new Completer<Uri>();
@@ -90,6 +95,11 @@
       hostUri = deviceUri.replace(port: hostPort);
     }
 
+    assert(new InternetAddress(hostUri.host).isLoopback);
+    if (ipv6) {
+      hostUri = hostUri.replace(host: InternetAddress.LOOPBACK_IP_V6.host);
+    }
+
     return hostUri;
   }
 }
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart
index 2e557d2..c284dce 100644
--- a/packages/flutter_tools/lib/src/resident_runner.dart
+++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -273,6 +273,7 @@
       prebuiltApplication: prebuiltMode,
       applicationNeedsRebuild: shouldBuild || hasDirtyDependencies,
       usesTerminalUi: hotRunner.usesTerminalUI,
+      ipv6: hotRunner.ipv6,
     );
 
     final LaunchResult result = await futureResult;
@@ -316,9 +317,9 @@
       return 1;
     }
 
-    Map<String, dynamic> platformArgs;
+    final Map<String, dynamic> platformArgs = <String, dynamic>{};
     if (coldRunner.traceStartup != null)
-      platformArgs = <String, dynamic>{ 'trace-startup': coldRunner.traceStartup };
+      platformArgs['trace-startup'] = coldRunner.traceStartup;
 
     startEchoingDeviceLog();
 
@@ -332,6 +333,7 @@
       prebuiltApplication: prebuiltMode,
       applicationNeedsRebuild: shouldBuild || hasDirtyDependencies,
       usesTerminalUi: coldRunner.usesTerminalUI,
+      ipv6: coldRunner.ipv6,
     );
 
     if (!result.started) {
@@ -394,6 +396,7 @@
     String packagesFilePath,
     String projectAssets,
     this.stayResident,
+    this.ipv6,
   }) {
     _mainPath = findMainDartFile(target);
     _projectRootPath = projectRootPath ?? fs.currentDirectory.path;
@@ -410,6 +413,7 @@
   final DebuggingOptions debuggingOptions;
   final bool usesTerminalUI;
   final bool stayResident;
+  final bool ipv6;
   final Completer<int> _finished = new Completer<int>();
   bool _stopped = false;
   String _packagesFilePath;
diff --git a/packages/flutter_tools/lib/src/run_cold.dart b/packages/flutter_tools/lib/src/run_cold.dart
index 713d61c..7a004f2 100644
--- a/packages/flutter_tools/lib/src/run_cold.dart
+++ b/packages/flutter_tools/lib/src/run_cold.dart
@@ -22,11 +22,13 @@
     this.applicationBinary,
     this.previewDart2 : false,
     bool stayResident: true,
+    bool ipv6: false,
   }) : super(devices,
              target: target,
              debuggingOptions: debuggingOptions,
              usesTerminalUI: usesTerminalUI,
-             stayResident: stayResident);
+             stayResident: stayResident,
+             ipv6: ipv6);
 
   final bool traceStartup;
   final String applicationBinary;
diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart
index 9986ef6..b7a6f38 100644
--- a/packages/flutter_tools/lib/src/run_hot.dart
+++ b/packages/flutter_tools/lib/src/run_hot.dart
@@ -45,6 +45,7 @@
     String packagesFilePath,
     String projectAssets,
     bool stayResident: true,
+    bool ipv6: false,
   }) : super(devices,
              target: target,
              debuggingOptions: debuggingOptions,
@@ -52,7 +53,8 @@
              projectRootPath: projectRootPath,
              packagesFilePath: packagesFilePath,
              projectAssets: projectAssets,
-             stayResident: stayResident);
+             stayResident: stayResident,
+             ipv6: ipv6);
 
   final String applicationBinary;
   final bool hostIsIde;