Commit 6e0b59fc authored by Jason Simmons's avatar Jason Simmons

Add a flag that selects which Android device ID is the target for Flutter commands

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