Merge pull request #77 from chinmaygarde/master

Fix Flutter project template
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index e0f058e..e51d27d 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -56,6 +56,12 @@
 
   /// Check if the current version of the given app is already installed
   bool isAppInstalled(ApplicationPackage app);
+
+  /// Start an app package on the current device
+  Future<bool> startApp(ApplicationPackage app);
+
+  /// Stop an app package on the current device
+  Future<bool> stopApp(ApplicationPackage app);
 }
 
 class IOSDevice extends _Device {
@@ -80,6 +86,12 @@
   String _informerPath;
   String get informerPath => _informerPath;
 
+  String _debuggerPath;
+  String get debuggerPath => _debuggerPath;
+
+  String _loggerPath;
+  String get loggerPath => _loggerPath;
+
   String _name;
   String get name => _name;
 
@@ -93,6 +105,8 @@
     _installerPath = _checkForCommand('ideviceinstaller');
     _listerPath = _checkForCommand('idevice_id');
     _informerPath = _checkForCommand('ideviceinfo');
+    _debuggerPath = _checkForCommand('idevicedebug');
+    _loggerPath = _checkForCommand('idevicesyslog');
   }
 
   static List<IOSDevice> getAttachedDevices([IOSDevice mockIOS]) {
@@ -160,8 +174,44 @@
 
   @override
   bool isAppInstalled(ApplicationPackage app) {
+    try {
+      String apps = runCheckedSync([installerPath, '-l']);
+      if (new RegExp(app.appPackageID, multiLine: true).hasMatch(apps)) {
+        return true;
+      }
+    } catch (e) {
+      return false;
+    }
     return false;
   }
+
+  @override
+  Future<bool> startApp(ApplicationPackage app) async {
+    if (!isAppInstalled(app)) {
+      return false;
+    }
+    // idevicedebug hangs forever after launching the app, so kill it after
+    // giving it plenty of time to send the launch command.
+    return runAndKill(
+        [debuggerPath, 'run', app.appPackageID], new Duration(seconds: 3)).then(
+        (_) {
+      return true;
+    }, onError: (e) {
+      _logging.info('Failure running $debuggerPath: ', e);
+      return false;
+    });
+  }
+
+  @override
+  Future<bool> stopApp(ApplicationPackage app) async {
+    // Currently we don't have a way to stop an app running on iOS.
+    return false;
+  }
+
+  Future<int> logs({bool clear: false}) {
+    return runCommandAndStreamOutput([loggerPath],
+        prefix: 'IOS DEV: ', filter: new RegExp(r'.*SkyShell.*'));
+  }
 }
 
 class AndroidDevice extends _Device {
@@ -230,13 +280,15 @@
     _adbPath = _getAdbPath();
     _hasAdb = _checkForAdb();
 
-    // Checking for lollipop only needs to be done if we are starting an
-    // app, but it has an important side effect, which is to discard any
-    // progress messages if the adb server is restarted.
-    _hasValidAndroid = _checkForLollipopOrLater();
+    if (isConnected()) {
+      // Checking for lollipop only needs to be done if we are starting an
+      // app, but it has an important side effect, which is to discard any
+      // progress messages if the adb server is restarted.
+      _hasValidAndroid = _checkForLollipopOrLater();
 
-    if (!_hasAdb || !_hasValidAndroid) {
-      _logging.severe('Unable to run on Android.');
+      if (!_hasAdb || !_hasValidAndroid) {
+        _logging.severe('Unable to run on Android.');
+      }
     }
   }
 
@@ -456,7 +508,14 @@
     return true;
   }
 
-  bool stop(AndroidApk apk) {
+  @override
+  Future<bool> startApp(AndroidApk apk) async {
+    // Android currently has to be started with startServer(...).
+    assert(false);
+    return false;
+  }
+
+  Future<bool> stopApp(AndroidApk apk) async {
     // Turn off reverse port forwarding
     runSync([adbPath, 'reverse', '--remove', 'tcp:$_serverPort']);
     // Stop the app
diff --git a/packages/flutter_tools/lib/src/logs.dart b/packages/flutter_tools/lib/src/logs.dart
index d3f9081..518b75e 100644
--- a/packages/flutter_tools/lib/src/logs.dart
+++ b/packages/flutter_tools/lib/src/logs.dart
@@ -15,9 +15,10 @@
 class LogsCommand extends Command {
   final name = 'logs';
   final description = 'Show logs for running Sky apps.';
-  AndroidDevice android = null;
+  AndroidDevice android;
+  IOSDevice ios;
 
-  LogsCommand([this.android]) {
+  LogsCommand({this.android, this.ios}) {
     argParser.addFlag('clear',
         negatable: false,
         help: 'Clear log history before reading from logs (Android only).');
@@ -28,16 +29,28 @@
     if (android == null) {
       android = new AndroidDevice();
     }
+    if (ios == null) {
+      ios = new IOSDevice();
+    }
 
     Future<int> androidLogProcess = null;
     if (android.isConnected()) {
       androidLogProcess = android.logs(clear: argResults['clear']);
     }
 
+    Future<int> iosLogProcess = null;
+    if (ios.isConnected()) {
+      iosLogProcess = ios.logs(clear: argResults['clear']);
+    }
+
     if (androidLogProcess != null) {
       await androidLogProcess;
     }
 
+    if (iosLogProcess != null) {
+      await iosLogProcess;
+    }
+
     return 0;
   }
 }
diff --git a/packages/flutter_tools/lib/src/process.dart b/packages/flutter_tools/lib/src/process.dart
index c703571..7be82b2 100644
--- a/packages/flutter_tools/lib/src/process.dart
+++ b/packages/flutter_tools/lib/src/process.dart
@@ -15,19 +15,43 @@
 /// This runs the command and streams stdout/stderr from the child process to
 /// this process' stdout/stderr.
 Future<int> runCommandAndStreamOutput(List<String> cmd,
-    {String prefix: ''}) async {
+    {String prefix: '', RegExp filter}) async {
   _logging.info(cmd.join(' '));
   Process proc =
       await Process.start(cmd[0], cmd.getRange(1, cmd.length).toList());
-  proc.stdout.transform(UTF8.decoder).listen((data) {
-    stdout.write('$prefix${data.trimRight().split('\n').join('\n$prefix')}\n');
+  proc.stdout.transform(UTF8.decoder).listen((String data) {
+    List<String> dataLines = data.trimRight().split('\n');
+    if (filter != null) {
+      dataLines = dataLines.where((String s) => filter.hasMatch(s));
+    }
+    if (dataLines.length > 0) {
+      stdout.write('$prefix${dataLines.join('\n$prefix')}\n');
+    }
   });
-  proc.stderr.transform(UTF8.decoder).listen((data) {
-    stderr.write('$prefix${data.trimRight().split('\n').join('\n$prefix')}\n');
+  proc.stderr.transform(UTF8.decoder).listen((String data) {
+    List<String> dataLines = data.trimRight().split('\n');
+    if (filter != null) {
+      dataLines = dataLines.where((String s) => filter.hasMatch(s));
+    }
+    if (dataLines.length > 0) {
+      stderr.write('$prefix${dataLines.join('\n$prefix')}\n');
+    }
   });
   return proc.exitCode;
 }
 
+Future runAndKill(List<String> cmd, Duration timeout) async {
+  _logging.info(cmd.join(' '));
+  Future<Process> proc = Process.start(
+      cmd[0], cmd.getRange(1, cmd.length).toList(),
+      mode: ProcessStartMode.DETACHED);
+
+  return new Future.delayed(timeout, () async {
+    _logging.info('Intentionally killing ${cmd[0]}');
+    Process.killPid((await proc).pid);
+  });
+}
+
 /// Run cmd and return stdout.
 /// Throws an error if cmd exits with a non-zero value.
 String runCheckedSync(List<String> cmd) =>
diff --git a/packages/flutter_tools/lib/src/start.dart b/packages/flutter_tools/lib/src/start.dart
index 08af1a1..8d2dbc0 100644
--- a/packages/flutter_tools/lib/src/start.dart
+++ b/packages/flutter_tools/lib/src/start.dart
@@ -41,22 +41,26 @@
     if (android == null) {
       android = new AndroidDevice();
     }
+    if (ios == null) {
+      ios = new IOSDevice();
+    }
 
     bool startedSomewhere = false;
     bool poke = argResults['poke'];
     if (!poke) {
-      StopCommand stopper = new StopCommand(android);
+      StopCommand stopper = new StopCommand(android: android, ios: ios);
       stopper.stop();
 
       // Only install if the user did not specify a poke
       InstallCommand installer = new InstallCommand(android: android, ios: ios);
-      startedSomewhere = installer.install();
+      installer.install();
     }
 
+    Map<BuildPlatform, ApplicationPackage> packages =
+        ApplicationPackageFactory.getAvailableApplicationPackages();
+
     bool startedOnAndroid = false;
     if (android.isConnected()) {
-      Map<BuildPlatform, ApplicationPackage> packages =
-          ApplicationPackageFactory.getAvailableApplicationPackages();
       ApplicationPackage androidApp = packages[BuildPlatform.android];
 
       String target = path.absolute(argResults['target']);
@@ -64,6 +68,12 @@
           target, poke, argResults['checked'], androidApp);
     }
 
+    if (ios.isConnected()) {
+      ApplicationPackage iosApp = packages[BuildPlatform.iOS];
+
+      startedSomewhere = await ios.startApp(iosApp) || startedSomewhere;
+    }
+
     if (startedSomewhere || startedOnAndroid) {
       return 0;
     } else {
diff --git a/packages/flutter_tools/lib/src/stop.dart b/packages/flutter_tools/lib/src/stop.dart
index c3edaf6..d1f9844 100644
--- a/packages/flutter_tools/lib/src/stop.dart
+++ b/packages/flutter_tools/lib/src/stop.dart
@@ -17,29 +17,39 @@
   final name = 'stop';
   final description = 'Stop your Flutter app on all attached devices.';
   AndroidDevice android = null;
+  IOSDevice ios = null;
 
-  StopCommand([this.android]);
+  StopCommand({this.android, this.ios});
 
   @override
   Future<int> run() async {
-    if (android == null) {
-      android = new AndroidDevice();
-    }
-
-    if (stop()) {
+    if (await stop()) {
       return 0;
     } else {
       return 2;
     }
   }
 
-  bool stop() {
+  Future<bool> stop() async {
+    if (android == null) {
+      android = new AndroidDevice();
+    }
+    if (ios == null) {
+      ios = new IOSDevice();
+    }
+
     bool stoppedSomething = false;
+    Map<BuildPlatform, ApplicationPackage> packages =
+        ApplicationPackageFactory.getAvailableApplicationPackages();
+
     if (android.isConnected()) {
-      Map<BuildPlatform, ApplicationPackage> packages =
-          ApplicationPackageFactory.getAvailableApplicationPackages();
       ApplicationPackage androidApp = packages[BuildPlatform.android];
-      stoppedSomething = android.stop(androidApp) || stoppedSomething;
+      stoppedSomething = await android.stopApp(androidApp) || stoppedSomething;
+    }
+
+    if (ios.isConnected()) {
+      ApplicationPackage iosApp = packages[BuildPlatform.iOS];
+      stoppedSomething = await ios.stopApp(iosApp) || stoppedSomething;
     }
 
     return stoppedSomething;
diff --git a/packages/flutter_tools/test/logs_test.dart b/packages/flutter_tools/test/logs_test.dart
index 19a5872..e0f2c38 100644
--- a/packages/flutter_tools/test/logs_test.dart
+++ b/packages/flutter_tools/test/logs_test.dart
@@ -20,7 +20,10 @@
 
       MockAndroidDevice android = new MockAndroidDevice();
       when(android.isConnected()).thenReturn(false);
-      LogsCommand command = new LogsCommand(android);
+      MockIOSDevice ios = new MockIOSDevice();
+      when(ios.isConnected()).thenReturn(false);
+
+      LogsCommand command = new LogsCommand(android: android, ios: ios);
 
       CommandRunner runner = new CommandRunner('test_flutter', '')
         ..addCommand(command);
diff --git a/packages/flutter_tools/test/start_test.dart b/packages/flutter_tools/test/start_test.dart
index 732f18b..9ddfb0e 100644
--- a/packages/flutter_tools/test/start_test.dart
+++ b/packages/flutter_tools/test/start_test.dart
@@ -21,11 +21,36 @@
       MockAndroidDevice android = new MockAndroidDevice();
       when(android.isConnected()).thenReturn(true);
       when(android.installApp(any)).thenReturn(true);
-      when(android.stop(any)).thenReturn(true);
+      when(android.startServer(any, any, any, any)).thenReturn(true);
+      when(android.stopApp(any)).thenReturn(true);
 
       MockIOSDevice ios = new MockIOSDevice();
       when(ios.isConnected()).thenReturn(false);
       when(ios.installApp(any)).thenReturn(false);
+      when(ios.startApp(any)).thenReturn(false);
+      when(ios.startApp(any)).thenReturn(false);
+
+      StartCommand command = new StartCommand(android: android, ios: ios);
+
+      CommandRunner runner = new CommandRunner('test_flutter', '')
+        ..addCommand(command);
+      runner.run(['start']).then((int code) => expect(code, equals(0)));
+    });
+
+    test('returns 0 when iOS is connected and ready to be started', () {
+      applicationPackageSetup();
+
+      MockAndroidDevice android = new MockAndroidDevice();
+      when(android.isConnected()).thenReturn(false);
+      when(android.installApp(any)).thenReturn(false);
+      when(android.startServer(any, any, any, any)).thenReturn(false);
+      when(android.stopApp(any)).thenReturn(false);
+
+      MockIOSDevice ios = new MockIOSDevice();
+      when(ios.isConnected()).thenReturn(true);
+      when(ios.installApp(any)).thenReturn(true);
+      when(ios.startApp(any)).thenReturn(true);
+      when(ios.stopApp(any)).thenReturn(false);
 
       StartCommand command = new StartCommand(android: android, ios: ios);
 
diff --git a/packages/flutter_tools/test/stop_test.dart b/packages/flutter_tools/test/stop_test.dart
index af6ec19..8d67d66 100644
--- a/packages/flutter_tools/test/stop_test.dart
+++ b/packages/flutter_tools/test/stop_test.dart
@@ -20,8 +20,31 @@
 
       MockAndroidDevice android = new MockAndroidDevice();
       when(android.isConnected()).thenReturn(true);
-      when(android.stop(any)).thenReturn(true);
-      StopCommand command = new StopCommand(android);
+      when(android.stopApp(any)).thenReturn(true);
+
+      MockIOSDevice ios = new MockIOSDevice();
+      when(ios.isConnected()).thenReturn(false);
+      when(ios.stopApp(any)).thenReturn(false);
+
+      StopCommand command = new StopCommand(android: android, ios: ios);
+
+      CommandRunner runner = new CommandRunner('test_flutter', '')
+        ..addCommand(command);
+      runner.run(['stop']).then((int code) => expect(code, equals(0)));
+    });
+
+    test('returns 0 when iOS is connected and ready to be stopped', () {
+      applicationPackageSetup();
+
+      MockAndroidDevice android = new MockAndroidDevice();
+      when(android.isConnected()).thenReturn(false);
+      when(android.stopApp(any)).thenReturn(false);
+
+      MockIOSDevice ios = new MockIOSDevice();
+      when(ios.isConnected()).thenReturn(true);
+      when(ios.stopApp(any)).thenReturn(true);
+
+      StopCommand command = new StopCommand(android: android, ios: ios);
 
       CommandRunner runner = new CommandRunner('test_flutter', '')
         ..addCommand(command);