Files
android-dev-ext/src/sockets/adbsocket.js
Dave Holoway 44d887dd6c Support attaching to running app (#85)
* add support for timeout on adb socket reads

* add debugger support for attaching to a process

* add new launch configuration and support for picking an Android process ID

* initial support for attaching to android process

* display enhanced quick pick list with pids and names

* add flag to prevent disconnect messages when not connected

* Retrieve all loaded classes during startup.
This allows us to identify breakpoints in anonymous classes that are already loaded.

* correct name of process picker command

* make PickAndroidProcess command private

* selectAndroidProcessID always returns an object

* make breakpoint setup a loop instead of recursive

* tidy some labels and error messages

* use a more consistent command for retrieving process names

* show pid list sorted by pid instead of name

* refactor some Android and ADB-specific functions
Check ANDROID_SDK as replacement for ANDROID_HOME

* tidy up logcat launch and refactor target device selection

* fix logcat not displaying

* filter duplicates and blanks from logcat output
2020-04-23 13:28:03 +01:00

147 lines
4.7 KiB
JavaScript

const AndroidSocket = require('./androidsocket');
/**
* Manages a socket connection to Android Debug Bridge
*/
class ADBSocket extends AndroidSocket {
/**
* The port number to run ADB on.
* The value can be overriden by the adbPort value in each configuration.
*/
static ADBPort = 5037;
constructor() {
super('ADBSocket');
}
/**
* Reads and checks the reply from an ADB command
* @param {boolean} [throw_on_fail] true if the function should throw on non-OKAY status
*/
async read_adb_status(throw_on_fail = true) {
// read back the status
const status = await this.read_bytes(4, 'latin1')
if (status !== 'OKAY' && throw_on_fail) {
throw new Error(`ADB command failed. Status: '${status}'`);
}
return status;
}
/**
* Reads and decodes an ADB reply. The reply is always in the form XXXXnnnn where XXXX is a 4 digit ascii hex length
*/
async read_adb_reply() {
const hexlen = await this.read_bytes(4, 'latin1');
if (/[^\da-fA-F]/.test(hexlen)) {
throw new Error('Bad ADB reply - invalid length data');
}
return this.read_bytes(parseInt(hexlen, 16), 'latin1');
}
/**
* Writes a command to the ADB socket
* @param {string} command
*/
write_adb_command(command) {
const command_bytes = Buffer.from(command);
const command_length = Buffer.from(('000' + command_bytes.byteLength.toString(16)).slice(-4));
return this.write_bytes(Buffer.concat([command_length, command_bytes]));
}
/**
* Sends an ADB command and checks the returned status
* @param {String} command ADB command to send
* @returns {Promise<string>} OKAY status or rejected
*/
async cmd_and_status(command) {
await this.write_adb_command(command);
return this.read_adb_status();
}
/**
* Sends an ADB command, checks the returned status and then reads the return reply
* @param {String} command ADB command to send
* @returns {Promise<string>} reply string or rejected if the status is not OKAY
*/
async cmd_and_reply(command) {
await this.cmd_and_status(command);
return this.read_adb_reply();
}
/**
* Sends an ADB command, checks the returned status and then reads raw data from the socket
* @param {string} command
* @param {number} timeout_ms
* @param {boolean} [until_closed]
*/
async cmd_and_read_stdout(command, timeout_ms, until_closed) {
await this.cmd_and_status(command);
const buf = await this.read_stdout(timeout_ms, until_closed);
return buf.toString('latin1');
}
/**
* Copies a file to the device, setting the file time and permissions
* @param {ADBFileTransferParams} file file parameters
*/
async transfer_file(file) {
await this.cmd_and_status('sync:');
// initiate the file send
const filename_and_perms = `${file.pathname},${file.perms}`;
const send_and_fileinfo = Buffer.from(`SEND\0\0\0\0${filename_and_perms}`);
send_and_fileinfo.writeUInt32LE(filename_and_perms.length, 4);
await this.write_bytes(send_and_fileinfo);
// send the file data
await this.write_file_data(file.data);
// send the DONE message with the new filetime
const done_and_mtime = Buffer.from('DONE\0\0\0\0');
done_and_mtime.writeUInt32LE(file.mtime, 4);
await this.write_bytes(done_and_mtime);
// read the final status and any error message
const result = await this.read_adb_status(false);
const failmsg = await this.read_le_length_data('latin1');
// finish the transfer mode
await this.write_bytes('QUIT\0\0\0\0');
if (result !== 'OKAY') {
throw new Error(`File transfer failed. ${failmsg}`);
}
return true;
}
/**
* @param {Buffer} data
*/
async write_file_data(data) {
const dtinfo = {
transferred: 0,
transferring: 0,
chunk_size: 10240,
};
for (;;) {
dtinfo.transferred += dtinfo.transferring;
const remaining = data.byteLength - dtinfo.transferred;
if (remaining <= 0 || isNaN(remaining)) {
return dtinfo.transferred;
}
const datalen = Math.min(remaining, dtinfo.chunk_size);
const cmd = Buffer.concat([Buffer.from(`DATA\0\0\0\0`), data.slice(dtinfo.transferred, dtinfo.transferred + datalen)]);
cmd.writeUInt32LE(datalen, 4);
dtinfo.transferring = datalen;
await this.write_bytes(cmd);
}
}
}
module.exports = ADBSocket;