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