diff --git a/packages/flutter_tools/lib/src/build_configuration.dart b/packages/flutter_tools/lib/src/build_configuration.dart index 5fc0356495..1d74bd2e9a 100644 --- a/packages/flutter_tools/lib/src/build_configuration.dart +++ b/packages/flutter_tools/lib/src/build_configuration.dart @@ -37,15 +37,19 @@ HostPlatform getCurrentHostPlatform() { } class BuildConfiguration { - BuildConfiguration.prebuilt({ this.hostPlatform, this.targetPlatform }) - : type = BuildType.prebuilt, buildDir = null; + BuildConfiguration.prebuilt({ + this.hostPlatform, + this.targetPlatform, + this.deviceId + }) : type = BuildType.prebuilt, buildDir = null; BuildConfiguration.local({ this.type, this.hostPlatform, this.targetPlatform, String enginePath, - String buildPath + String buildPath, + this.deviceId }) : buildDir = path.normalize(path.join(enginePath, buildPath)) { assert(type == BuildType.debug || type == BuildType.release); } @@ -54,4 +58,5 @@ class BuildConfiguration { final HostPlatform hostPlatform; final TargetPlatform targetPlatform; final String buildDir; + final String deviceId; } diff --git a/packages/flutter_tools/lib/src/commands/flutter_command_runner.dart b/packages/flutter_tools/lib/src/commands/flutter_command_runner.dart index 8f0b3891c9..6d54a3b84e 100644 --- a/packages/flutter_tools/lib/src/commands/flutter_command_runner.dart +++ b/packages/flutter_tools/lib/src/commands/flutter_command_runner.dart @@ -29,6 +29,8 @@ class FlutterCommandRunner extends CommandRunner { 'shell commands executed.'); argParser.addOption('package-root', help: 'Path to your packages directory.', defaultsTo: 'packages'); + argParser.addOption('android-device-id', + help: 'Serial number of the target Android device.'); argParser.addSeparator('Local build selection options:'); argParser.addFlag('debug', @@ -143,7 +145,10 @@ class FlutterCommandRunner extends CommandRunner { if (enginePath == null) { configs.add(new BuildConfiguration.prebuilt( - hostPlatform: hostPlatform, targetPlatform: TargetPlatform.android)); + hostPlatform: hostPlatform, + targetPlatform: TargetPlatform.android, + deviceId: globalResults['android-device-id'] + )); } else { if (!FileSystemEntity.isDirectorySync(enginePath)) _logging.warning('$enginePath is not a valid directory'); @@ -157,7 +162,8 @@ class FlutterCommandRunner extends CommandRunner { hostPlatform: hostPlatform, targetPlatform: TargetPlatform.android, enginePath: enginePath, - buildPath: globalResults['android-debug-build-path'] + buildPath: globalResults['android-debug-build-path'], + deviceId: globalResults['android-device-id'] )); if (Platform.isMacOS) { @@ -185,7 +191,8 @@ class FlutterCommandRunner extends CommandRunner { hostPlatform: hostPlatform, targetPlatform: TargetPlatform.android, enginePath: enginePath, - buildPath: globalResults['android-release-build-path'] + buildPath: globalResults['android-release-build-path'], + deviceId: globalResults['android-device-id'] )); if (Platform.isMacOS) { diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart index 3dfe573bfb..c968e3c859 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart @@ -603,6 +603,15 @@ class AndroidDevice extends Device { } } + List adbCommandForDevice(List args) { + List result = [adbPath]; + if (id != defaultDeviceID) { + result.addAll(['-s', id]); + } + result.addAll(args); + return result; + } + bool _isValidAdbVersion(String adbVersion) { // Sample output: 'Android Debug Bridge version 1.0.31' Match versionFields = @@ -655,9 +664,9 @@ class AndroidDevice extends Device { // output lines like this, which we want to ignore: // adb server is out of date. killing.. // * daemon started successfully * - runCheckedSync([adbPath, 'start-server']); + runCheckedSync(adbCommandForDevice(['start-server'])); - String ready = runSync([adbPath, 'shell', 'echo', 'ready']); + String ready = runSync(adbCommandForDevice(['shell', 'echo', 'ready'])); if (ready.trim() != 'ready') { _logging.info('Android device not found.'); return false; @@ -665,7 +674,7 @@ class AndroidDevice extends Device { // Sample output: '22' String sdkVersion = - runCheckedSync([adbPath, 'shell', 'getprop', 'ro.build.version.sdk']) + runCheckedSync(adbCommandForDevice(['shell', 'getprop', 'ro.build.version.sdk'])) .trimRight(); int sdkVersionParsed = @@ -699,7 +708,7 @@ class AndroidDevice extends Device { } String _getDeviceApkSha1(ApplicationPackage app) { - return runCheckedSync([adbPath, 'shell', 'cat', _getDeviceSha1Path(app)]); + return runCheckedSync(adbCommandForDevice(['shell', 'cat', _getDeviceSha1Path(app)])); } String _getSourceSha1(ApplicationPackage app) { @@ -721,7 +730,7 @@ class AndroidDevice extends Device { if (!isConnected()) { return false; } - if (runCheckedSync([adbPath, 'shell', 'pm', 'path', app.id]) == + if (runCheckedSync(adbCommandForDevice(['shell', 'pm', 'path', app.id])) == '') { _logging.info( 'TODO(iansf): move this log to the caller. ${app.name} is not on the device. Installing now...'); @@ -747,16 +756,16 @@ class AndroidDevice extends Device { } print('Installing ${app.name} on device.'); - runCheckedSync([adbPath, 'install', '-r', app.localPath]); - runCheckedSync([adbPath, 'shell', 'run-as', app.id, 'chmod', '777', _getDeviceDataPath(app)]); - runCheckedSync([adbPath, 'shell', 'echo', '-n', _getSourceSha1(app), '>', _getDeviceSha1Path(app)]); + runCheckedSync(adbCommandForDevice(['install', '-r', app.localPath])); + runCheckedSync(adbCommandForDevice(['shell', 'run-as', app.id, 'chmod', '777', _getDeviceDataPath(app)])); + runCheckedSync(adbCommandForDevice(['shell', 'echo', '-n', _getSourceSha1(app), '>', _getDeviceSha1Path(app)])); return true; } void _forwardObservatoryPort() { // Set up port forwarding for observatory. String portString = 'tcp:$_observatoryPort'; - runCheckedSync([adbPath, 'forward', portString, portString]); + runCheckedSync(adbCommandForDevice(['forward', portString, portString])); } bool startBundle(AndroidApk apk, String bundlePath, bool poke, bool checked) { @@ -770,14 +779,13 @@ class AndroidDevice extends Device { String deviceTmpPath = '/data/local/tmp/dev.flx'; String deviceBundlePath = _getDeviceBundlePath(apk); - runCheckedSync([adbPath, 'push', bundlePath, deviceTmpPath]); - runCheckedSync([adbPath, 'shell', 'mv', deviceTmpPath, deviceBundlePath]); - List cmd = [ - adbPath, + runCheckedSync(adbCommandForDevice(['push', bundlePath, deviceTmpPath])); + runCheckedSync(adbCommandForDevice(['shell', 'mv', deviceTmpPath, deviceBundlePath])); + List cmd = adbCommandForDevice([ 'shell', 'am', 'start', '-a', 'android.intent.action.RUN', '-d', deviceBundlePath, - ]; + ]); if (checked) cmd.addAll(['--ez', 'enable-checked-mode', 'true']); cmd.add(apk.launchActivity); @@ -821,7 +829,7 @@ class AndroidDevice extends Device { // Set up reverse port-forwarding so that the Android app can reach the // server running on localhost. String serverPortString = 'tcp:$_serverPort'; - runCheckedSync([adbPath, 'reverse', serverPortString, serverPortString]); + runCheckedSync(adbCommandForDevice(['reverse', serverPortString, serverPortString])); } String relativeDartMain = _convertToURL(path.relative(mainDart, from: serverRoot)); @@ -830,12 +838,11 @@ class AndroidDevice extends Device { url += '?rand=${new Random().nextDouble()}'; // Actually launch the app on Android. - List cmd = [ - adbPath, + List cmd = adbCommandForDevice([ 'shell', 'am', 'start', '-a', 'android.intent.action.VIEW', '-d', url, - ]; + ]); if (checked) cmd.addAll(['--ez', 'enable-checked-mode', 'true']); cmd.add(apk.launchActivity); @@ -854,9 +861,9 @@ class AndroidDevice extends Device { final AndroidApk apk = app; // Turn off reverse port forwarding - runSync([adbPath, 'reverse', '--remove', 'tcp:$_serverPort']); + runSync(adbCommandForDevice(['reverse', '--remove', 'tcp:$_serverPort'])); // Stop the app - runSync([adbPath, 'shell', 'am', 'force-stop', apk.id]); + runSync(adbCommandForDevice(['shell', 'am', 'force-stop', apk.id])); // Kill the server osUtils.killTcpPortListeners(_serverPort); @@ -868,7 +875,7 @@ class AndroidDevice extends Device { TargetPlatform get platform => TargetPlatform.android; void clearLogs() { - runSync([adbPath, 'logcat', '-c']); + runSync(adbCommandForDevice(['logcat', '-c'])); } Future logs({bool clear: false}) async { @@ -880,8 +887,7 @@ class AndroidDevice extends Device { clearLogs(); } - return runCommandAndStreamOutput([ - adbPath, + return runCommandAndStreamOutput(adbCommandForDevice([ 'logcat', '-v', 'tag', // Only log the tag and the message @@ -890,30 +896,28 @@ class AndroidDevice extends Device { 'chromium:D', 'ActivityManager:W', '*:F', - ], prefix: 'android: '); + ]), prefix: 'android: '); } void startTracing(AndroidApk apk) { - runCheckedSync([ - adbPath, + runCheckedSync(adbCommandForDevice([ 'shell', 'am', 'broadcast', '-a', '${apk.id}.TRACING_START' - ]); + ])); } String stopTracing(AndroidApk apk) { clearLogs(); - runCheckedSync([ - adbPath, + runCheckedSync(adbCommandForDevice([ 'shell', 'am', 'broadcast', '-a', '${apk.id}.TRACING_STOP' - ]); + ])); RegExp traceRegExp = new RegExp(r'Saving trace to (\S+)', multiLine: true); RegExp completeRegExp = new RegExp(r'Trace complete', multiLine: true); @@ -921,7 +925,7 @@ class AndroidDevice extends Device { String tracePath = null; bool isComplete = false; while (!isComplete) { - String logs = runSync([adbPath, 'logcat', '-d']); + String logs = runSync(adbCommandForDevice(['logcat', '-d'])); Match fileMatch = traceRegExp.firstMatch(logs); if (fileMatch[1] != null) { tracePath = fileMatch[1]; @@ -930,9 +934,9 @@ class AndroidDevice extends Device { } if (tracePath != null) { - runSync([adbPath, 'shell', 'run-as', apk.id, 'chmod', '777', tracePath]); - runSync([adbPath, 'pull', tracePath]); - runSync([adbPath, 'shell', 'rm', tracePath]); + runSync(adbCommandForDevice(['shell', 'run-as', apk.id, 'chmod', '777', tracePath])); + runSync(adbCommandForDevice(['pull', tracePath])); + runSync(adbCommandForDevice(['shell', 'rm', tracePath])); return path.basename(tracePath); } _logging.warning('No trace file detected. ' @@ -975,7 +979,19 @@ class DeviceStore { switch (config.targetPlatform) { case TargetPlatform.android: assert(android == null); - android = new AndroidDevice(); + List androidDevices = AndroidDevice.getAttachedDevices(); + if (config.deviceId != null) { + android = androidDevices.firstWhere( + (AndroidDevice dev) => (dev.id == config.deviceId), + orElse: () => null); + if (android == null) { + print('Warning: Device ID ${config.deviceId} not found'); + } + } else if (androidDevices.length == 1) { + android = androidDevices[0]; + } else if (androidDevices.length > 1) { + print('Warning: Multiple Android devices are connected, but no device ID was specified.'); + } break; case TargetPlatform.iOS: assert(iOS == null);