mirror of
https://github.com/adelphes/android-dev-ext.git
synced 2025-12-23 18:08:29 +00:00
* 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
147 lines
4.7 KiB
JavaScript
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;
|