Add stop command and supporting Android support.
diff --git a/packages/flutter_tools/bin/sky_tools.dart b/packages/flutter_tools/bin/sky_tools.dart
index 12052a8..920ea5e 100644
--- a/packages/flutter_tools/bin/sky_tools.dart
+++ b/packages/flutter_tools/bin/sky_tools.dart
@@ -14,9 +14,11 @@
 import 'package:sky_tools/src/init.dart';
 import 'package:sky_tools/src/install.dart';
 import 'package:sky_tools/src/run_mojo.dart';
+import 'package:sky_tools/src/stop.dart';
 
 class FlutterCommandRunner extends CommandRunner {
-  FlutterCommandRunner() : super('flutter', 'Manage your flutter app development.') {
+  FlutterCommandRunner()
+      : super('flutter', 'Manage your flutter app development.') {
     argParser.addFlag('verbose',
         abbr: 'v',
         negatable: false,
@@ -41,7 +43,8 @@
             'not set. Note that release is not compatible with the listen command '
             'on iOS devices and simulators. Not normally required.');
     argParser.addOption('sky-src-path',
-        help: 'Path to your Sky src directory, if you are building Sky locally. '
+        help:
+            'Path to your Sky src directory, if you are building Sky locally. '
             'Ignored if neither debug nor release is set. Not normally required.');
     argParser.addOption('android-debug-build-path',
         help:
@@ -120,8 +123,8 @@
       ApplicationPackageFactory.defaultBuildType = BuildType.release;
       ApplicationPackageFactory.setBuildPath(BuildType.release,
           BuildPlatform.android, results['android-release-build-path']);
-      ApplicationPackageFactory.setBuildPath(BuildType.release, BuildPlatform.iOS,
-          results['ios-release-build-path']);
+      ApplicationPackageFactory.setBuildPath(BuildType.release,
+          BuildPlatform.iOS, results['ios-release-build-path']);
       ApplicationPackageFactory.setBuildPath(BuildType.release,
           BuildPlatform.iOSSimulator, results['ios-sim-release-build-path']);
     }
@@ -141,6 +144,7 @@
   });
 
   new FlutterCommandRunner()
+    ..addCommand(new StopCommand())
     ..addCommand(new BuildCommand())
     ..addCommand(new CacheCommand())
     ..addCommand(new InitCommand())
diff --git a/packages/flutter_tools/lib/src/application_package.dart b/packages/flutter_tools/lib/src/application_package.dart
index 37b7e5e..0739f77 100644
--- a/packages/flutter_tools/lib/src/application_package.dart
+++ b/packages/flutter_tools/lib/src/application_package.dart
@@ -29,10 +29,16 @@
 
 class AndroidApk extends ApplicationPackage {
   static const String _apkName = 'SkyShell.apk';
-  static const String _androidPackage = 'org.domokit.sky.shell';
+  static const String _packageID = 'org.domokit.sky.shell';
+  static const String _componentID = '$_packageID/$_packageID.SkyActivity';
 
+  /// The path to the activity that should be launched.
+  /// Defaults to 'org.domokit.sky.shell/org.domokit.sky.shell.SkyActivity'
+  String component;
   AndroidApk(String appDir,
-      [String appPackageID = _androidPackage, String appFileName = _apkName])
+      {String appPackageID: _packageID,
+      String appFileName: _apkName,
+      this.component: _componentID})
       : super(path.join(appDir, 'apks'), appPackageID, appFileName);
 }
 
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index 85e66dd..22db7de 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -52,6 +52,7 @@
 
 class AndroidDevice extends _Device {
   static const String _ADB_PATH = 'adb';
+  static const String _serverPort = '9888';
 
   static const String className = 'AndroidDevice';
   static final String defaultDeviceID = 'default';
@@ -232,6 +233,31 @@
     return true;
   }
 
+
+  bool stop(AndroidApk apk) {
+    // Turn off reverse port forwarding
+    try {
+      runCheckedSync([adbPath, 'reverse', '--remove', 'tcp:$_serverPort']);
+    } catch (e) {}
+    // Stop the app
+    runCheckedSync([adbPath, 'shell', 'am', 'force-stop', apk.appPackageID]);
+    // Kill the server
+    try {
+      if (Platform.isMacOS) {
+        String pid = runCheckedSync(['lsof', '-i', ':$_serverPort', '-t']);
+        // Killing a pid with a shell command from within dart is hard,
+        // so use a library command, but it's still nice to give the
+        // equivalent command when doing verbose logging.
+        _logging.info('kill $pid');
+        Process.killPid(int.parse(pid));
+      } else {
+        runCheckedSync(['fuser', '-k', '$_serverPort/tcp']);
+      }
+    } catch (e) {}
+
+    return true;
+  }
+
   @override
   bool isConnected() => _hasValidAndroid;
 }
diff --git a/packages/flutter_tools/lib/src/stop.dart b/packages/flutter_tools/lib/src/stop.dart
new file mode 100644
index 0000000..e2f288c
--- /dev/null
+++ b/packages/flutter_tools/lib/src/stop.dart
@@ -0,0 +1,47 @@
+// Copyright 2015 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.
+
+library sky_tools.stop;
+
+import 'dart:async';
+
+import 'package:args/command_runner.dart';
+import 'package:logging/logging.dart';
+import 'package:sky_tools/src/application_package.dart';
+import 'package:sky_tools/src/device.dart';
+
+final Logger _logging = new Logger('sky_tools.stop');
+
+class StopCommand extends Command {
+  final name = 'stop';
+  final description = 'Stop your Flutter app on all attached devices.';
+  AndroidDevice android = null;
+
+  StopCommand([this.android]) {
+    if (android == null) {
+      android = new AndroidDevice();
+    }
+  }
+
+  @override
+  Future<int> run() async {
+    if (stop()) {
+      return 0;
+    } else {
+      return 2;
+    }
+  }
+
+  bool stop() {
+    bool stoppedSomething = false;
+    if (android.isConnected()) {
+      Map<BuildPlatform, ApplicationPackage> packages =
+          ApplicationPackageFactory.getAvailableApplicationPackages();
+      ApplicationPackage androidApp = packages[BuildPlatform.android];
+      stoppedSomething = android.stop(androidApp) || stoppedSomething;
+    }
+
+    return stoppedSomething;
+  }
+}
diff --git a/packages/flutter_tools/test/stop_test.dart b/packages/flutter_tools/test/stop_test.dart
new file mode 100644
index 0000000..ff53eda
--- /dev/null
+++ b/packages/flutter_tools/test/stop_test.dart
@@ -0,0 +1,34 @@
+// Copyright 2015 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.
+
+library stop_test;
+
+import 'package:args/command_runner.dart';
+import 'package:mockito/mockito.dart';
+import 'package:sky_tools/src/application_package.dart';
+import 'package:sky_tools/src/stop.dart';
+import 'package:test/test.dart';
+
+import 'src/common.dart';
+
+main() => defineTests();
+
+defineTests() {
+  group('stop', () {
+    test('returns 0 when Android is connected and ready to be stopped', () {
+      ApplicationPackageFactory.srcPath = './';
+      ApplicationPackageFactory.setBuildPath(
+          BuildType.prebuilt, BuildPlatform.android, './');
+
+      MockAndroidDevice android = new MockAndroidDevice();
+      when(android.isConnected()).thenReturn(true);
+      when(android.stop(any)).thenReturn(true);
+      StopCommand command = new StopCommand(android);
+
+      CommandRunner runner = new CommandRunner('test_flutter', '')
+        ..addCommand(command);
+      runner.run(['stop']).then((int code) => expect(code, equals(0)));
+    });
+  });
+}