Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 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 | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library android; | |
| 6 | |
| 7 import "dart:io"; | |
| 8 import "dart:async"; | |
| 9 import "dart:core"; | |
| 10 import "dart:utf"; | |
| 11 | |
| 12 import "utils.dart"; | |
| 13 | |
| 14 Future _executeCommand(String executable, | |
| 15 List<String> args, | |
| 16 [String stdin = ""]) { | |
| 17 return _executeCommandRaw(executable, args, stdin).then((results) => null); | |
| 18 } | |
| 19 | |
| 20 Future _executeCommandGetOutput(String executable, | |
| 21 List<String> args, | |
| 22 [String stdin = ""]) { | |
| 23 return _executeCommandRaw(executable, args, stdin) | |
| 24 .then((results) => results[0]); | |
| 25 } | |
| 26 | |
| 27 /** | |
| 28 * [_executeCommandRaw] will write [stdin] to the standard input of the created | |
| 29 * process and will return a tuple (stdout, stderr). | |
| 30 * | |
| 31 * If the exit code of the process was nonzero it will complete with an error. | |
| 32 * If starting the process failed, it will complete with an error as well. | |
| 33 */ | |
| 34 Future _executeCommandRaw(String executable, | |
| 35 List<String> args, | |
| 36 [String stdin = ""]) { | |
| 37 Future<String> getOutput(Stream<List<int>> stream) { | |
| 38 return stream.transform(new StringDecoder()) | |
| 39 .reduce(new StringBuffer(), (buf, data) { | |
| 40 buf.write(data); | |
|
ricow1
2013/05/21 18:30:43
indentation seems off here, hard to follow flow
kustermann
2013/05/22 09:30:58
Done.
| |
| 41 return buf; | |
| 42 }).then((buf) => buf.toString()); | |
| 43 } | |
| 44 | |
| 45 DebugLogger.info("Running: '\$ $executable ${args.join(' ')}'"); | |
| 46 return Process.start(executable, args).then((Process process) { | |
| 47 if (stdin != null && stdin != '') { | |
| 48 process.stdin.write(stdin); | |
| 49 } | |
| 50 process.stdin.close(); | |
| 51 | |
| 52 var futures = [getOutput(process.stdout), | |
| 53 getOutput(process.stderr), | |
| 54 process.exitCode]; | |
| 55 return Future.wait(futures).then((results) { | |
| 56 bool success = results[2] == 0; | |
| 57 if (!success) { | |
| 58 var error = """Running: '\$ $executable ${args.join(' ')}' failed: | |
| 59 stdout: \n ${results[0]} | |
|
ricow1
2013/05/21 18:30:43
this may look sort of strange due to the whitespac
kustermann
2013/05/22 09:30:58
Done.
| |
| 60 stderr: \n ${results[1]} | |
| 61 exitCode: \n ${results[2]} | |
| 62 """; | |
| 63 throw new Exception(error); | |
| 64 } | |
| 65 return [results[0], results[1]]; | |
|
ricow1
2013/05/21 18:30:43
how about a ProcessResult like class for returning
kustermann
2013/05/22 09:30:58
I think there is no public ProcessResult construct
ricow1
2013/05/22 13:13:36
That is why I wrote ProcessResult _like_ class :-)
| |
| 66 }); | |
| 67 }); | |
| 68 } | |
| 69 | |
| 70 /** | |
| 71 * Helper class to loop through all adb ports. | |
| 72 * | |
| 73 * The ports come in pairs: | |
| 74 * - even number: console connection | |
| 75 * - odd number: adb connection | |
| 76 * Note that this code doesn't check if the ports are used. | |
| 77 */ | |
| 78 class AdbServerPortPool { | |
| 79 static int MIN_PORT = 5554; | |
| 80 static int MAX_PORT = 5584; | |
| 81 | |
| 82 static int _nextPort = MIN_PORT; | |
| 83 | |
| 84 static int next() { | |
| 85 var port = _nextPort; | |
| 86 if (port > MAX_PORT) { | |
| 87 throw new Exception("All ports are used."); | |
| 88 } | |
| 89 _nextPort += 2; | |
| 90 return port; | |
| 91 } | |
| 92 } | |
| 93 | |
| 94 /** | |
| 95 * Represents the interface to the emulator. | |
| 96 * New emulators can be launched by calling the static [launchNewEmulator] | |
| 97 * method. | |
| 98 */ | |
| 99 class AndroidEmulator { | |
| 100 int _port; | |
| 101 Process _emulatorProcess; | |
| 102 AdbDevice _adbDevice; | |
| 103 | |
| 104 int get port => _port; | |
| 105 | |
| 106 AdbDevice get adbDevice => _adbDevice; | |
| 107 | |
| 108 static Future<AndroidEmulator> launchNewEmulator(String avdName) { | |
| 109 var portNumber = AdbServerPortPool.next(); | |
| 110 var args = ['-avd', '$avdName', '-port', "$portNumber" /*, '-gpu', 'on'*/]; | |
| 111 return Process.start("emulator64-arm", args).then((Process process) { | |
| 112 var adbDevice = new AdbDevice('emulator-$portNumber'); | |
| 113 return new AndroidEmulator(portNumber, adbDevice, process); | |
| 114 }); | |
| 115 } | |
| 116 | |
| 117 AndroidEmulator(this._port, this._adbDevice, this._emulatorProcess) { | |
|
ricow1
2013/05/21 18:30:43
since you are using private methods and instance v
kustermann
2013/05/22 09:30:58
Done.
| |
| 118 Stream<String> getLines(Stream s) { | |
| 119 return s.transform(new StringDecoder()).transform(new LineTransformer()); | |
| 120 } | |
| 121 | |
| 122 getLines(_emulatorProcess.stdout).listen((line) { | |
| 123 log("stdout: ${line.trim()}"); | |
| 124 }); | |
| 125 getLines(_emulatorProcess.stderr).listen((line) { | |
| 126 log("stderr: ${line.trim()}"); | |
| 127 }); | |
| 128 _emulatorProcess.exitCode.then((exitCode) { | |
| 129 log("emulator exited with exitCode: $exitCode."); | |
| 130 }); | |
| 131 } | |
| 132 | |
| 133 Future<bool> kill() { | |
| 134 var completer = new Completer(); | |
| 135 if (_emulatorProcess.kill()) { | |
| 136 _emulatorProcess.exitCode.then((exitCode) { | |
| 137 completer.complete(true); | |
| 138 }); | |
| 139 } else { | |
| 140 log("Sending kill signal to emulator process failed"); | |
| 141 completer.complete(false); | |
| 142 } | |
| 143 return completer.future; | |
| 144 } | |
| 145 | |
| 146 void log(String msg) { | |
| 147 DebugLogger.info("AndroidEmulator(${_adbDevice.deviceId}): $msg"); | |
| 148 } | |
| 149 } | |
| 150 | |
| 151 /** | |
| 152 * Helper class to create avd device configurations. | |
| 153 */ | |
| 154 class AndroidHelper { | |
| 155 static Future createAvd(String name, String target) { | |
| 156 var args = ['--silent', 'create', 'avd', '--name', '$name', | |
| 157 '--target', '$target', '--force', '--abi', 'armeabi-v7a']; | |
| 158 return _executeCommand("android", args, "\n\n\n\n"); | |
|
ricow1
2013/05/21 18:30:43
add a comment stating why we add 4 new lines to st
kustermann
2013/05/22 09:30:58
Done. I don't remember if we actually need 4 of th
| |
| 159 } | |
| 160 } | |
| 161 | |
| 162 /** | |
| 163 * Used for communicating with an emulator or with a real device. | |
| 164 */ | |
| 165 class AdbDevice { | |
| 166 static const _adbServerStartupTime = const Duration(seconds: 3); | |
| 167 String _deviceId; | |
| 168 | |
| 169 String get deviceId => _deviceId; | |
| 170 | |
| 171 AdbDevice(this._deviceId); | |
| 172 | |
| 173 /** | |
| 174 * Blocks execution until the device is online | |
| 175 */ | |
| 176 Future waitForDevice() { | |
| 177 return _adbCommand(['wait-for-device']); | |
| 178 } | |
| 179 | |
| 180 /** | |
| 181 * Polls the 'sys.boot_completed' property. Returns as soon as the property is | |
| 182 * 1. | |
| 183 */ | |
| 184 Future waitForBootCompleted() { | |
| 185 var timeout = const Duration(seconds: 2); | |
| 186 var completer = new Completer(); | |
| 187 | |
| 188 checkUntilBooted() { | |
| 189 _adbCommandGetOutput(['shell', 'getprop', 'sys.boot_completed']) | |
| 190 .then((String stdout) { | |
| 191 stdout = stdout.trim(); | |
| 192 if (stdout == '1') { | |
| 193 completer.complete(); | |
| 194 } else { | |
| 195 new Timer(timeout, checkUntilBooted); | |
| 196 } | |
| 197 }).catchError((error) { | |
| 198 new Timer(timeout, checkUntilBooted); | |
| 199 }); | |
| 200 } | |
| 201 checkUntilBooted(); | |
| 202 return completer.future; | |
| 203 } | |
| 204 | |
| 205 /** | |
| 206 * Put adb in root mode. | |
| 207 */ | |
| 208 Future adbRoot() { | |
| 209 var adbRootCompleter = new Completer(); | |
| 210 return _adbCommand(['root']).then((_) { | |
| 211 new Timer(_adbServerStartupTime, () => adbRootCompleter.complete(true)); | |
| 212 }).catchError((error) => adbRootCompleter.completeError(error)); | |
| 213 return adbRootCompleter.future; | |
| 214 } | |
| 215 | |
| 216 /** | |
| 217 * Download data form the device. | |
| 218 */ | |
| 219 Future pullData(Path remote, Path local) { | |
| 220 return _adbCommand(['pull', '$remote', '$local']); | |
| 221 } | |
| 222 | |
| 223 /** | |
| 224 * Upload data to the device. | |
| 225 */ | |
| 226 Future pushData(Path local, Path remote) { | |
| 227 return _adbCommand(['push', '$local', '$remote']); | |
| 228 } | |
| 229 | |
| 230 /** | |
| 231 * Change permission of directory recursively. | |
| 232 */ | |
| 233 Future chmod(String mode, Path directory) { | |
| 234 var arguments = ['shell', 'chmod', '-R', mode, '$directory']; | |
| 235 return _adbCommand(arguments); | |
| 236 } | |
| 237 | |
| 238 /** | |
| 239 * Install an application on the device. | |
| 240 */ | |
| 241 Future installApk(Path filename) { | |
| 242 return _adbCommand( | |
| 243 ['install', '-i', 'com.google.android.feedback', '-r', '$filename']); | |
| 244 } | |
| 245 | |
| 246 /** | |
| 247 * Start the given intent on the device. | |
| 248 */ | |
| 249 Future startActivity(Intent intent) { | |
| 250 var arguments = ['shell', 'am', 'start', '-W', | |
| 251 '-a', intent.action, | |
| 252 '-n', "${intent.package}/${intent.activity}"]; | |
| 253 if (intent.dataUri != null) { | |
| 254 arguments.addAll(['-d', intent.dataUri]); | |
| 255 } | |
| 256 return _adbCommand(arguments); | |
| 257 } | |
| 258 | |
| 259 /** | |
| 260 * Force to stop everything associated with [package]. | |
| 261 */ | |
| 262 Future forceStop(String package) { | |
| 263 var arguments = ['shell', 'am', 'force-stop', package]; | |
| 264 return _adbCommand(arguments); | |
| 265 } | |
| 266 | |
| 267 /** | |
| 268 * Kill all background processes. | |
| 269 */ | |
| 270 Future killAll() { | |
| 271 var arguments = ['shell', 'am', 'kill-all']; | |
| 272 return _adbCommand(arguments); | |
| 273 } | |
| 274 | |
| 275 Future _adbCommand(List<String> adbArgs) { | |
| 276 if (_deviceId != null) { | |
| 277 var extendedAdbArgs = ['-s', _deviceId]; | |
| 278 extendedAdbArgs.addAll(adbArgs); | |
| 279 adbArgs = extendedAdbArgs; | |
| 280 } | |
| 281 return _executeCommand("adb", adbArgs); | |
| 282 } | |
| 283 | |
| 284 Future<String> _adbCommandGetOutput(List<String> adbArgs) { | |
| 285 if (_deviceId != null) { | |
| 286 var extendedAdbArgs = ['-s', _deviceId]; | |
| 287 extendedAdbArgs.addAll(adbArgs); | |
| 288 adbArgs = extendedAdbArgs; | |
| 289 } | |
| 290 return _executeCommandGetOutput("adb", adbArgs); | |
| 291 } | |
| 292 } | |
| 293 | |
| 294 /** | |
| 295 * Helper to list all adb devicess available. | |
| 296 */ | |
| 297 class AdbHelper { | |
| 298 static RegExp _deviceLineRegexp = | |
| 299 new RegExp(r'^([a-zA-Z0-9_-]+)[ \t]+device$', multiLine: true); | |
| 300 | |
| 301 static Future<List<String>> listDevices() { | |
| 302 return Process.run('adb', ['devices']).then((ProcessResult result) { | |
| 303 if (result.exitCode != 0) { | |
| 304 throw new Exception("Could not list devices [stdout: ${result.stdout}," | |
| 305 "stderr: ${result.stderr}]"); | |
| 306 } | |
| 307 return _deviceLineRegexp.allMatches(result.stdout).map((Match m) => m.grou p(1)).toList(); | |
|
ricow1
2013/05/21 18:30:43
long line
kustermann
2013/05/22 09:30:58
Done.
| |
| 308 }); | |
| 309 } | |
| 310 } | |
| 311 | |
| 312 /** | |
| 313 * Represents an android intent. | |
| 314 */ | |
| 315 class Intent { | |
| 316 String action; | |
| 317 String package; | |
| 318 String activity; | |
| 319 String dataUri; | |
| 320 | |
| 321 Intent(this.action, this.package, this.activity, [this.dataUri]); | |
| 322 } | |
| 323 | |
| OLD | NEW |