diff --git a/packages/flutter_tools/bin/sky_tools.dart b/packages/flutter_tools/bin/sky_tools.dart index 12052a8b0a4..920ea5ecbbb 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/cache.dart'; 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 @@ class FlutterCommandRunner extends CommandRunner { '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 @@ class FlutterCommandRunner extends CommandRunner { 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 @@ void main(List args) { }); 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 37b7e5ed3f1..0739f77f0fb 100644 --- a/packages/flutter_tools/lib/src/application_package.dart +++ b/packages/flutter_tools/lib/src/application_package.dart @@ -29,10 +29,16 @@ abstract class ApplicationPackage { 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 85e66dd224a..22db7de79c5 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart @@ -52,6 +52,7 @@ abstract class _Device { 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 @@ class AndroidDevice extends _Device { 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 00000000000..e2f288c27ac --- /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 run() async { + if (stop()) { + return 0; + } else { + return 2; + } + } + + bool stop() { + bool stoppedSomething = false; + if (android.isConnected()) { + Map 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 00000000000..ff53eda3988 --- /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))); + }); + }); +}