Chromium Code Reviews| 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:io"; | 11 import "dart:io"; |
| 11 | 12 |
| 12 import "path.dart"; | 13 import "path.dart"; |
| 13 import "utils.dart"; | 14 import "utils.dart"; |
| 14 | 15 |
| 15 Future _executeCommand(String executable, List<String> args, | 16 class AdbCommandResult { |
| 16 [String stdin = ""]) { | 17 final String command; |
| 17 return _executeCommandRaw(executable, args, stdin).then((results) => null); | 18 final String stdout; |
| 18 } | 19 final String stderr; |
| 20 final int exitCode; | |
| 19 | 21 |
| 20 Future _executeCommandGetOutput(String executable, List<String> args, | 22 AdbCommandResult(this.command, this.stdout, this.stderr, this.exitCode); |
| 21 [String stdin = ""]) { | 23 |
| 22 return _executeCommandRaw(executable, args, stdin).then((output) => output); | 24 void throwIfFailed() { |
| 25 if (exitCode != 0) { | |
| 26 var error = "Running: $command failed:" | |
| 27 "stdout: \n $stdout" | |
| 28 "stderr: \n $stderr" | |
| 29 "exitCode: \n $exitCode"; | |
| 30 throw new Exception(error); | |
| 31 } | |
| 32 } | |
| 23 } | 33 } |
| 24 | 34 |
| 25 /** | 35 /** |
| 26 * [_executeCommandRaw] will write [stdin] to the standard input of the created | 36 * [_executeCommand] will write [stdin] to the standard input of the created |
| 27 * process and will return a tuple (stdout, stderr). | 37 * process and will return a tuple (stdout, stderr). |
| 28 * | 38 * |
| 29 * If the exit code of the process was nonzero it will complete with an error. | 39 * If the exit code of the process was nonzero it will complete with an error. |
| 30 * If starting the process failed, it will complete with an error as well. | 40 * If starting the process failed, it will complete with an error as well. |
| 31 */ | 41 */ |
| 32 Future _executeCommandRaw(String executable, List<String> args, | 42 Future<AdbCommandResult> _executeCommand( |
| 33 [String stdin = ""]) { | 43 String executable, List<String> args, [String stdin = ""]) { |
| 34 Future<String> getOutput(Stream<List<int>> stream) { | 44 Future<String> getOutput(Stream<List<int>> stream) { |
| 35 return stream | 45 return stream |
| 36 .transform(UTF8.decoder) | 46 .transform(UTF8.decoder) |
| 37 .toList() | 47 .toList() |
| 38 .then((data) => data.join("")); | 48 .then((data) => data.join("")); |
| 39 } | 49 } |
| 40 | 50 |
| 41 DebugLogger.info("Running: '\$ $executable ${args.join(' ')}'"); | 51 return Process.start(executable, args).then((Process process) async { |
| 42 return Process.start(executable, args).then((Process process) { | |
| 43 if (stdin != null && stdin != '') { | 52 if (stdin != null && stdin != '') { |
| 44 process.stdin.write(stdin); | 53 process.stdin.write(stdin); |
| 45 } | 54 } |
| 46 process.stdin.close(); | 55 process.stdin.close(); |
| 47 | 56 |
| 48 var futures = [ | 57 var results = await Future.wait([ |
| 49 getOutput(process.stdout), | 58 getOutput(process.stdout), |
| 50 getOutput(process.stderr), | 59 getOutput(process.stderr), |
| 51 process.exitCode | 60 process.exitCode |
| 52 ]; | 61 ]); |
| 53 return Future.wait(futures).then((results) { | 62 String command = '$executable ${args.join(' ')}'; |
|
Bill Hesse
2016/04/27 13:54:40
It may be legal to have ' inside ' because it is i
kustermann
2016/05/02 10:40:23
Done.
| |
| 54 bool success = results[2] == 0; | 63 return new AdbCommandResult(command, results[0], results[1], results[2]); |
| 55 if (!success) { | |
| 56 var error = "Running: '\$ $executable ${args.join(' ')}' failed:" | |
| 57 "stdout: \n ${results[0]}" | |
| 58 "stderr: \n ${results[1]}" | |
| 59 "exitCode: \n ${results[2]}"; | |
| 60 throw new Exception(error); | |
| 61 } else { | |
| 62 DebugLogger.info("Success: $executable finished"); | |
| 63 } | |
| 64 return results[0]; | |
| 65 }); | |
| 66 }); | 64 }); |
| 67 } | 65 } |
| 68 | 66 |
| 69 /** | 67 /** |
| 70 * Helper class to loop through all adb ports. | 68 * Helper class to loop through all adb ports. |
| 71 * | 69 * |
| 72 * The ports come in pairs: | 70 * The ports come in pairs: |
| 73 * - even number: console connection | 71 * - even number: console connection |
| 74 * - odd number: adb connection | 72 * - odd number: adb connection |
| 75 * Note that this code doesn't check if the ports are used. | 73 * Note that this code doesn't check if the ports are used. |
| (...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 145 | 143 |
| 146 void log(String msg) { | 144 void log(String msg) { |
| 147 DebugLogger.info("AndroidEmulator(${_adbDevice.deviceId}): $msg"); | 145 DebugLogger.info("AndroidEmulator(${_adbDevice.deviceId}): $msg"); |
| 148 } | 146 } |
| 149 } | 147 } |
| 150 | 148 |
| 151 /** | 149 /** |
| 152 * Helper class to create avd device configurations. | 150 * Helper class to create avd device configurations. |
| 153 */ | 151 */ |
| 154 class AndroidHelper { | 152 class AndroidHelper { |
| 155 static Future createAvd(String name, String target) { | 153 static Future createAvd(String name, String target) async { |
| 156 var args = [ | 154 var args = [ |
| 157 '--silent', | 155 '--silent', |
| 158 'create', | 156 'create', |
| 159 'avd', | 157 'avd', |
| 160 '--name', | 158 '--name', |
| 161 '$name', | 159 '$name', |
| 162 '--target', | 160 '--target', |
| 163 '$target', | 161 '$target', |
| 164 '--force', | 162 '--force', |
| 165 '--abi', | 163 '--abi', |
| 166 'armeabi-v7a' | 164 'armeabi-v7a' |
| 167 ]; | 165 ]; |
| 168 // We're adding newlines to stdin to simulate <enter>. | 166 // We're adding newlines to stdin to simulate <enter>. |
| 169 return _executeCommand("android", args, "\n\n\n\n"); | 167 var result = await _executeCommand("android", args, "\n\n\n\n"); |
| 168 result.throwIfFailed(); | |
| 170 } | 169 } |
| 171 } | 170 } |
| 172 | 171 |
| 173 /** | 172 /** |
| 174 * Used for communicating with an emulator or with a real device. | 173 * Used for communicating with an emulator or with a real device. |
| 175 */ | 174 */ |
| 176 class AdbDevice { | 175 class AdbDevice { |
| 177 static const _adbServerStartupTime = const Duration(seconds: 3); | 176 static const _adbServerStartupTime = const Duration(seconds: 3); |
| 178 String _deviceId; | 177 String _deviceId; |
| 179 | 178 |
| 180 String get deviceId => _deviceId; | 179 String get deviceId => _deviceId; |
| 181 | 180 |
| 182 AdbDevice(this._deviceId); | 181 AdbDevice(this._deviceId); |
| 183 | 182 |
| 184 /** | 183 /** |
| 185 * Blocks execution until the device is online | 184 * Blocks execution until the device is online |
| 186 */ | 185 */ |
| 187 Future waitForDevice() { | 186 Future waitForDevice() { |
| 188 return _adbCommand(['wait-for-device']); | 187 return _adbCommand(['wait-for-device']); |
| 189 } | 188 } |
| 190 | 189 |
| 191 /** | 190 /** |
| 192 * Polls the 'sys.boot_completed' property. Returns as soon as the property is | 191 * Polls the 'sys.boot_completed' property. Returns as soon as the property is |
| 193 * 1. | 192 * 1. |
| 194 */ | 193 */ |
| 195 Future waitForBootCompleted() { | 194 Future waitForBootCompleted() async { |
| 196 var timeout = const Duration(seconds: 2); | 195 while (true) { |
| 197 var completer = new Completer(); | 196 try { |
| 198 | 197 AdbCommandResult result = |
| 199 checkUntilBooted() { | 198 await _adbCommand(['shell', 'getprop', 'sys.boot_completed']); |
| 200 _adbCommandGetOutput(['shell', 'getprop', 'sys.boot_completed']) | 199 if (result.stdout.trim() == '1') return; |
| 201 .then((String stdout) { | 200 } catch (_) { } |
| 202 stdout = stdout.trim(); | 201 await new Future.delayed(const Duration(seconds: 2)); |
| 203 if (stdout == '1') { | |
| 204 completer.complete(); | |
| 205 } else { | |
| 206 new Timer(timeout, checkUntilBooted); | |
| 207 } | |
| 208 }).catchError((error) { | |
| 209 new Timer(timeout, checkUntilBooted); | |
| 210 }); | |
| 211 } | 202 } |
| 212 checkUntilBooted(); | |
| 213 return completer.future; | |
| 214 } | 203 } |
| 215 | 204 |
| 216 /** | 205 /** |
| 217 * Put adb in root mode. | 206 * Put adb in root mode. |
| 218 */ | 207 */ |
| 219 Future adbRoot() { | 208 Future adbRoot() { |
| 220 var adbRootCompleter = new Completer(); | 209 var adbRootCompleter = new Completer(); |
| 221 _adbCommand(['root']).then((_) { | 210 _adbCommand(['root']).then((_) { |
| 222 // TODO: Figure out a way to wait until the adb daemon was restarted in | 211 // TODO: Figure out a way to wait until the adb daemon was restarted in |
| 223 // 'root mode' on the device. | 212 // 'root mode' on the device. |
| (...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 292 } | 281 } |
| 293 | 282 |
| 294 /** | 283 /** |
| 295 * Kill all background processes. | 284 * Kill all background processes. |
| 296 */ | 285 */ |
| 297 Future killAll() { | 286 Future killAll() { |
| 298 var arguments = ['shell', 'am', 'kill-all']; | 287 var arguments = ['shell', 'am', 'kill-all']; |
| 299 return _adbCommand(arguments); | 288 return _adbCommand(arguments); |
| 300 } | 289 } |
| 301 | 290 |
| 302 Future _adbCommand(List<String> adbArgs) { | 291 Future<AdbCommandResult> runAdbCommand(List<String> adbArgs) { |
| 292 return _executeCommand("adb", _deviceSpecificArgs(adbArgs)); | |
| 293 } | |
| 294 | |
| 295 Future<AdbCommandResult> runAdbShellCommand(List<String> shellArgs) async { | |
| 296 const MARKER = 'AdbShellExitCode: '; | |
| 297 | |
| 298 // The exitcode of 'adb shell ...' can be 0 even though the command failed | |
| 299 // with a non-zero exit code. We therefore explicitly print it to stdout and | |
| 300 // search for it. | |
| 301 | |
| 302 var args = ['shell', | |
| 303 "${shellArgs.join(' ')} ; echo $MARKER \$?"]; | |
| 304 AdbCommandResult result = await _executeCommand( | |
| 305 "adb", _deviceSpecificArgs(args)); | |
| 306 int exitCode = result.exitCode; | |
| 307 var lines = result | |
| 308 .stdout.split('\n') | |
| 309 .where((line) => line.trim().length > 0) | |
| 310 .toList(); | |
| 311 if (lines.length > 0) { | |
| 312 int index = lines.last.indexOf(MARKER); | |
| 313 assert(index >= 0); | |
| 314 exitCode = int.parse(lines.last.substring(index + MARKER.length).trim()); | |
| 315 } | |
| 316 return new AdbCommandResult( | |
| 317 result.command, result.stdout, result.stderr, exitCode); | |
| 318 } | |
| 319 | |
| 320 Future<AdbCommandResult> _adbCommand(List<String> adbArgs) async { | |
| 321 var result = await _executeCommand("adb", _deviceSpecificArgs(adbArgs)); | |
| 322 result.throwIfFailed(); | |
| 323 return result; | |
| 324 } | |
| 325 | |
| 326 List<String> _deviceSpecificArgs(List<String> adbArgs) { | |
| 303 if (_deviceId != null) { | 327 if (_deviceId != null) { |
| 304 var extendedAdbArgs = ['-s', _deviceId]; | 328 var extendedAdbArgs = ['-s', _deviceId]; |
| 305 extendedAdbArgs.addAll(adbArgs); | 329 extendedAdbArgs.addAll(adbArgs); |
| 306 adbArgs = extendedAdbArgs; | 330 adbArgs = extendedAdbArgs; |
| 307 } | 331 } |
| 308 return _executeCommand("adb", adbArgs); | 332 return adbArgs; |
| 309 } | |
| 310 | |
| 311 Future<String> _adbCommandGetOutput(List<String> adbArgs) { | |
| 312 if (_deviceId != null) { | |
| 313 var extendedAdbArgs = ['-s', _deviceId]; | |
| 314 extendedAdbArgs.addAll(adbArgs); | |
| 315 adbArgs = extendedAdbArgs; | |
| 316 } | |
| 317 return _executeCommandGetOutput("adb", adbArgs); | |
| 318 } | 333 } |
| 319 } | 334 } |
| 320 | 335 |
| 321 /** | 336 /** |
| 322 * Helper to list all adb devices available. | 337 * Helper to list all adb devices available. |
| 323 */ | 338 */ |
| 324 class AdbHelper { | 339 class AdbHelper { |
| 325 static RegExp _deviceLineRegexp = | 340 static RegExp _deviceLineRegexp = |
| 326 new RegExp(r'^([a-zA-Z0-9_-]+)[ \t]+device$', multiLine: true); | 341 new RegExp(r'^([a-zA-Z0-9_-]+)[ \t]+device$', multiLine: true); |
| 327 | 342 |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 343 * Represents an android intent. | 358 * Represents an android intent. |
| 344 */ | 359 */ |
| 345 class Intent { | 360 class Intent { |
| 346 String action; | 361 String action; |
| 347 String package; | 362 String package; |
| 348 String activity; | 363 String activity; |
| 349 String dataUri; | 364 String dataUri; |
| 350 | 365 |
| 351 Intent(this.action, this.package, this.activity, [this.dataUri]); | 366 Intent(this.action, this.package, this.activity, [this.dataUri]); |
| 352 } | 367 } |
| 368 | |
| 369 /** | |
| 370 * Discovers all available devices and supports acquire/release. | |
| 371 */ | |
| 372 class AdbDevicePool { | |
| 373 final Queue<AdbDevice> _idleDevices = new Queue<AdbDevice>(); | |
| 374 final Queue<Completer> _waiter = new Queue<Completer>(); | |
| 375 | |
| 376 AdbDevicePool(List<AdbDevice> idleDevices) { | |
| 377 _idleDevices.addAll(idleDevices); | |
| 378 } | |
| 379 | |
| 380 static Future<AdbDevicePool> create() async { | |
| 381 var names = await AdbHelper.listDevices(); | |
| 382 var devices = names.map((id) => new AdbDevice(id)).toList(); | |
| 383 if (devices.length == 0) { | |
| 384 throw new Exception( | |
| 385 'No android devices found. ' | |
| 386 'Please make sure "adb devices" shows your device!'); | |
| 387 } | |
| 388 return new AdbDevicePool(devices); | |
| 389 } | |
| 390 | |
| 391 Future<AdbDevice> acquireDevice() async { | |
| 392 if (_idleDevices.length > 0) { | |
| 393 return _idleDevices.removeFirst(); | |
| 394 } else { | |
| 395 var completer = new Completer(); | |
| 396 _waiter.add(completer); | |
| 397 return completer.future; | |
| 398 } | |
| 399 } | |
| 400 | |
| 401 void releaseDevice(AdbDevice device) { | |
| 402 if (_waiter.length > 0) { | |
| 403 Completer completer = _waiter.removeFirst(); | |
| 404 completer.complete(device); | |
| 405 } else { | |
| 406 _idleDevices.add(device); | |
| 407 } | |
| 408 } | |
| 409 } | |
| OLD | NEW |