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 |