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 |