| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library android; | 5 library android; |
| 6 | 6 |
| 7 import "dart:async"; | 7 import "dart:async"; |
| 8 import "dart:convert" show LineSplitter, UTF8; | 8 import "dart:convert" show LineSplitter, UTF8; |
| 9 import "dart:core"; | 9 import "dart:core"; |
| 10 import "dart:collection"; | 10 import "dart:collection"; |
| 11 import "dart:io"; | 11 import "dart:io"; |
| 12 | 12 |
| 13 import "path.dart"; | 13 import "path.dart"; |
| 14 import "utils.dart"; | 14 import "utils.dart"; |
| 15 | 15 |
| 16 class AdbCommandResult { | 16 class AdbCommandResult { |
| 17 final String command; | 17 final String command; |
| 18 final String stdout; | 18 final String stdout; |
| 19 final String stderr; | 19 final String stderr; |
| 20 final int exitCode; | 20 final int exitCode; |
| 21 final bool timedOut; | 21 final bool timedOut; |
| 22 | 22 |
| 23 AdbCommandResult(this.command, this.stdout, this.stderr, this.exitCode, | 23 AdbCommandResult( |
| 24 this.timedOut); | 24 this.command, this.stdout, this.stderr, this.exitCode, this.timedOut); |
| 25 | 25 |
| 26 void throwIfFailed() { | 26 void throwIfFailed() { |
| 27 if (exitCode != 0) { | 27 if (exitCode != 0) { |
| 28 var error = "Running: $command failed:" | 28 var error = "Running: $command failed:" |
| 29 "stdout:\n ${stdout.trim()}\n" | 29 "stdout:\n ${stdout.trim()}\n" |
| 30 "stderr:\n ${stderr.trim()}\n" | 30 "stderr:\n ${stderr.trim()}\n" |
| 31 "exitCode: $exitCode\n" | 31 "exitCode: $exitCode\n" |
| 32 "timedOut: $timedOut"; | 32 "timedOut: $timedOut"; |
| 33 throw new Exception(error); | 33 throw new Exception(error); |
| 34 } | 34 } |
| 35 } | 35 } |
| 36 } | 36 } |
| 37 | 37 |
| 38 /** | 38 /** |
| 39 * [_executeCommand] will write [stdin] to the standard input of the created | 39 * [_executeCommand] will write [stdin] to the standard input of the created |
| 40 * process and will return a tuple (stdout, stderr). | 40 * process and will return a tuple (stdout, stderr). |
| 41 * | 41 * |
| 42 * If the exit code of the process was nonzero it will complete with an error. | 42 * If the exit code of the process was nonzero it will complete with an error. |
| 43 * If starting the process failed, it will complete with an error as well. | 43 * If starting the process failed, it will complete with an error as well. |
| 44 */ | 44 */ |
| 45 Future<AdbCommandResult> _executeCommand( | 45 Future<AdbCommandResult> _executeCommand(String executable, List<String> args, |
| 46 String executable, List<String> args, | |
| 47 {String stdin, Duration timeout}) { | 46 {String stdin, Duration timeout}) { |
| 48 Future<String> getOutput(Stream<List<int>> stream) { | 47 Future<String> getOutput(Stream<List<int>> stream) { |
| 49 return stream | 48 return stream |
| 50 .transform(UTF8.decoder) | 49 .transform(UTF8.decoder) |
| 51 .toList() | 50 .toList() |
| 52 .then((data) => data.join("")); | 51 .then((data) => data.join("")); |
| 53 } | 52 } |
| 54 | 53 |
| 55 return Process.start(executable, args).then((Process process) async { | 54 return Process.start(executable, args).then((Process process) async { |
| 56 if (stdin != null && stdin != '') { | 55 if (stdin != null && stdin != '') { |
| 57 process.stdin.write(stdin); | 56 process.stdin.write(stdin); |
| 58 await process.stdin.flush(); | 57 await process.stdin.flush(); |
| 59 } | 58 } |
| 60 process.stdin.close(); | 59 process.stdin.close(); |
| 61 | 60 |
| 62 Timer timer; | 61 Timer timer; |
| 63 bool timedOut = false; | 62 bool timedOut = false; |
| 64 if (timeout != null) { | 63 if (timeout != null) { |
| 65 timer = new Timer(timeout, () { | 64 timer = new Timer(timeout, () { |
| 66 timedOut = true; | 65 timedOut = true; |
| 67 process.kill(ProcessSignal.SIGTERM); | 66 process.kill(ProcessSignal.SIGTERM); |
| 68 timer = null; | 67 timer = null; |
| 69 }); | 68 }); |
| 70 } | 69 } |
| 71 | 70 |
| 72 var results = await Future.wait([ | 71 var results = await Future.wait([ |
| 73 getOutput(process.stdout), | 72 getOutput(process.stdout), |
| 74 getOutput(process.stderr), | 73 getOutput(process.stderr), |
| 75 process.exitCode | 74 process.exitCode |
| 76 ]); | 75 ]); |
| 77 if (timer != null) timer.cancel(); | 76 if (timer != null) timer.cancel(); |
| 78 | 77 |
| 79 String command = "$executable ${args.join(' ')}"; | 78 String command = "$executable ${args.join(' ')}"; |
| 80 return new AdbCommandResult( | 79 return new AdbCommandResult( |
| 81 command, results[0], results[1], results[2], timedOut); | 80 command, results[0], results[1], results[2], timedOut); |
| 82 }); | 81 }); |
| 83 } | 82 } |
| 84 | 83 |
| 85 /** | 84 /** |
| (...skipping 123 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 209 /** | 208 /** |
| 210 * Polls the 'sys.boot_completed' property. Returns as soon as the property is | 209 * Polls the 'sys.boot_completed' property. Returns as soon as the property is |
| 211 * 1. | 210 * 1. |
| 212 */ | 211 */ |
| 213 Future waitForBootCompleted() async { | 212 Future waitForBootCompleted() async { |
| 214 while (true) { | 213 while (true) { |
| 215 try { | 214 try { |
| 216 AdbCommandResult result = | 215 AdbCommandResult result = |
| 217 await _adbCommand(['shell', 'getprop', 'sys.boot_completed']); | 216 await _adbCommand(['shell', 'getprop', 'sys.boot_completed']); |
| 218 if (result.stdout.trim() == '1') return; | 217 if (result.stdout.trim() == '1') return; |
| 219 } catch (_) { } | 218 } catch (_) {} |
| 220 await new Future.delayed(const Duration(seconds: 2)); | 219 await new Future.delayed(const Duration(seconds: 2)); |
| 221 } | 220 } |
| 222 } | 221 } |
| 223 | 222 |
| 224 /** | 223 /** |
| 225 * Put adb in root mode. | 224 * Put adb in root mode. |
| 226 */ | 225 */ |
| 227 Future adbRoot() { | 226 Future adbRoot() { |
| 228 var adbRootCompleter = new Completer(); | 227 var adbRootCompleter = new Completer(); |
| 229 _adbCommand(['root']).then((_) { | 228 _adbCommand(['root']).then((_) { |
| (...skipping 17 matching lines...) Expand all Loading... |
| 247 Future pushData(Path local, Path remote) { | 246 Future pushData(Path local, Path remote) { |
| 248 return _adbCommand(['push', '$local', '$remote']); | 247 return _adbCommand(['push', '$local', '$remote']); |
| 249 } | 248 } |
| 250 | 249 |
| 251 /** | 250 /** |
| 252 * Upload data to the device, unless [local] is the same as the most recently | 251 * Upload data to the device, unless [local] is the same as the most recently |
| 253 * used source for [remote]. | 252 * used source for [remote]. |
| 254 */ | 253 */ |
| 255 Future pushCachedData(String local, String remote) { | 254 Future pushCachedData(String local, String remote) { |
| 256 if (_cachedData[remote] == local) { | 255 if (_cachedData[remote] == local) { |
| 257 return new Future.value(new AdbCommandResult( | 256 return new Future.value( |
| 258 "Skipped cached push", "", "", 0, false)); | 257 new AdbCommandResult("Skipped cached push", "", "", 0, false)); |
| 259 } | 258 } |
| 260 _cachedData[remote] = local; | 259 _cachedData[remote] = local; |
| 261 return _adbCommand(['push', local, remote]); | 260 return _adbCommand(['push', local, remote]); |
| 262 } | 261 } |
| 263 | 262 |
| 264 /** | 263 /** |
| 265 * Change permission of directory recursively. | 264 * Change permission of directory recursively. |
| 266 */ | 265 */ |
| 267 Future chmod(String mode, Path directory) { | 266 Future chmod(String mode, Path directory) { |
| 268 var arguments = ['shell', 'chmod', '-R', mode, '$directory']; | 267 var arguments = ['shell', 'chmod', '-R', mode, '$directory']; |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 314 | 313 |
| 315 /** | 314 /** |
| 316 * Kill all background processes. | 315 * Kill all background processes. |
| 317 */ | 316 */ |
| 318 Future killAll() { | 317 Future killAll() { |
| 319 var arguments = ['shell', 'am', 'kill-all']; | 318 var arguments = ['shell', 'am', 'kill-all']; |
| 320 return _adbCommand(arguments); | 319 return _adbCommand(arguments); |
| 321 } | 320 } |
| 322 | 321 |
| 323 Future<AdbCommandResult> runAdbCommand(List<String> adbArgs, | 322 Future<AdbCommandResult> runAdbCommand(List<String> adbArgs, |
| 324 {Duration timeout}) { | 323 {Duration timeout}) { |
| 325 return _executeCommand( | 324 return _executeCommand("adb", _deviceSpecificArgs(adbArgs), |
| 326 "adb", _deviceSpecificArgs(adbArgs), timeout: timeout); | 325 timeout: timeout); |
| 327 } | 326 } |
| 328 | 327 |
| 329 Future<AdbCommandResult> runAdbShellCommand(List<String> shellArgs, | 328 Future<AdbCommandResult> runAdbShellCommand(List<String> shellArgs, |
| 330 {Duration timeout}) async { | 329 {Duration timeout}) async { |
| 331 const MARKER = 'AdbShellExitCode: '; | 330 const MARKER = 'AdbShellExitCode: '; |
| 332 | 331 |
| 333 // The exitcode of 'adb shell ...' can be 0 even though the command failed | 332 // The exitcode of 'adb shell ...' can be 0 even though the command failed |
| 334 // with a non-zero exit code. We therefore explicitly print it to stdout and | 333 // with a non-zero exit code. We therefore explicitly print it to stdout and |
| 335 // search for it. | 334 // search for it. |
| 336 | 335 |
| 337 var args = ['shell', | 336 var args = ['shell', "${shellArgs.join(' ')} ; echo $MARKER \$?"]; |
| 338 "${shellArgs.join(' ')} ; echo $MARKER \$?"]; | |
| 339 AdbCommandResult result = await _executeCommand( | 337 AdbCommandResult result = await _executeCommand( |
| 340 "adb", _deviceSpecificArgs(args), timeout: timeout); | 338 "adb", _deviceSpecificArgs(args), |
| 339 timeout: timeout); |
| 341 int exitCode = result.exitCode; | 340 int exitCode = result.exitCode; |
| 342 var lines = result | 341 var lines = result.stdout |
| 343 .stdout.split('\n') | 342 .split('\n') |
| 344 .where((line) => line.trim().length > 0) | 343 .where((line) => line.trim().length > 0) |
| 345 .toList(); | 344 .toList(); |
| 346 if (lines.length > 0) { | 345 if (lines.length > 0) { |
| 347 int index = lines.last.indexOf(MARKER); | 346 int index = lines.last.indexOf(MARKER); |
| 348 if (index >= 0) { | 347 if (index >= 0) { |
| 349 exitCode = int.parse( | 348 exitCode = |
| 350 lines.last.substring(index + MARKER.length).trim()); | 349 int.parse(lines.last.substring(index + MARKER.length).trim()); |
| 351 if (exitCode > 128 && exitCode <= 128 + 31) { | 350 if (exitCode > 128 && exitCode <= 128 + 31) { |
| 352 // Return negative exit codes for signals 1..31 (128+N for signal N) | 351 // Return negative exit codes for signals 1..31 (128+N for signal N) |
| 353 exitCode = 128 - exitCode; | 352 exitCode = 128 - exitCode; |
| 354 } | 353 } |
| 355 } else { | 354 } else { |
| 356 // In case of timeouts, for example, we won't get the exitcode marker. | 355 // In case of timeouts, for example, we won't get the exitcode marker. |
| 357 assert(result.exitCode != 0); | 356 assert(result.exitCode != 0); |
| 358 } | 357 } |
| 359 } | 358 } |
| 360 return new AdbCommandResult( | 359 return new AdbCommandResult(result.command, result.stdout, result.stderr, |
| 361 result.command, result.stdout, result.stderr, exitCode, | 360 exitCode, result.timedOut); |
| 362 result.timedOut); | |
| 363 } | 361 } |
| 364 | 362 |
| 365 Future<AdbCommandResult> _adbCommand(List<String> adbArgs) async { | 363 Future<AdbCommandResult> _adbCommand(List<String> adbArgs) async { |
| 366 var result = await _executeCommand("adb", _deviceSpecificArgs(adbArgs)); | 364 var result = await _executeCommand("adb", _deviceSpecificArgs(adbArgs)); |
| 367 result.throwIfFailed(); | 365 result.throwIfFailed(); |
| 368 return result; | 366 return result; |
| 369 } | 367 } |
| 370 | 368 |
| 371 List<String> _deviceSpecificArgs(List<String> adbArgs) { | 369 List<String> _deviceSpecificArgs(List<String> adbArgs) { |
| 372 if (_deviceId != null) { | 370 if (_deviceId != null) { |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 419 final Queue<Completer> _waiter = new Queue<Completer>(); | 417 final Queue<Completer> _waiter = new Queue<Completer>(); |
| 420 | 418 |
| 421 AdbDevicePool(List<AdbDevice> idleDevices) { | 419 AdbDevicePool(List<AdbDevice> idleDevices) { |
| 422 _idleDevices.addAll(idleDevices); | 420 _idleDevices.addAll(idleDevices); |
| 423 } | 421 } |
| 424 | 422 |
| 425 static Future<AdbDevicePool> create() async { | 423 static Future<AdbDevicePool> create() async { |
| 426 var names = await AdbHelper.listDevices(); | 424 var names = await AdbHelper.listDevices(); |
| 427 var devices = names.map((id) => new AdbDevice(id)).toList(); | 425 var devices = names.map((id) => new AdbDevice(id)).toList(); |
| 428 if (devices.length == 0) { | 426 if (devices.length == 0) { |
| 429 throw new Exception( | 427 throw new Exception('No android devices found. ' |
| 430 'No android devices found. ' | |
| 431 'Please make sure "adb devices" shows your device!'); | 428 'Please make sure "adb devices" shows your device!'); |
| 432 } | 429 } |
| 433 print("Found ${devices.length} Android devices."); | 430 print("Found ${devices.length} Android devices."); |
| 434 return new AdbDevicePool(devices); | 431 return new AdbDevicePool(devices); |
| 435 } | 432 } |
| 436 | 433 |
| 437 Future<AdbDevice> acquireDevice() async { | 434 Future<AdbDevice> acquireDevice() async { |
| 438 if (_idleDevices.length > 0) { | 435 if (_idleDevices.length > 0) { |
| 439 return _idleDevices.removeFirst(); | 436 return _idleDevices.removeFirst(); |
| 440 } else { | 437 } else { |
| 441 var completer = new Completer(); | 438 var completer = new Completer(); |
| 442 _waiter.add(completer); | 439 _waiter.add(completer); |
| 443 return completer.future; | 440 return completer.future; |
| 444 } | 441 } |
| 445 } | 442 } |
| 446 | 443 |
| 447 void releaseDevice(AdbDevice device) { | 444 void releaseDevice(AdbDevice device) { |
| 448 if (_waiter.length > 0) { | 445 if (_waiter.length > 0) { |
| 449 Completer completer = _waiter.removeFirst(); | 446 Completer completer = _waiter.removeFirst(); |
| 450 completer.complete(device); | 447 completer.complete(device); |
| 451 } else { | 448 } else { |
| 452 _idleDevices.add(device); | 449 _idleDevices.add(device); |
| 453 } | 450 } |
| 454 } | 451 } |
| 455 } | 452 } |
| OLD | NEW |