UNPKG

29.2 kBJavaScriptView Raw
1/**
2 * A library for interacting with the Android Debug Bridge (adb).
3 *
4 * This library directly communicates over TCP/IP with the adb server using the
5 * service commands found here:
6 * {@link https://android.googlesource.com/platform/system/core/+/master/adb/SERVICES.TXT}
7 *
8 * @module adb
9 *
10 * @copyright
11 * Copyright (c) 2009-2017 by Appcelerator, Inc. All Rights Reserved.
12 *
13 * @license
14 * Licensed under the terms of the Apache Public License
15 * Please see the LICENSE included with this distribution for details.
16 */
17'use strict';
18
19const appc = require('node-appc');
20const __ = appc.i18n(__dirname).__;
21const async = require('async');
22const fs = require('fs-extra');
23const net = require('net');
24const path = require('path');
25const spawn = require('child_process').spawn; // eslint-disable-line security/detect-child-process
26const StreamSplitter = require('stream-splitter');
27
28require('colors');
29
30let connCounter = 0;
31
32module.exports = ADB;
33
34/**
35 * Debug flag that is enabled via the android.debugadb setting.
36 */
37var DEBUG = false;
38
39/**
40 * @constant
41 * Initial state. Also set if a command fails or the connection is closed.
42 */
43const DO_NOTHING = 0;
44
45/**
46 * @constant
47 * After a command is executed, this will wait for the OKAY/FAIL response.
48 */
49const WAIT_FOR_COMMAND_RESULT = 1;
50
51/**
52 * @constant
53 * After a command executes and we have received the OKAY/FAIL, then we process
54 * whatever data is left. Certain commands, such as track-devices, keep sending
55 * more data that begins with the length of data expected.
56 */
57const WAIT_FOR_NEW_DATA = 2;
58
59/**
60 * @constant
61 * After a command is executed, this will wait for additional data until the
62 * connection is closed. This is for "adb shell" commands where the exact
63 * output length is unknown.
64 */
65const BUFFER_UNTIL_CLOSE = 3;
66
67/**
68 * @constant
69 * After a command is executed, wait for a response before executing the callback
70 */
71const WAIT_FOR_RESPONSE = 4;
72
73/**
74 * @typedef {Function} ConfigGetFunction
75 * @param {string} key key of the value to retrieve
76 * @param {*} [defaultValue=undefined] default value to return if not in config
77 * @returns {*}
78 */
79
80/**
81 * CLI Config
82 * @typedef {Object} Config
83 * @property {ConfigGetFunction} get method to retrieve config values
84 */
85
86/**
87 * Creates an Connection object.
88 * @class
89 * @classdesc Manages the connection and communcations with the ADB server.
90 * @constructor
91 * @param {ADB} adb - The ADB instance
92 */
93function Connection(adb) {
94 this.adb = adb;
95 this.port = adb.config && adb.config.get('android.adb.port') || 5037;
96 this.socket = null;
97 this.state = DO_NOTHING;
98 this.connNum = ++connCounter;
99}
100
101/**
102 * Executes a command. If there is no connection to the ADB server, it will
103 * connect to it, then run the command.
104 * @param {String} cmd - The command to run
105 * @param {Connection~execCallback} callback - A function to call when the command is finished executing
106 * @param {Object} [opts] - Execute options
107 * @param {Boolean} [opts.bufferUntilClose=false] - Buffers all received data until ADB closes the connection
108 */
109Connection.prototype.exec = function exec(cmd, callback, opts) {
110 var conn = this,
111 socket = this.socket,
112 doSend = !!socket,
113 buffer = null,
114 len = null;
115 function send () {
116 DEBUG && console.log('[' + conn.connNum + '] SENDING ' + cmd);
117 conn.state = WAIT_FOR_COMMAND_RESULT;
118 buffer = null;
119 socket.write(('0000' + cmd.length.toString(16)).substr(-4).toUpperCase() + cmd);
120 }
121
122 this.opts = opts || {};
123
124 if (!socket) {
125 socket = this.socket = net.connect({
126 port: this.port,
127 family: 4
128 }, function () {
129 DEBUG && console.log('[' + this.connNum + '] CONNECTED');
130
131 // TIMOB-24906: in some circumstances sending a command to adb right away
132 // can yield no response. So we allow 200ms before sending the initial command
133 setTimeout(function () {
134 send();
135 }, 200);
136 }.bind(this));
137
138 socket.setKeepAlive(true);
139 socket.setNoDelay(true);
140 } else {
141 DEBUG && console.log('[' + this.connNum + '] SOCKET ALREADY OPEN, RE-LISTENING AND SENDING NEW COMMAND "' + cmd + '"');
142 socket.removeAllListeners('data');
143 socket.removeAllListeners('end');
144 socket.removeAllListeners('error');
145 }
146
147 socket.on('data', function (data) {
148 DEBUG && console.log('[' + this.connNum + '] RECEIVED ' + data.length + ' BYTES (state=' + this.state + ') (cmd=' + cmd + ')');
149
150 if (this.state === DO_NOTHING) {
151 return;
152 }
153
154 if (!buffer || buffer.length === 0) {
155 buffer = data;
156 } else {
157 buffer += data;
158 }
159
160 DEBUG && console.log('[' + this.connNum + '] BUFFER LENGTH = ' + buffer.length);
161
162 while (1) {
163 switch (this.state) {
164 case WAIT_FOR_COMMAND_RESULT:
165 const result = buffer.slice(0, 4).toString();
166 DEBUG && console.log('[' + this.connNum + '] RESULT ' + result);
167 if (!/^OKAY|FAIL$/.test(result)) {
168 callback(new Error(__('Unknown adb result "%s"', result)));
169 return;
170 }
171 buffer = buffer.slice(4);
172
173 // did we fail?
174 if (result === 'FAIL') {
175 len = 0;
176 if (buffer.length >= 4) {
177 len = parseInt(buffer.slice(0, 4), 16);
178 isNaN(len) && (len = 0);
179 buffer = buffer.slice(4);
180 }
181 len && (buffer = buffer.slice(0, len));
182 DEBUG && console.log('[' + this.connNum + '] ERROR! ' + buffer.toString());
183 this.state = DO_NOTHING;
184
185 // copy the buffer into an error so we can free up the buffer
186 var err = new Error(buffer.toString());
187 buffer = null;
188 callback(err);
189 conn.end();
190 return;
191 }
192
193 // if there's no more data, then we're done
194 if (buffer.length === 0) {
195 if (this.opts.bufferUntilClose) {
196 DEBUG && console.log('[' + this.connNum + '] DONE, SETTING STATE TO BUFFER_UNTIL_CLOSE');
197 this.state = BUFFER_UNTIL_CLOSE;
198 } else if (this.opts.waitForResponse) {
199 DEBUG && console.log('[' + this.connNum + '] DONE, SETTING STATE TO WAIT_FOR_NEW_DATA');
200 this.state = WAIT_FOR_NEW_DATA;
201 } else {
202 DEBUG && console.log('[' + this.connNum + '] DONE, SETTING STATE TO DO_NOTHING');
203 this.state = DO_NOTHING;
204 callback();
205 }
206 return;
207 }
208
209 // if we aren't expecting the data to have a length (i.e. the shell command),
210 // then buffer immediately
211 if (this.opts.noLength) {
212 DEBUG && console.log('[' + this.connNum + '] PUSHING REMAINING DATA INTO BUFFER AND SETTING STATE TO BUFFER_UNTIL_CLOSE');
213 this.state = BUFFER_UNTIL_CLOSE;
214 return;
215 }
216
217 this.state = WAIT_FOR_NEW_DATA;
218 len = null; // we don't know the length yet
219 // purposely fall through
220
221 case WAIT_FOR_NEW_DATA:
222 // find how many bytes we are waiting for
223 if (len === null && buffer.length >= 4) {
224 len = parseInt(buffer.slice(0, 4), 16);
225 DEBUG && console.log('[' + this.connNum + '] DETERMINING EXPECTED LENGTH...');
226 isNaN(len) && (len = null);
227 buffer = buffer.slice(4);
228 }
229
230 // if there's no length, then let's fire the callback or wait until the socket closes
231 if (len === 0) {
232 DEBUG && console.log('[' + this.connNum + '] NO EXPECTED LENGTH, FIRING CALLBACK');
233 callback();
234 buffer = null;
235 len = null;
236 return;
237 } else if (len === null) {
238 DEBUG && console.log('[' + this.connNum + '] NO EXPECTED LENGTH');
239 if (this.opts.bufferUntilClose) {
240 DEBUG && console.log('[' + this.connNum + '] BUFFERING DATA UNTIL SOCKET CLOSE');
241 this.state = BUFFER_UNTIL_CLOSE;
242 } else {
243 buffer = null;
244 len = null;
245 this.state = WAIT_FOR_NEW_DATA;
246 callback();
247 }
248 return;
249 }
250
251 DEBUG && console.log('[' + this.connNum + '] EXPECTED LENGTH = ' + len);
252 DEBUG && console.log('[' + this.connNum + '] BUFFER LENGTH = ' + buffer.length);
253
254 // do we have enough bytes?
255 if (buffer.length >= len) {
256 // yup
257 const result = buffer.slice(0, len);
258 buffer = buffer.slice(len);
259 DEBUG && console.log('[' + this.connNum + '] SUCCESS AND JUST THE RIGHT AMOUNT OF BYTES (' + len + ') WITH ' + buffer.length + ' BYTES LEFT');
260 if (this.opts.bufferUntilClose) {
261 this.state = BUFFER_UNTIL_CLOSE;
262 } else {
263 this.state = WAIT_FOR_NEW_DATA;
264 len = null;
265 buffer = null;
266 callback(null, result);
267 }
268 } else {
269 // we need more data!
270 DEBUG && console.log('[' + this.connNum + '] WAITING FOR MORE DATA');
271 }
272 return;
273
274 case BUFFER_UNTIL_CLOSE:
275 // we've already added data to the buffer
276 return;
277 case WAIT_FOR_RESPONSE:
278 DEBUG && console.log('[' + this.connNum + '] DONE, RECEIVED RESPONSE');
279 this.state = DO_NOTHING;
280 callback(null, buffer);
281 return;
282 }
283 }
284 }.bind(this));
285
286 socket.on('end', function () {
287 DEBUG && console.log('[' + this.connNum + '] SOCKET CLOSED BY SERVER', (buffer && buffer.length));
288 if (buffer) {
289 if (!this.opts.waitForResponse) {
290 callback(null, buffer);
291 }
292 buffer = null;
293 }
294 this.end();
295 }.bind(this));
296
297 socket.on('error', function (err) {
298 this.end();
299
300 if (!err.code || err.code !== 'ECONNREFUSED') {
301 return callback(err);
302 }
303
304 this.adb.startServer(function (code) {
305 if (code) {
306 callback(new Error(__('Unable to start Android Debug Bridge server (exit code %s)', code)));
307 } else {
308 this.exec(cmd, callback, this.opts);
309 }
310 }.bind(this));
311 }.bind(this));
312
313 doSend && send();
314};
315
316/**
317 * Closes the connection and resets the socket and state.
318 */
319Connection.prototype.end = function end() {
320 if (this.socket) {
321 try {
322 this.socket.end();
323 } catch (ex) {
324 // ignore
325 }
326 this.socket = null;
327 }
328 this.state = DO_NOTHING;
329};
330
331/**
332 * Creates an ADB object.
333 * @class
334 * @classdesc Provides methods to interact with the Android Debug Bridge (ADB).
335 * @constructor
336 * @param {Config} [config] cli config
337 */
338function ADB(config) {
339 this.config = config;
340 if (config && config.get('android.debugadb', false)) {
341 DEBUG = true;
342 }
343}
344
345/**
346 * Returns the version of the ADB server.
347 * @param {ADB~versionCallback} callback - A function to call when the version has been retreived
348 */
349ADB.prototype.version = function version(callback) {
350 const conn = new Connection(this);
351 conn.exec('host:version', function (err, data) {
352 if (err) {
353 return callback(err);
354 }
355 if (data === null || data === undefined) {
356 return callback(new Error(`Unable to get adb version, received value ${data}`));
357 }
358 // Check if parseInt result is NaN?
359 callback(null, '1.0.' + parseInt(data, 16));
360 });
361};
362
363/**
364 * Parses the device list, then fetches additional device info.
365 * @param {ADB} adb - The ADB instance
366 * @param {Function} callback - A function to call when the devices have been parsed
367 * @param {Error} err - An error if the list devices call failed
368 * @param {Buffer|String} data - The buffer containing the list of devices
369 */
370function parseDevices(adb, callback, err, data) {
371 if (err) {
372 callback(err);
373 return;
374 }
375
376 var EmulatorManager = require('./emulator'),
377 emuMgr = new EmulatorManager(adb.config);
378
379 async.series((data || '').toString().split('\n').map(function (line) {
380 return function (done) {
381 var p = line.split(/\s+/);
382 if (p.length <= 1) {
383 return done();
384 }
385
386 var info = {
387 id: p.shift(),
388 state: p.shift()
389 };
390
391 if (info.state !== 'device') {
392 emuMgr.isEmulator(info.id, function (err, emu) {
393 info.emulator = emu || false;
394 done(null, info);
395 });
396 return;
397 }
398
399 adb.shell(info.id, 'getprop', function (err, data) {
400 if (!err && data) {
401 const re = /^\[([^\]]*)\]: \[(.*)\]\s*$/;
402 data.toString().split('\n').forEach(function (line) {
403 const m = line.match(re);
404 if (m) {
405 const key = m[1];
406 const value = m[2];
407
408 switch (key) {
409 case 'ro.product.model.internal':
410 info.modelnumber = value;
411 break;
412 case 'ro.build.version.release':
413 case 'ro.build.version.sdk':
414 case 'ro.product.brand':
415 case 'ro.product.device':
416 case 'ro.product.manufacturer':
417 case 'ro.product.model':
418 case 'ro.product.name':
419 info[key.split('.').pop()] = value;
420 break;
421 case 'ro.genymotion.version':
422 info.genymotion = value;
423 break;
424 default:
425 if (key.indexOf('ro.product.cpu.abi') === 0) {
426 Array.isArray(info.abi) || (info.abi = []);
427 value.split(',').forEach(function (abi) {
428 abi = abi.trim();
429 if (abi && info.abi.indexOf(abi) === -1) {
430 info.abi.push(abi);
431 }
432 });
433 }
434 break;
435 }
436 }
437 });
438 }
439
440 emuMgr.isEmulator(info.id, function (err, emu) {
441 info.emulator = emu || false;
442 done(null, info);
443 });
444 });
445 };
446 }), function (err, results) {
447 callback(null, results.filter(device => !!device));
448 });
449}
450
451/**
452 * Retrieves a list of all devices and emulators.
453 * @param {ADB~devicesCallback} callback - A function that is called with the list of devices
454 */
455ADB.prototype.devices = function devices(callback) {
456 new Connection(this).exec('host:devices', function (err, data) {
457 parseDevices(this, callback, err, data);
458 }.bind(this), { waitForResponse: true });
459};
460
461/**
462 * Retrieves a list of all devices and emulators, then listens for changes to devices.
463 * @param {ADB~trackDevicesCallback} callback - A function that is continually called with the list of devices
464 * @returns {Connection} The connection so you can end() it.
465 */
466ADB.prototype.trackDevices = function trackDevices(callback) {
467 var conn = new Connection(this),
468 _t = this,
469 queue = async.queue(function (task, next) {
470 parseDevices(_t, function (err, results) {
471 callback(err, results);
472 next();
473 }, task.err, task.data);
474 }, 1);
475
476 conn.exec('host:track-devices', function (err, data) {
477 queue.push({ err: err, data: data });
478 }, { waitForResponse: true });
479
480 return conn;
481};
482
483/**
484 * Helper function that loads the Android detection library and detects the adb settings.
485 * @param {Config} config CLI config
486 * @param {Function} callback async callback
487 */
488function androidDetect(config, callback) {
489 (require('./android')).detect(config, null, function (results) {
490 if (results.sdk && results.sdk.executables.adb) {
491 callback(null, results);
492 } else {
493 callback(new Error(__('Android SDK not found')));
494 }
495 });
496}
497
498/**
499 * Attempts to find the adb executable, then start the adb server.
500 * @param {ADB~startServerCallback} callback - A function that is called when the server has started
501 */
502ADB.prototype.startServer = function startServer(callback) {
503 androidDetect(this.config, function (err, results) {
504 if (err) {
505 return callback(err);
506 }
507 appc.subprocess.run(results.sdk.executables.adb, 'start-server', function (code, out, err) {
508 callback(code ? new Error(__('Failed to start ADB (code %s): %s', code, err)) : null);
509 });
510 });
511};
512
513/**
514 * Attempts to find the adb executable, then stop the adb server.
515 * @param {ADB~stopServerCallback} callback - A callback that is fired when the server has stopped
516 */
517ADB.prototype.stopServer = function stopServer(callback) {
518 androidDetect(this.config, function (err, results) {
519 if (err) {
520 return callback(err);
521 }
522 appc.subprocess.run(results.sdk.executables.adb, 'kill-server', function (code, _out, _err) {
523 callback(code);
524 });
525 });
526};
527
528/**
529 * Runs the specified command on the Android emulator/device. Note that ADB
530 * converts all \n to \r\n. So data will probably be larger than the original
531 * output on the device.
532 * @param {String} deviceId - android emulator id (of form 'android-5554', gotten from emulator.id after starting it (not to be confused with ids from emulator.detect listing))
533 * @param {String} cmd - The command to run
534 * @param {ADB~shellCallback} callback - A callback that is fired when the command has completed
535 */
536ADB.prototype.shell = function shell(deviceId, cmd, callback) {
537 var conn = new Connection(this);
538 conn.exec('host:transport:' + deviceId, function (err, _data) {
539 if (err) {
540 callback(err);
541 } else {
542 conn.exec('shell:' + cmd.replace(/^shell:/, ''), function (err, result) {
543 callback(err, result);
544 }, { bufferUntilClose: true, noLength: true });
545 }
546 });
547};
548
549/**
550 * Installs an app to the specified device/emulator.
551 * @param {String} deviceId - The id of the device or emulator
552 * @param {String} apkFile - The application apk file to install
553 * @param {Object} [opts] - Install options
554 * @param {Object} [opts.logger] - A logger instance
555 * @param {ADB~installAppCallback} callback - A callback that is fired when the application has been installed
556 */
557ADB.prototype.installApp = function installApp(deviceId, apkFile, opts, callback) {
558 if (typeof opts === 'function') {
559 callback = opts;
560 opts = {};
561 }
562 apkFile = appc.fs.resolvePath(apkFile);
563 if (!fs.existsSync(apkFile)) {
564 callback(new Error(__('APK file "%s" does not exist', apkFile)));
565 return;
566 }
567
568 this.devices(function (err, devices) {
569 if (err) {
570 return callback(err);
571 }
572
573 // Fetch info about the device we're installing to.
574 devices = devices.filter(d => d.id === deviceId);
575 if (devices.length < 1) {
576 return callback(new Error(__('device not found')));
577 }
578 const deviceInfo = devices[0];
579
580 androidDetect(this.config, function (err, results) {
581 if (err) {
582 return callback(err);
583 }
584
585 // Fetch the device's API Level.
586 let deviceApiLevel = 1;
587 if (deviceInfo.sdk) {
588 const value = parseInt(deviceInfo.sdk);
589 if (!isNaN(value)) {
590 deviceApiLevel = value;
591 }
592 }
593
594 // Set up the 'adb' arguments array.
595 const args = [];
596 args.push('-s', deviceId);
597 args.push('install');
598 args.push('-r');
599 if (deviceApiLevel >= 17) {
600 // Allow installation of an older APK version over a newer one.
601 // Note: Only supported on Android 4.2 (API Level 17) and higher.
602 args.push('-d');
603 }
604 args.push(apkFile);
605
606 // Run the adb install command.
607 opts.logger && opts.logger.trace(__('Executing: %s', [ results.sdk.executables.adb ].concat(args).join(' ').cyan));
608 appc.subprocess.run(results.sdk.executables.adb, args, function (code, out, err) {
609 var m = out.match(/^Failure \[(.+)\]$/m);
610 if ((code && err.indexOf('No space left on device') !== -1) || (!code && m && m[1] === 'INSTALL_FAILED_INSUFFICIENT_STORAGE')) {
611 callback(new Error(__('Not enough free space on device')));
612 } else if (m && m[1] === 'INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES') {
613 callback(__('The app is already installed, but signed with a different certificate') + '\n'
614 + __('You need to either manually uninstall the app or rebuild using the same certificate that was used to sign the installed app'));
615 } else if (m) {
616 callback(new Error(m[1]));
617 } else if (code) {
618 callback(new Error(out.trim() + '\n' + err.trim()));
619 } else {
620 // no obvious errors, now we need to check stdout
621 m = out.match(/^Error: (.+)$/m);
622 if (m) {
623 callback(new Error(m[1]));
624 } else {
625 callback();
626 }
627 }
628 });
629 });
630 }.bind(this));
631};
632
633/**
634 * Returns the ps output of the specified app and device/emulator, if running.
635 * @param {String} deviceId - The id of the device or emulator
636 * @param {ADB~psCallback} callback - A callback that is fired once ps is executed
637 */
638ADB.prototype.ps = function ps(deviceId, callback) {
639 var outputCallback = function (err, data) {
640 if (err) {
641 callback(err);
642 } else {
643 // old ps, does not support '-A' parameter
644 var dataStr = data.toString().trim();
645 if (dataStr.startsWith('bad pid \'-A\'') || dataStr.endsWith('NAME')) {
646 this.shell(deviceId, 'ps', outputCallback);
647 } else {
648 callback(null, data);
649 }
650 }
651 }.bind(this);
652 this.shell(deviceId, 'ps -A', outputCallback);
653};
654
655/**
656 * Returns the pid of the specified app and device/emulator, if running.
657 * @param {String} deviceId - The id of the device or emulator
658 * @param {String} appid - The application's id
659 * @param {ADB~getPidCallback} callback - A callback that is fired once the pid has been determined
660 */
661ADB.prototype.getPid = function getPid(deviceId, appid, callback) {
662 this.ps(deviceId, function (err, data) {
663 if (err) {
664 callback(err);
665 } else {
666 var lines = data.toString().split('\n'),
667 i = 0,
668 len = lines.length,
669 columns;
670 for (; i < len; i++) {
671 columns = lines[i].trim().split(/\s+/);
672 if (columns.pop() == appid) { // eslint-disable-line eqeqeq
673 callback(null, parseInt(columns[1]));
674 return;
675 }
676 }
677 callback(null, 0);
678 }
679 });
680};
681
682/**
683 * Starts an application on the specified device/emulator.
684 * @param {String} deviceId - The id of the device or emulator
685 * @param {String} appid - The application's id
686 * @param {String} activity - The name of the activity to run
687 * @param {ADB~startAppCallback} callback - A function that is called once the application has been started
688 */
689ADB.prototype.startApp = function startApp(deviceId, appid, activity, callback) {
690 // This launches the app via an intent just like how the Android OS would do it when tapping on the app.
691 // Notes:
692 // - The "-n" sets the intent's component name. Needed by explicit intents.
693 // - The "-a" sets the intent's action.
694 // - The "-c" sets the intent's category.
695 // - The "-f 0x10200000" sets intent flags: FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
696 this.shell(deviceId, 'am start -n ' + appid + '/.' + activity.replace(/^\./, '') + ' -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -f 0x10200000', callback);
697};
698
699/**
700 * Stops an application on the specified device/emulator.
701 * @param {String} deviceId - The id of the device or emulator
702 * @param {String} appid - The application's id
703 * @param {ADB~stopAppCallback} callback - A function that is called once the application has been stopped
704 */
705ADB.prototype.stopApp = function stopApp(deviceId, appid, callback) {
706 this.getPid(deviceId, appid, function (err, pid) {
707 if (!err && pid) {
708 this.shell(deviceId, 'am force-stop ' + appid, function (err, data) {
709 if (data.toString().indexOf('Unknown command: force-stop') !== -1) {
710 this.shell(deviceId, 'kill ' + pid, callback);
711 } else {
712 callback(err, data);
713 }
714 }.bind(this));
715 return;
716 }
717 callback(new Error(__('Application "%s" is not running', appid)));
718 }.bind(this));
719};
720
721/**
722 * Forwards the specified device/emulator's socket connections to the destination.
723 * @param {String} deviceId - The id of the device or emulator
724 * @param {String} src - The source port in the format "tcp:<port>"
725 * @param {String} dest - The destination port in the format "tcp:<port>" or "jdwp:<pid>"
726 * @param {ADB~forwardCallback} callback - A function that is called once the sockets have been forwarded
727 */
728ADB.prototype.forward = function forward(deviceId, src, dest, callback) {
729 androidDetect(this.config, function (err, results) {
730 if (err) {
731 return callback(err);
732 }
733 appc.subprocess.run(results.sdk.executables.adb, [ '-s', deviceId, 'forward', src, dest ], function (code, _out, _err) {
734 callback(code);
735 });
736 });
737};
738
739/**
740 * Pushes a single file to a device or emulator.
741 * @param {String} deviceId - The id of the device or emulator
742 * @param {String} src - The source file to copy to the device
743 * @param {String} dest - The destination to write the file
744 * @param {ADB~pushCallback} callback - A function that is called once the file has been copied
745 */
746ADB.prototype.push = function push(deviceId, src, dest, callback) {
747 src = appc.fs.resolvePath(src);
748 if (!fs.existsSync(src)) {
749 callback(new Error(__('Source file "%s" does not exist', src)));
750 } else {
751 androidDetect(this.config, function (err, results) {
752 if (err) {
753 return callback(err);
754 }
755 appc.subprocess.run(results.sdk.executables.adb, [ '-s', deviceId, 'push', src, dest ], function (code, _out, _err) {
756 callback(code);
757 });
758 });
759 }
760};
761
762/**
763 * Pulls a single file from a device or emulator.
764 * @param {String} deviceId - The id of the device or emulator
765 * @param {String} src - The source file to copy from the device
766 * @param {String} dest - The destination to write the file
767 * @param {ADB~pullCallback} callback - A function that is called once the file has been copied
768 */
769ADB.prototype.pull = function pull(deviceId, src, dest, callback) {
770 dest = appc.fs.resolvePath(dest);
771 var destDir = path.dirname(dest);
772
773 try {
774 fs.ensureDirSync(destDir);
775
776 androidDetect(this.config, function (err, results) {
777 if (err) {
778 return callback(err);
779 }
780 appc.subprocess.run(results.sdk.executables.adb, [ '-s', deviceId, 'pull', src, dest ], function (code, _out, _err) {
781 callback(code);
782 });
783 });
784 } catch (ex) {
785 callback(new Error(__('Failed to create destination directory "%s"', destDir)));
786 }
787};
788
789/**
790 * Streams output from logcat into the specified handler until the adb logcat
791 * process ends.
792 * @param {String} deviceId - The id of the device or emulator
793 * @param {Function} handler - A function to call whenever data becomes available
794 * @param {Function} callback - A function that is called once 'adb logcat' exits
795 */
796ADB.prototype.logcat = function logcat(deviceId, handler, callback) {
797 androidDetect(this.config, function (err, results) {
798 if (err) {
799 return callback(err);
800 }
801
802 var child = spawn(results.sdk.executables.adb, [ '-s', deviceId, 'logcat', '-v', 'brief', '-b', 'main' ]), // , '-s', '*:d,*,TiAPI:V']);
803 splitter = child.stdout.pipe(StreamSplitter('\n'));
804
805 // Set encoding on the splitter Stream, so tokens come back as a String.
806 splitter.encoding = 'utf8';
807 splitter.on('token', function (data) {
808 handler(data);
809 });
810
811 child.on('close', function () {
812 callback();
813 });
814 });
815};
816
817/**
818 * A function to call when the version has been retreived.
819 * @callback ADB~versionCallback
820 * @param {Error} err - In the event of an error, an exception, otherwise falsey
821 * @param {String} version - The version of the adb server
822 */
823
824/**
825 * A function to call when the command is finished executing.
826 * @callback Connection~execCallback
827 * @param {Error} err - In the event of an error, an exception, otherwise falsey
828 * @param {Buffer} data - The output from the executed command
829 */
830
831/**
832 * A function that is called with the list of devices.
833 * @callback ADB~devicesCallback
834 * @param {Error} err - In the event of an error, an exception, otherwise falsey
835 * @param {Array} devices - An array of devices and emulators found
836 */
837
838/**
839 * A function that is continually called with the list of devices when the state
840 * of any devices or emulators.
841 * @callback ADB~trackDevicesCallback
842 * @param {Error} err - In the event of an error, an exception, otherwise falsey
843 * @param {Array} devices - An array of devices and emulators found
844 */
845
846/**
847 * A function that is called when the adb start-server has completed.
848 * @callback ADB~startServerCallback
849 * @param {Number|Error} err - The exit code from adb start-server command or an exception
850 */
851
852/**
853 * A function that is called when the adb kill-server has completed.
854 * @callback ADB~stopServerCallback
855 * @param {Number|Error} err - The exit code from adb kill-server command or an exception
856 */
857
858/**
859 * A function that is called when the shell command has completed.
860 * Called after the shell command completes.
861 * @callback ADB~shellCallback
862 * @param {Error} err - In the event of an error, an exception, otherwise falsey
863 * @param {Buffer} data - The output from the executed command
864 */
865
866/**
867 * A function that is called when the application has been installed.
868 * @callback ADB~installAppCallback
869 * @param {Number|Error} err - The exit code from adb install command or an exception
870 */
871
872/**
873 * A callback that is fired once the pid has been determined.
874 * @callback ADB~getPidCallback
875 * @param {Error} err - In the event of an error, an exception, otherwise falsey
876 * @param {Number} pid - The pid or zero if the process is not found
877 */
878
879/**
880 * A function that is called when the application has been started.
881 * @callback ADB~startAppCallback
882 * @param {Error} err - In the event of an error, an exception, otherwise falsey
883 * @param {Buffer} data - The output from the executed command
884 */
885
886/**
887 * A function that is called when the application has been stopped.
888 * @callback ADB~stopAppCallback
889 * @param {Error} err - In the event of an error, an exception, otherwise falsey
890 * @param {Buffer} data - The output from the executed command
891 */
892
893/**
894 * A function that is called once the sockets have been forwarded.
895 * @callback ADB~forwardCallback
896 * @param {Number|Error} err - The exit code from adb forward command or an exception
897 */
898
899/**
900 * A function that is called once the file has been copied.
901 * @callback ADB~pushCallback
902 * @param {Number|Error} err - The exit code from adb forward command or an exception
903 */
904
905/**
906 * A function that is called once the file has been copied.
907 * @callback ADB~pullCallback
908 * @param {Number|Error} err - The exit code from adb forward command or an exception
909 */
910
911/**
912 * A function to call whenever data becomes available.
913 * @callback ADB~logcatHandler
914 * @param {String} data - One or more lines of logcat output
915 */
916
917/**
918 * A function that is called once 'adb logcat' exits
919 * @callback ADB~logcatCallback
920 */