mirror of
https://github.com/adelphes/android-dev-ext.git
synced 2025-12-25 02:48:05 +00:00
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
This commit is contained in:
87
src/utils/android.js
Normal file
87
src/utils/android.js
Normal file
@@ -0,0 +1,87 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const { ADBClient } = require('../adbclient');
|
||||
const ADBSocket = require('../sockets/adbsocket');
|
||||
const { LOG } = require('../utils/print');
|
||||
|
||||
function getAndroidSDKFolder() {
|
||||
// ANDROID_HOME is deprecated
|
||||
return process.env.ANDROID_HOME || process.env.ANDROID_SDK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} api_level
|
||||
* @param {boolean} check_is_dir
|
||||
*/
|
||||
function getAndroidSourcesFolder(api_level, check_is_dir) {
|
||||
const android_sdk = getAndroidSDKFolder();
|
||||
if (!android_sdk) {
|
||||
return null;
|
||||
}
|
||||
const sources_path = path.join(android_sdk,'sources',`android-${api_level}`);
|
||||
if (check_is_dir) {
|
||||
try {
|
||||
const stat = fs.statSync(sources_path);
|
||||
if (!stat || !stat.isDirectory()) {
|
||||
return null;
|
||||
}
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return sources_path;
|
||||
}
|
||||
|
||||
function getADBPathName() {
|
||||
const android_sdk = getAndroidSDKFolder();
|
||||
if (!android_sdk) {
|
||||
return '';
|
||||
}
|
||||
return path.join(android_sdk, 'platform-tools', /^win/.test(process.platform)?'adb.exe':'adb');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} port
|
||||
*/
|
||||
function startADBServer(port) {
|
||||
if (typeof port !== 'number' || port <= 0 || port >= 65536) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const adb_exe_path = getADBPathName();
|
||||
if (!adb_exe_path) {
|
||||
return false;
|
||||
}
|
||||
const adb_start_server_args = ['-P',`${port}`,'start-server'];
|
||||
try {
|
||||
LOG([adb_exe_path, ...adb_start_server_args].join(' '));
|
||||
const stdout = require('child_process').execFileSync(adb_exe_path, adb_start_server_args, {
|
||||
cwd: getAndroidSDKFolder(),
|
||||
encoding:'utf8',
|
||||
});
|
||||
LOG(stdout);
|
||||
return true;
|
||||
} catch (ex) {} // if we fail, it doesn't matter - the device query will fail and the user will have to work it out themselves
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} auto_start
|
||||
*/
|
||||
async function checkADBStarted(auto_start) {
|
||||
const err = await new ADBClient().test_adb_connection();
|
||||
// if adb is not running, see if we can start it ourselves using ANDROID_HOME (and a sensible port number)
|
||||
if (err && auto_start) {
|
||||
return startADBServer(ADBSocket.ADBPort);
|
||||
}
|
||||
return !err;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
checkADBStarted,
|
||||
getADBPathName,
|
||||
getAndroidSDKFolder,
|
||||
getAndroidSourcesFolder,
|
||||
startADBServer,
|
||||
}
|
||||
55
src/utils/device.js
Normal file
55
src/utils/device.js
Normal file
@@ -0,0 +1,55 @@
|
||||
const { ADBClient } = require('../adbclient');
|
||||
|
||||
/**
|
||||
* @param {import('vscode')} vscode
|
||||
* @param {{serial:string}[]} devices
|
||||
*/
|
||||
async function showDevicePicker(vscode, devices) {
|
||||
const sorted_devices = devices.slice().sort((a,b) => a.serial.localeCompare(b.serial, undefined, {sensitivity: 'base'}));
|
||||
|
||||
/** @type {import('vscode').QuickPickItem[]} */
|
||||
const quick_pick_items = sorted_devices
|
||||
.map(device => ({
|
||||
label: `${device.serial}`,
|
||||
}));
|
||||
|
||||
/** @type {import('vscode').QuickPickOptions} */
|
||||
const quick_pick_options = {
|
||||
canPickMany: false,
|
||||
placeHolder: 'Choose an Android device',
|
||||
};
|
||||
|
||||
const chosen_option = await vscode.window.showQuickPick(quick_pick_items, quick_pick_options);
|
||||
return sorted_devices[quick_pick_items.indexOf(chosen_option)] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('vscode')} vscode
|
||||
* @param {'Attach'|'Logcat display'} action
|
||||
*/
|
||||
async function selectTargetDevice(vscode, action) {
|
||||
const devices = await new ADBClient().list_devices();
|
||||
let device;
|
||||
switch(devices.length) {
|
||||
case 0:
|
||||
vscode.window.showWarningMessage(`${action} failed. No Android devices are connected.`);
|
||||
return null;
|
||||
case 1:
|
||||
return devices[0]; // only one device - just use it
|
||||
}
|
||||
device = await showDevicePicker(vscode, devices);
|
||||
// the user might take a while to choose the device, so once
|
||||
// chosen, recheck it exists
|
||||
const current_devices = await new ADBClient().list_devices();
|
||||
if (!current_devices.find(d => d.serial === device.serial)) {
|
||||
vscode.window.showInformationMessage(`${action} failed. The target device is disconnected`);
|
||||
return null;
|
||||
}
|
||||
return device;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
selectTargetDevice,
|
||||
showDevicePicker,
|
||||
}
|
||||
@@ -35,6 +35,23 @@ function W(...args) {
|
||||
callMessagePrintCallbacks(args);
|
||||
}
|
||||
|
||||
let printLogToClient;
|
||||
function initLogToClient(fn) {
|
||||
printLogToClient = fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a log message
|
||||
* @param {*} msg
|
||||
*/
|
||||
function LOG(msg) {
|
||||
if (printLogToClient) {
|
||||
printLogToClient(msg);
|
||||
} else {
|
||||
D(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a callback to be called when any message is output
|
||||
* @param {Function} cb
|
||||
@@ -45,7 +62,9 @@ function onMessagePrint(cb) {
|
||||
|
||||
module.exports = {
|
||||
D,
|
||||
E,
|
||||
E,
|
||||
initLogToClient,
|
||||
LOG,
|
||||
W,
|
||||
onMessagePrint,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user