| 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:async"; | |
| 8 import "dart:convert" show LineSplitter, UTF8; | |
| 9 import "dart:core"; | |
| 10 import "dart:io"; | |
| 11 | |
| 12 import "path.dart"; | |
| 13 import "utils.dart"; | |
| 14 | |
| 15 Future _executeCommand(String executable, | |
| 16 List<String> args, | |
| 17 [String stdin = ""]) { | |
| 18 return _executeCommandRaw(executable, args, stdin).then((results) => null); | |
| 19 } | |
| 20 | |
| 21 Future _executeCommandGetOutput(String executable, | |
| 22 List<String> args, | |
| 23 [String stdin = ""]) { | |
| 24 return _executeCommandRaw(executable, args, stdin) | |
| 25 .then((output) => output); | |
| 26 } | |
| 27 | |
| 28 /** | |
| 29 * [_executeCommandRaw] will write [stdin] to the standard input of the created | |
| 30 * process and will return a tuple (stdout, stderr). | |
| 31 * | |
| 32 * If the exit code of the process was nonzero it will complete with an error. | |
| 33 * If starting the process failed, it will complete with an error as well. | |
| 34 */ | |
| 35 Future _executeCommandRaw(String executable, | |
| 36 List<String> args, | |
| 37 [String stdin = ""]) { | |
| 38 Future<String> getOutput(Stream<List<int>> stream) { | |
| 39 return stream.transform(UTF8.decoder).toList() | |
| 40 .then((data) => data.join("")); | |
| 41 } | |
| 42 | |
| 43 DebugLogger.info("Running: '\$ $executable ${args.join(' ')}'"); | |
| 44 return Process.start(executable, args).then((Process process) { | |
| 45 if (stdin != null && stdin != '') { | |
| 46 process.stdin.write(stdin); | |
| 47 } | |
| 48 process.stdin.close(); | |
| 49 | |
| 50 var futures = [getOutput(process.stdout), | |
| 51 getOutput(process.stderr), | |
| 52 process.exitCode]; | |
| 53 return Future.wait(futures).then((results) { | |
| 54 bool success = results[2] == 0; | |
| 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 }); | |
| 67 } | |
| 68 | |
| 69 /** | |
| 70 * Helper class to loop through all adb ports. | |
| 71 * | |
| 72 * The ports come in pairs: | |
| 73 * - even number: console connection | |
| 74 * - odd number: adb connection | |
| 75 * Note that this code doesn't check if the ports are used. | |
| 76 */ | |
| 77 class AdbServerPortPool { | |
| 78 static int MIN_PORT = 5554; | |
| 79 static int MAX_PORT = 5584; | |
| 80 | |
| 81 static int _nextPort = MIN_PORT; | |
| 82 | |
| 83 static int next() { | |
| 84 var port = _nextPort; | |
| 85 if (port > MAX_PORT) { | |
| 86 throw new Exception("All ports are used."); | |
| 87 } | |
| 88 _nextPort += 2; | |
| 89 return port; | |
| 90 } | |
| 91 } | |
| 92 | |
| 93 /** | |
| 94 * Represents the interface to the emulator. | |
| 95 * New emulators can be launched by calling the static [launchNewEmulator] | |
| 96 * method. | |
| 97 */ | |
| 98 class AndroidEmulator { | |
| 99 int _port; | |
| 100 Process _emulatorProcess; | |
| 101 AdbDevice _adbDevice; | |
| 102 | |
| 103 int get port => _port; | |
| 104 | |
| 105 AdbDevice get adbDevice => _adbDevice; | |
| 106 | |
| 107 static Future<AndroidEmulator> launchNewEmulator(String avdName) { | |
| 108 var portNumber = AdbServerPortPool.next(); | |
| 109 var args = ['-avd', '$avdName', '-port', "$portNumber" /*, '-gpu', 'on'*/]; | |
| 110 return Process.start("emulator64-arm", args).then((Process process) { | |
| 111 var adbDevice = new AdbDevice('emulator-$portNumber'); | |
| 112 return new AndroidEmulator._private(portNumber, adbDevice, process); | |
| 113 }); | |
| 114 } | |
| 115 | |
| 116 AndroidEmulator._private(this._port, this._adbDevice, this._emulatorProcess) { | |
| 117 Stream<String> getLines(Stream s) { | |
| 118 return s.transform(UTF8.decoder).transform(new LineSplitter()); | |
| 119 } | |
| 120 | |
| 121 getLines(_emulatorProcess.stdout).listen((line) { | |
| 122 log("stdout: ${line.trim()}"); | |
| 123 }); | |
| 124 getLines(_emulatorProcess.stderr).listen((line) { | |
| 125 log("stderr: ${line.trim()}"); | |
| 126 }); | |
| 127 _emulatorProcess.exitCode.then((exitCode) { | |
| 128 log("emulator exited with exitCode: $exitCode."); | |
| 129 }); | |
| 130 } | |
| 131 | |
| 132 Future<bool> kill() { | |
| 133 var completer = new Completer(); | |
| 134 if (_emulatorProcess.kill()) { | |
| 135 _emulatorProcess.exitCode.then((exitCode) { | |
| 136 // TODO: Should we use exitCode to do something clever? | |
| 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 // We're adding newlines to stdin to simulate <enter>. | |
| 159 return _executeCommand("android", args, "\n\n\n\n"); | |
| 160 } | |
| 161 } | |
| 162 | |
| 163 /** | |
| 164 * Used for communicating with an emulator or with a real device. | |
| 165 */ | |
| 166 class AdbDevice { | |
| 167 static const _adbServerStartupTime = const Duration(seconds: 3); | |
| 168 String _deviceId; | |
| 169 | |
| 170 String get deviceId => _deviceId; | |
| 171 | |
| 172 AdbDevice(this._deviceId); | |
| 173 | |
| 174 /** | |
| 175 * Blocks execution until the device is online | |
| 176 */ | |
| 177 Future waitForDevice() { | |
| 178 return _adbCommand(['wait-for-device']); | |
| 179 } | |
| 180 | |
| 181 /** | |
| 182 * Polls the 'sys.boot_completed' property. Returns as soon as the property is | |
| 183 * 1. | |
| 184 */ | |
| 185 Future waitForBootCompleted() { | |
| 186 var timeout = const Duration(seconds: 2); | |
| 187 var completer = new Completer(); | |
| 188 | |
| 189 checkUntilBooted() { | |
| 190 _adbCommandGetOutput(['shell', 'getprop', 'sys.boot_completed']) | |
| 191 .then((String stdout) { | |
| 192 stdout = stdout.trim(); | |
| 193 if (stdout == '1') { | |
| 194 completer.complete(); | |
| 195 } else { | |
| 196 new Timer(timeout, checkUntilBooted); | |
| 197 } | |
| 198 }).catchError((error) { | |
| 199 new Timer(timeout, checkUntilBooted); | |
| 200 }); | |
| 201 } | |
| 202 checkUntilBooted(); | |
| 203 return completer.future; | |
| 204 } | |
| 205 | |
| 206 /** | |
| 207 * Put adb in root mode. | |
| 208 */ | |
| 209 Future adbRoot() { | |
| 210 var adbRootCompleter = new Completer(); | |
| 211 _adbCommand(['root']).then((_) { | |
| 212 // TODO: Figure out a way to wait until the adb daemon was restarted in | |
| 213 // 'root mode' on the device. | |
| 214 new Timer(_adbServerStartupTime, () => adbRootCompleter.complete(true)); | |
| 215 }).catchError((error) => adbRootCompleter.completeError(error)); | |
| 216 return adbRootCompleter.future; | |
| 217 } | |
| 218 | |
| 219 /** | |
| 220 * Download data form the device. | |
| 221 */ | |
| 222 Future pullData(Path remote, Path local) { | |
| 223 return _adbCommand(['pull', '$remote', '$local']); | |
| 224 } | |
| 225 | |
| 226 /** | |
| 227 * Upload data to the device. | |
| 228 */ | |
| 229 Future pushData(Path local, Path remote) { | |
| 230 return _adbCommand(['push', '$local', '$remote']); | |
| 231 } | |
| 232 | |
| 233 /** | |
| 234 * Change permission of directory recursively. | |
| 235 */ | |
| 236 Future chmod(String mode, Path directory) { | |
| 237 var arguments = ['shell', 'chmod', '-R', mode, '$directory']; | |
| 238 return _adbCommand(arguments); | |
| 239 } | |
| 240 | |
| 241 /** | |
| 242 * Install an application on the device. | |
| 243 */ | |
| 244 Future installApk(Path filename) { | |
| 245 return _adbCommand( | |
| 246 ['install', '-i', 'com.google.android.feedback', '-r', '$filename']); | |
| 247 } | |
| 248 | |
| 249 /** | |
| 250 * Start the given intent on the device. | |
| 251 */ | |
| 252 Future startActivity(Intent intent) { | |
| 253 var arguments = ['shell', 'am', 'start', '-W', | |
| 254 '-a', intent.action, | |
| 255 '-n', "${intent.package}/${intent.activity}"]; | |
| 256 if (intent.dataUri != null) { | |
| 257 arguments.addAll(['-d', intent.dataUri]); | |
| 258 } | |
| 259 return _adbCommand(arguments); | |
| 260 } | |
| 261 | |
| 262 /** | |
| 263 * Force to stop everything associated with [package]. | |
| 264 */ | |
| 265 Future forceStop(String package) { | |
| 266 var arguments = ['shell', 'am', 'force-stop', package]; | |
| 267 return _adbCommand(arguments); | |
| 268 } | |
| 269 | |
| 270 /** | |
| 271 * Set system property name to value. | |
| 272 */ | |
| 273 Future setProp(String name, String value) { | |
| 274 return _adbCommand(['shell', 'setprop', name, value]); | |
| 275 } | |
| 276 | |
| 277 /** | |
| 278 * Kill all background processes. | |
| 279 */ | |
| 280 Future killAll() { | |
| 281 var arguments = ['shell', 'am', 'kill-all']; | |
| 282 return _adbCommand(arguments); | |
| 283 } | |
| 284 | |
| 285 Future _adbCommand(List<String> adbArgs) { | |
| 286 if (_deviceId != null) { | |
| 287 var extendedAdbArgs = ['-s', _deviceId]; | |
| 288 extendedAdbArgs.addAll(adbArgs); | |
| 289 adbArgs = extendedAdbArgs; | |
| 290 } | |
| 291 return _executeCommand("adb", adbArgs); | |
| 292 } | |
| 293 | |
| 294 Future<String> _adbCommandGetOutput(List<String> adbArgs) { | |
| 295 if (_deviceId != null) { | |
| 296 var extendedAdbArgs = ['-s', _deviceId]; | |
| 297 extendedAdbArgs.addAll(adbArgs); | |
| 298 adbArgs = extendedAdbArgs; | |
| 299 } | |
| 300 return _executeCommandGetOutput("adb", adbArgs); | |
| 301 } | |
| 302 } | |
| 303 | |
| 304 /** | |
| 305 * Helper to list all adb devices available. | |
| 306 */ | |
| 307 class AdbHelper { | |
| 308 static RegExp _deviceLineRegexp = | |
| 309 new RegExp(r'^([a-zA-Z0-9_-]+)[ \t]+device$', multiLine: true); | |
| 310 | |
| 311 static Future<List<String>> listDevices() { | |
| 312 return Process.run('adb', ['devices']).then((ProcessResult result) { | |
| 313 if (result.exitCode != 0) { | |
| 314 throw new Exception("Could not list devices [stdout: ${result.stdout}," | |
| 315 "stderr: ${result.stderr}]"); | |
| 316 } | |
| 317 return _deviceLineRegexp.allMatches(result.stdout) | |
| 318 .map((Match m) => m.group(1)).toList(); | |
| 319 }); | |
| 320 } | |
| 321 } | |
| 322 | |
| 323 /** | |
| 324 * Represents an android intent. | |
| 325 */ | |
| 326 class Intent { | |
| 327 String action; | |
| 328 String package; | |
| 329 String activity; | |
| 330 String dataUri; | |
| 331 | |
| 332 Intent(this.action, this.package, this.activity, [this.dataUri]); | |
| 333 } | |
| 334 | |
| OLD | NEW |