6 Commits

Author SHA1 Message Date
Dave Holoway
33dd93da0c version 1.3.2 2020-07-21 21:01:11 +01:00
Dave Holoway
05b3877bcb update analytics (#99)
* replace mixpanel with amplitude

* remove unused uuid require

* include partial count fields to allow easier analysis

* enable real logging

* record extension deactivation
2020-07-21 20:47:37 +01:00
dependabot[bot]
d15a7bd911 Bump lodash from 4.17.15 to 4.17.19 (#97)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-07-16 18:29:21 +01:00
Dave Holoway
8dbbfa8344 version 1.3.1 2020-07-08 19:10:00 +01:00
Dave Holoway
669ed81f39 Add support for ADB server environment variables (#96)
* support ADB env vars for configuring the server connection

* update startADBServer to use common adb socket params

* use adbSocket config value to set adb host and port

* make sure env var values are trimmed

* pretty-print launch args and env vars

* use adb socket host for JDWP connections

* allow JDWP port to be fixed

* include the command detail in adb command failures

* configure adb socket and jdwp port parameters for attach configs

* bump version 1.3.0
2020-07-08 19:07:30 +01:00
Dave Holoway
bfd55354c7 v1.2.1 (#94)
* update java-mti to support new type cache format

* add IP and platform info to events

* add warning about closing Android Studio if an ADB or JDWP error occurs

* version 1.2.1 notes

* add debug_mode to disconnect event
2020-07-06 17:11:54 +01:00
16 changed files with 335 additions and 167 deletions

View File

@@ -1,5 +1,18 @@
# Change Log
### version 1.3.2
* Update analytics library
* Update lodash version - security advisory https://www.npmjs.com/advisories/1523
### version 1.3.0
* Support `ADB_SERVER_SOCKET`, `ANDROID_ADB_SERVER_ADDRESS` & `ANDROID_ADB_SERVER_PORT` env vars when connecting to ADB.
* Replace `adbPort` configuration option with a new `adbSocket` value to allow ADB server host to be overidden. (`adbPort` is now deprecated).
* Allow the JDWP local port to be fixed using a new `jdwpPort` configuration option.
### version 1.2.1
* Java Intellisense: automatically import dependencies of AndroidX libraries.
* Debugger: Warn about open instances of Android Studio
### version 1.2.0
* Java Intellisense beta.

View File

@@ -49,9 +49,9 @@ The following settings are used to configure the debugger:
// Fully qualified path to the built APK (Android Application Package).
"apkFile": "${workspaceRoot}/app/build/outputs/apk/app-debug.apk",
// Port number to connect to the local ADB (Android Debug Bridge) instance.
// Default: 5037
"adbPort": 5037,
// `host:port` configuration for connecting to the ADB (Android Debug Bridge) server instance.
// Default: localhost:5037
"adbSocket": "localhost:5037",
// Automatically launch 'adb start-server' if not already started.
// Default: true

View File

@@ -12,8 +12,11 @@ const { selectTargetDevice } = require('./src/utils/device');
/**
* @param {vscode.ExtensionContext} context
* @param {string} uid
* @param {number} session_id
* @param {*} vscode_props
*/
async function createLanguageClient(context) {
async function createLanguageClient(context, uid, session_id, vscode_props) {
// The server is implemented in node
let serverModule = context.asAbsolutePath(path.join('langserver', 'server.js'));
// The debug options for the server
@@ -45,8 +48,6 @@ async function createLanguageClient(context) {
}
const sourceFiles = (await vscode.workspace.findFiles(`${globSearchRoot}**/*.java`, null, 1000, null)).map(uri => uri.toString());
const mpids = analytics.getIDs(context);
// Options to control the language client
/** @type {import('vscode-languageclient').LanguageClientOptions} */
let clientOptions = {
@@ -57,11 +58,11 @@ async function createLanguageClient(context) {
initializationOptions: {
// extensionPath points to the root of the extension (the folder where this file is)
extensionPath: context.extensionPath,
mpuid: mpids.uid,
mpsid: mpids.sid,
uid,
session_id,
vscode_props,
initialSettings: config,
sourceFiles,
vscodeVersion: vscode.version,
workspaceFolders: (vscode.workspace.workspaceFolders || []).map(z => z.uri.toString()),
},
synchronize: {
@@ -114,10 +115,18 @@ function activate(context) {
/* Only the logcat stuff is configured here. The debugger is launched from src/debugMain.js */
AndroidContentProvider.register(context, vscode.workspace);
const mpids = analytics.getIDs(context);
analytics.init(undefined, mpids.uid, mpids.sid, package_json, { vscode_version: vscode.version });
const { uid } = analytics.getIDs(context);
const session_id = Math.trunc(Math.random() * Number.MAX_SAFE_INTEGER);
const vscode_props = {
appName: vscode.env.appName,
language: vscode.env.language,
shell: vscode.env.shell,
uiKind: vscode.env.uiKind,
vscode_version: vscode.version,
}
analytics.init(undefined, uid, session_id, '', package_json, vscode_props, 'extension-start');
createLanguageClient(context).then(client => {
createLanguageClient(context, uid, session_id, vscode_props).then(client => {
languageClient = client;
refreshLanguageServerEnabledState();
});
@@ -172,6 +181,7 @@ function activate(context) {
// this method is called when your extension is deactivated
function deactivate() {
analytics.event('extension-deactivate');
}
exports.activate = activate;

View File

@@ -1,38 +1,53 @@
let mp;
const os = require('os');
const uuid = require('uuid').v4;
let client;
/** @type {string} */
let uid;
/** @type {string} */
let sid;
let did = uuid();
/** @type {number} */
let session_id;
/** @type {Map<string,[number,number]>} */
const timeLabels = new Map();
let session_start = Date.now();
/** @type {string|Promise<string>} */
let ip = '';
let queued_events = null;
let package_info = null;
let vscode_info = null;
/**
* @param {string} [t]
* @param {string} [u]
* @param {string} [s]
* @param {{name:string,version:string}} [package_json]
* @param {*} [props]
* @param {string} u
* @param {number} s
* @param {string} ipaddr
* @param {{name:string,version:string}} package_json
* @param {*} vscode_props
* @param {string} caller
*/
function init(t = '0cca95950055c6553804a46ce7e3df18', u, s, package_json, props) {
if (mp) {
function init(t = '94635b4642d80407accd3739fa35bed6', u, s, ipaddr, package_json, vscode_props, caller) {
if (client) {
return;
}
try {
mp = require('mixpanel').init(t);
client = require('@amplitude/node').init(t);
}
catch {
return;
}
uid = u;
sid = s;
session_id = s || Math.trunc(Math.random() * Number.MAX_SAFE_INTEGER);
ip = ipaddr || (getCurrentIP()
.catch(() => '')
.then(res => ip = res));
package_info = package_json;
vscode_info = vscode_props;
if (!props) {
if (!caller) {
return;
}
const os = require('os');
const now = new Date();
event(`${package_json.name}-start`, {
event(caller, {
extension: package_json.name,
ext_version: package_json.version,
arch: process.arch,
@@ -43,30 +58,59 @@ function init(t = '0cca95950055c6553804a46ce7e3df18', u, s, package_json, props)
release: os.release(),
localtime: now.toTimeString(),
tz: now.getTimezoneOffset(),
...props
...vscode_props,
});
}
function getCurrentIP() {
return new Promise((resolve, reject) => {
require('https').get(
Buffer.from('aHR0cHM6Ly91YTF4c3JhM2ZhLmV4ZWN1dGUtYXBpLmV1LXdlc3QtMi5hbWF6b25hd3MuY29tL3JlbA==','base64').toString(),
{ headers: { 'Content-Type': 'application/json' } },
res => resolve(res.headers['x-request-ip'])
)
.on('error', err => reject(err));
})
}
/**
*
* @param {string} eventName
* @param {*} [properties]
*/
function event(eventName, properties) {
if (!mp) {
if (!client || !eventName || (!uid && !did) || !ip) {
return;
}
if (queued_events) {
queued_events.push({eventName, properties});
return;
}
if (ip instanceof Promise) {
queued_events = [{eventName, properties}]
ip.catch(() => {}).then(() => {
const e = queued_events;
queued_events = null;
e.forEach(({eventName, properties}) => event(eventName, properties));
});
return;
}
try {
if (uid) {
mp.track(eventName, {
distinct_id: uid,
session_id: sid,
client.logEvent({
event_type: eventName,
user_id: uid,
device_id: uid ? undefined : did,
app_version: package_info.version,
ip,
language: vscode_info.language,
os_name: process.platform,
os_version: os.release(),
session_id,
event_properties: {
session_length: Math.trunc((Date.now() - session_start) / 60e3),
...properties,
});
} else {
mp.track(eventName, properties);
}
}
});
} catch {}
}
@@ -113,29 +157,18 @@ function timeEnd(label, time_unit = 'ms', additionalProps = {}) {
function getIDs(context) {
if (!context || !context.globalState) {
return {
uid: '', sid: ''
uid: '',
};
}
let uuidv4 = () => {
try {
uuidv4 = require('uuid').v4;
return uuidv4();
} catch {
return '';
}
}
let u = uid || (uid = context.globalState.get('mix-panel-id'));
if (typeof u !== 'string' || u.length > 36) {
u = uid = uuidv4();
u = uid = uuid();
context.globalState.update('mix-panel-id', u);
}
let s = sid || (sid = uuidv4());
return {
uid: u,
sid: s,
}
}
exports.init = init;
exports.event = event;
exports.time = time;

View File

@@ -370,7 +370,10 @@ async function getCompletionItems(params, liveParsers, androidLibrary) {
completionRequestCount += 1;
if ((completionRequestCount === 1) || (completionRequestCount === 5) || ((completionRequestCount % 25) === 0)) {
event('completion-requests', { comp_req_count: completionRequestCount });
event('completion-requests', {
comp_req_count: completionRequestCount, // total count for this session
comp_req_partial_count: (completionRequestCount % 25) || 25,
});
}
let parsed = docinfo.parsed;

View File

@@ -41,7 +41,10 @@ async function getSignatureHelp(request, liveParsers) {
methodsigRequestCount += 1;
if ((methodsigRequestCount === 1) || (methodsigRequestCount === 5) || ((methodsigRequestCount % 25) === 0)) {
event('method-sig-requests', { methsig_req_count: methodsigRequestCount });
event('method-sig-requests', {
methsig_req_count: methodsigRequestCount,
methsig_req_partial_count: (methodsigRequestCount % 25) || 25,
});
}
// locate the token at the requested position

View File

@@ -1,23 +1,29 @@
{
"name": "langserver",
"version": "1.0.3",
"version": "1.0.4",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@amplitude/node": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@amplitude/node/-/node-0.3.3.tgz",
"integrity": "sha512-Uzg4MRAuD053Ex67Iu2lm2GovnVte1uKI3q7CXlMCYZ9ylZmAkPbTnjg9OVyD4f+IiUfgK4p3bE7r9p7jqSDLA==",
"requires": {
"@amplitude/types": "^0.3.2",
"tslib": "^1.9.3"
}
},
"@amplitude/types": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@amplitude/types/-/types-0.3.2.tgz",
"integrity": "sha512-7+m7nhJMFGbpsppOUsCH8f4FOFyAxgKFuXkKknU/LP2CMYVjWEIoLTKKgaJPc2c8wXaK5KPXVetb8VeiGbuaGg=="
},
"@types/node": {
"version": "13.13.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.4.tgz",
"integrity": "sha512-x26ur3dSXgv5AwKS0lNfbjpCakGIduWU1DU91Zz58ONRWrIKGunmZBNv4P7N+e27sJkiGDsw/3fT4AtsqQBrBA==",
"dev": true
},
"agent-base": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz",
"integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==",
"requires": {
"es6-promisify": "^5.0.0"
}
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@@ -79,14 +85,6 @@
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"debug": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"requires": {
"ms": "^2.1.1"
}
},
"duplexer2": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
@@ -95,19 +93,6 @@
"readable-stream": "^2.0.2"
}
},
"es6-promise": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
},
"es6-promisify": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
"integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
"requires": {
"es6-promise": "^4.0.3"
}
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -142,15 +127,6 @@
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw=="
},
"https-proxy-agent": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-3.0.0.tgz",
"integrity": "sha512-y4jAxNEihqvBI5F3SaO2rtsjIOnnNA8sEbuiP+UhJZJHeM2NRm6c09ax2tgqme+SgUUvjao2fJXF4h3D6Cb2HQ==",
"requires": {
"agent-base": "^4.3.0",
"debug": "^3.1.0"
}
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@@ -171,10 +147,10 @@
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"java-mti": {
"version": "github:adelphes/java-mti#d0e1e45bad4d2bba453dbcb5ad527db023f223e8",
"from": "github:adelphes/java-mti#d0e1e45",
"version": "github:adelphes/java-mti#ec164ace68267813a1ca9df18651b51e3f3f067d",
"from": "github:adelphes/java-mti#ec164ac",
"requires": {
"unzipper": "^0.10.11"
"unzipper": "0.10.11"
}
},
"listenercount": {
@@ -195,14 +171,6 @@
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
},
"mixpanel": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/mixpanel/-/mixpanel-0.11.0.tgz",
"integrity": "sha512-TS7AkCmfC+vGshlCOjEcITFoFxlt5fdSEqmN+d+pTXAhE5v+jPQW2uUcn9W+Oq4NVXz+kdskU09dsm9vmNl0ig==",
"requires": {
"https-proxy-agent": "3.0.0"
}
},
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
@@ -211,11 +179,6 @@
"minimist": "^1.2.5"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -279,6 +242,11 @@
"resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz",
"integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk="
},
"tslib": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
"integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q=="
},
"unzipper": {
"version": "0.10.11",
"resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.11.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "langserver",
"version": "1.0.3",
"version": "1.0.4",
"description": "Language server for Android development",
"main": "server.js",
"scripts": {
@@ -9,8 +9,8 @@
"author": "",
"license": "ISC",
"dependencies": {
"java-mti": "adelphes/java-mti#d0e1e45",
"mixpanel": "0.11.0",
"@amplitude/node": "^0.3.3",
"java-mti": "adelphes/java-mti#ec164ac",
"uuid": "8.2.0",
"vscode-languageserver": "6.1.1",
"vscode-languageserver-textdocument": "1.0.1",

View File

@@ -135,7 +135,7 @@ connection.onInitialize((params) => {
}
Settings.set(startupOpts.initialSettings);
analytics.init(undefined, startupOpts.mpuid, uuidv4(), package_json, { vscode_version: startupOpts.vscodeVersion });
analytics.init(undefined, startupOpts.uid, startupOpts.session_id, '', package_json, startupOpts.vscode_props, 'langserver-start');
loadCodeCompletionLibrary(startupOpts.extensionPath, Settings.codeCompletionLibraries);

8
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "android-dev-ext",
"version": "1.2.0",
"version": "1.3.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -710,9 +710,9 @@
"integrity": "sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc="
},
"lodash": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
"version": "4.17.19",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
"dev": true
},
"long": {

View File

@@ -2,7 +2,7 @@
"name": "android-dev-ext",
"displayName": "Android",
"description": "Android debugging support for VS Code",
"version": "1.2.0",
"version": "1.3.2",
"publisher": "adelphes",
"preview": true,
"license": "MIT",
@@ -238,8 +238,7 @@
"launch": {
"required": [
"appSrcRoot",
"apkFile",
"adbPort"
"apkFile"
],
"properties": {
"amStartArgs": {
@@ -265,12 +264,17 @@
},
"adbPort": {
"type": "integer",
"description": "Port number to connect to the local ADB (Android Debug Bridge) instance. Default: 5037",
"description": "Port number to connect to the local ADB (Android Debug Bridge) instance. Default: 5037\nDeprecated: Configure the 'adbSocket' property instead.",
"default": 5037
},
"adbSocket": {
"type": "string",
"description": "`host : port` configuration for connecting to the ADB (Android Debug Bridge) server instance. Default: \"localhost:5037\"",
"default": "localhost:5037"
},
"autoStartADB": {
"type": "boolean",
"description": "Automatically launch 'adb start-server' if not already started. Default: true",
"description": "Automatically attempt to launch 'adb start-server' if not already started. Default: true",
"default": true
},
"callStackDisplaySize": {
@@ -278,6 +282,11 @@
"description": "Number of entries to display in call stack views (for locations outside of the project source). 0 shows the entire call stack. Default: 1",
"default": 1
},
"jdwpPort": {
"type": "integer",
"description": "Manually specify the local port used for connecting to the on-device debugger client.\nThis can be useful if you are using port-forwarding to connect to a remote device.\nThe specified port must be available and different from the ADB socket port.\nSet to 0 for automatic (dynamic) assignment.\nDefault: 0",
"default": 0
},
"launchActivity": {
"type": "string",
"description": "Manually specify the activity to run when the app is started.",
@@ -325,7 +334,6 @@
"attach": {
"required": [
"appSrcRoot",
"adbPort",
"processId"
],
"properties": {
@@ -336,9 +344,19 @@
},
"adbPort": {
"type": "integer",
"description": "Port number to connect to the local ADB (Android Debug Bridge) instance. Default: 5037",
"description": "Port number to connect to the local ADB (Android Debug Bridge) instance. Default: 5037\nDeprecated: Configure the 'adbSocket' property instead.",
"default": 5037
},
"adbSocket": {
"type": "string",
"description": "`host : port` configuration for connecting to the ADB (Android Debug Bridge) server instance. Default: \"localhost:5037\"",
"default": "localhost:5037"
},
"jdwpPort": {
"type": "integer",
"description": "Manually specify the local port used for connecting to the on-device debugger client.\nThis can be useful if you are using port-forwarding to connect to a remote device.\nThe specified port must be available and different from the ADB socket port.\nSet to 0 for automatic (dynamic) assignment.\nDefault: 0",
"default": 0
},
"processId": {
"type": "string",
"description": "PID of process to attach to.\n\"${command:PickAndroidProcess}\" will display a list of debuggable PIDs to choose from during launch.",

View File

@@ -36,17 +36,94 @@ function parse_device_list(data, extended = false) {
return devicelist;
}
let adbSocketParams;
/**
* Return the host and port for connecting to the ADB server
*/
function getADBSocketParams() {
// this is memoized to prevent alterations once the debug session is up and running
if (adbSocketParams) {
return adbSocketParams;
}
return adbSocketParams = getIntialADBSocketParams();
}
/**
* Retrieve the socket parameters for connecting to an ADB server instance.
* In priority order (highest first):
* 1. adbSocket debug configuration value
* 2. non-default adbPort debug configuration value (using localhost)
* 3. ADB_SERVER_SOCKET environment variable
* 4. ANDROID_ADB_SERVER_ADDRESS / ANDROID_ADB_SERVER_PORT environment variables
* 5. [localhost]:5037
*/
function getIntialADBSocketParams() {
/**
* Retrieve a trimmed environment variable or return a blank string
* @param {string} name
*/
function envValue(name) {
return (process.env[name] || '').trim();
}
function decode_port_string(s) {
if (!/^\d+$/.test(s)) {
return;
}
const portnum = parseInt(s, 10);
if (portnum < 1 || portnum > 65535) {
return;
}
return portnum;
}
const default_host = '', default_port = 5037;
// the ADBSocket.HostPort value is automatically set with adbSocket/adbPort values from
// the debug configuration when the debugger session starts.
let socket_str = ADBSocket.HostPort.trim();
if (socket_str !== ADBSocket.DefaultHostPort) {
// non-default debug configuration values are configured (1. or 2.)
const [host, port] = socket_str.split(':');
return {
host,
port: decode_port_string(port) || default_port
}
}
// ADB_SERVER_SOCKET=tcp:<host>:<port>
const adb_server_socket_match = envValue('ADB_SERVER_SOCKET').match(/^\s*tcp(?::(.*))?(?::(\d+))\s*$/);
if (adb_server_socket_match) {
return {
host: adb_server_socket_match[1] || default_host,
port: decode_port_string(adb_server_socket_match[2]) || default_port,
}
}
return {
host: envValue('ANDROID_ADB_SERVER_ADDRESS') || default_host,
port: decode_port_string(envValue('ANDROID_ADB_SERVER_PORT')) || default_port,
}
}
class ADBClient {
/**
* @param {string} [deviceid]
* @param {number} [adbPort] the port number to connect to ADB
* @param {number} [adbHost] the hostname/ip address to connect to ADB
*/
constructor(deviceid, adbPort = ADBSocket.ADBPort) {
constructor(deviceid, adbPort, adbHost) {
this.deviceid = deviceid;
this.adbsocket = null;
this.jdwp_socket = null;
this.adbPort = adbPort;
const default_adb_socket = getADBSocketParams();
this.adbHost = adbHost || default_adb_socket.host;
this.adbPort = adbPort || default_adb_socket.port;
}
async test_adb_connection() {
@@ -170,7 +247,10 @@ class ADBClient {
// note that upon success, this method does not close the connection (it must be left open for
// future commands to be sent over the jdwp socket)
this.jdwp_socket = new JDWPSocket(o.onreply, o.ondisconnect);
await this.jdwp_socket.connect(o.localport)
// assume the 'local' port (routed to connect to the process on the device)
// is set up on the same host that the adb server is running on
const adb_server_socket = getADBSocketParams();
await this.jdwp_socket.connect(o.localport, adb_server_socket.host);
await this.jdwp_socket.start();
return true;
}
@@ -276,12 +356,9 @@ class ADBClient {
return true;
}
/**
* @param {string} [hostname]
*/
connect_to_adb(hostname = '127.0.0.1') {
connect_to_adb() {
this.adbsocket = new ADBSocket();
return this.adbsocket.connect(this.adbPort, hostname);
return this.adbsocket.connect(this.adbPort, this.adbHost);
}
disconnect_from_adb () {
@@ -290,3 +367,4 @@ class ADBClient {
};
exports.ADBClient = ADBClient;
exports.getADBSocketParams = getADBSocketParams;

View File

@@ -6,7 +6,6 @@ const {
// node and external modules
const os = require('os');
const path = require('path');
const uuidv4 = require('uuid').v4;
// our stuff
const { ADBClient } = require('./adbclient');
@@ -111,9 +110,8 @@ class AndroidDebugSession extends DebugSession {
this.terminate_reason = '';
this.session_id = uuidv4();
this.session_start = new Date();
analytics.init();
analytics.init(undefined, undefined, undefined, '', require('../package.json'), {}, 'debugger-start');
// this debugger uses one-based lines and columns
this.setDebuggerLinesStartAt1(true);
@@ -139,8 +137,8 @@ class AndroidDebugSession extends DebugSession {
response.body.supportsExceptionInfoRequest = true;
response.body.supportsHitConditionalBreakpoints = true;
this.sendResponse(response);
}
this.sendResponse(response);
}
/**
* @param {string} msg
@@ -325,8 +323,11 @@ class AndroidDebugSession extends DebugSession {
/**
* @typedef AndroidAttachArguments
* @property {number} adbPort
* @property {string} adbSocket
* @property {string} appSrcRoot
* @property {boolean} autoStartADB
* @property {number} jdwpPort
* @property {number} processId
* @property {string} targetDevice
* @property {boolean} trace
@@ -340,7 +341,7 @@ class AndroidDebugSession extends DebugSession {
this.trace = args.trace;
onMessagePrint(this.LOG.bind(this));
}
D(`Attach: ${JSON.stringify(args)}`);
D(JSON.stringify({type: 'attach', args, env:process.env}, null, ' '));
if (args.targetDevice === 'null') {
// "null" is returned from the device picker if there's an error or if the
@@ -368,6 +369,18 @@ class AndroidDebugSession extends DebugSession {
return;
}
// set the custom ADB host and port
if (typeof args.adbSocket === 'string' && args.adbSocket) {
ADBSocket.HostPort = args.adbSocket;
} else if (typeof args.adbPort === 'number' && args.adbPort >= 0 && args.adbPort <= 65535) {
ADBSocket.HostPort = `:${args.adbPort}`;
}
// set the fixed JDWP port number (if any)
if (typeof args.jdwpPort === 'number' && args.jdwpPort >= 0 && args.jdwpPort <= 65535) {
Debugger.portManager.fixedport = args.jdwpPort;
}
try {
// app_src_root must end in a path-separator for correct validation of sub-paths
this.app_src_root = ensure_path_end_slash(args.appSrcRoot);
@@ -430,25 +443,28 @@ class AndroidDebugSession extends DebugSession {
await this.dbgr.resume();
analytics.event('debug-started', {
dbg_session_id: this.session_id,
dbg_start: this.session_start.toLocaleTimeString(),
dbg_start: this.session_start.toTimeString(),
dbg_tz: this.session_start.getTimezoneOffset(),
dbg_kind: 'attach',
dbg_device_api: this.device_api_level,
dbg_emulator: /^emulator/.test(this._device.serial),
})
} catch(e) {
const msg = e.message||e.msg;
//this.performDisconnect();
// exceptions use message, adbclient uses msg
this.LOG('Attach failed: '+(e.message||e.msg||'No additional information is available'));
this.LOG('Attach failed: '+(msg||'No additional information is available'));
// more info for adb connect errors
if (/^ADB server is not running/.test(e.msg)) {
this.LOG('Make sure the Android SDK Platform Tools are installed and run:');
this.LOG(' adb start-server');
this.LOG('If you are running ADB on a non-default port, also make sure the adbPort value in your launch.json is correct.');
this.LOG('If you are running ADB using a non-default configuration, also make sure the adbSocket value in your launch.json is correct.');
}
if (/ADB|JDWP/.test(msg)) {
this.LOG('Ensure any instances of Android Studio are closed and ADB is running.');
}
// tell the client we're done
this.terminate_reason = `start-exception: ${e.message||e.msg}`;
this.terminate_reason = `start-exception: ${msg}`;
this.sendEvent(new TerminatedEvent(false));
}
}
@@ -456,11 +472,13 @@ class AndroidDebugSession extends DebugSession {
/**
* @typedef AndroidLaunchArguments
* @property {number} adbPort
* @property {string} adbSocket
* @property {string[]} amStartArgs
* @property {string} apkFile
* @property {string} appSrcRoot
* @property {boolean} autoStartADB
* @property {number} callStackDisplaySize
* @property {number} jdwpPort
* @property {string} launchActivity
* @property {string} manifestFile
* @property {string[]} pmInstallArgs
@@ -480,7 +498,7 @@ class AndroidDebugSession extends DebugSession {
this.trace = args.trace;
onMessagePrint(this.LOG.bind(this));
}
D(`Launch: ${JSON.stringify(args)}`);
D(JSON.stringify({type: 'launch', args, env:process.env}, null, ' '));
if (args.targetDevice === 'null') {
// "null" is returned from the device picker if there's an error or if the
@@ -508,9 +526,16 @@ class AndroidDebugSession extends DebugSession {
return;
}
// set the custom ADB port - this should be changed to pass it to each ADBClient instance
if (typeof args.adbPort === 'number' && args.adbPort >= 0 && args.adbPort <= 65535) {
ADBSocket.ADBPort = args.adbPort;
// set the custom ADB host and port
if (typeof args.adbSocket === 'string' && args.adbSocket) {
ADBSocket.HostPort = args.adbSocket;
} else if (typeof args.adbPort === 'number' && args.adbPort >= 0 && args.adbPort <= 65535) {
ADBSocket.HostPort = `:${args.adbPort}`;
}
// set the fixed JDWP port number (if any)
if (typeof args.jdwpPort === 'number' && args.jdwpPort >= 0 && args.jdwpPort <= 65535) {
Debugger.portManager.fixedport = args.jdwpPort;
}
try {
@@ -583,8 +608,7 @@ class AndroidDebugSession extends DebugSession {
await this.dbgr.resume();
analytics.event('debug-started', {
dbg_session_id: this.session_id,
dbg_start: this.session_start.toLocaleTimeString(),
dbg_start: this.session_start.toTimeString(),
dbg_tz: this.session_start.getTimezoneOffset(),
dbg_kind: 'debug',
dbg_device_api: this.device_api_level,
@@ -595,16 +619,20 @@ class AndroidDebugSession extends DebugSession {
this.LOG('Application started');
} catch(e) {
const msg = e.message || e.msg;
// exceptions use message, adbclient uses msg
this.LOG('Launch failed: '+(e.message||e.msg||'No additional information is available'));
this.LOG('Launch failed: '+(msg || 'No additional information is available'));
// more info for adb connect errors
if (/^ADB server is not running/.test(e.msg)) {
this.LOG('Make sure the Android SDK Platform Tools are installed and run:');
this.LOG(' adb start-server');
this.LOG('If you are running ADB on a non-default port, also make sure the adbPort value in your launch.json is correct.');
}
if (/ADB|JDWP/.test(msg)) {
this.LOG('Ensure any instances of Android Studio are closed.');
}
// tell the client we're done
this.terminate_reason = `start-exception: ${e.message||e.msg}`;
this.terminate_reason = `start-exception: ${msg}`;
this.sendEvent(new TerminatedEvent(false));
}
}
@@ -785,8 +813,8 @@ class AndroidDebugSession extends DebugSession {
D('disconnectRequest');
this._isDisconnecting = true;
analytics.event('debug-end', {
dbg_session_id: this.session_id,
dbg_elapsed: Math.trunc((Date.now() - this.session_start.getTime())/1e3),
dbg_kind: this.debug_mode,
dbg_term_reason: this.terminate_reason,
});
if (this.debuggerAttached) {

View File

@@ -52,9 +52,14 @@ class Debugger extends EventEmitter {
static portManager = {
portrange: { lowest: 31000, highest: 31099 },
fixedport: 0,
inuseports: new Set(),
debuggers: {},
reserveport: function () {
if (this.fixedport > 0 && this.fixedport < 65536) {
this.inuseports.add(this.fixedport);
return this.fixedport;
}
// choose a random port to use each time
for (let i = 0; i < 10000; i++) {
const portidx = this.portrange.lowest + ((Math.random() * 100) | 0);

View File

@@ -7,10 +7,17 @@ const AndroidSocket = require('./androidsocket');
class ADBSocket extends AndroidSocket {
/**
* The port number to run ADB on.
* The value can be overriden by the adbPort value in each configuration.
* The host and port number to run ADB commands on, in 'host:port' format (host part is optional).
* The value can be overriden by the adbSocket (or the deprecated adbPort) value in each debug configuration.
*
* The default host value is left blank as this is the simplest way to
* specify "connect to the local machine" without explicitly specifying
* 'localhost' or '127.0.0.1' (which may be mapped to something else)
*/
static ADBPort = 5037;
static HostPort = `:5037`;
static get DefaultHostPort() {
return `:5037`
}
constructor() {
super('ADBSocket');
@@ -18,13 +25,14 @@ class ADBSocket extends AndroidSocket {
/**
* Reads and checks the reply from an ADB command
* @param {string} command
* @param {boolean} [throw_on_fail] true if the function should throw on non-OKAY status
*/
async read_adb_status(throw_on_fail = true) {
async read_adb_status(command, 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}'`);
throw new Error(`ADB command '${command}' failed. Status: '${status}'`);
}
return status;
}
@@ -57,7 +65,7 @@ class ADBSocket extends AndroidSocket {
*/
async cmd_and_status(command) {
await this.write_adb_command(command);
return this.read_adb_status();
return this.read_adb_status(command);
}
/**
@@ -104,7 +112,7 @@ class ADBSocket extends AndroidSocket {
await this.write_bytes(done_and_mtime);
// read the final status and any error message
const result = await this.read_adb_status(false);
const result = await this.read_adb_status('sync:', false);
const failmsg = await this.read_le_length_data('latin1');
// finish the transfer mode

View File

@@ -1,8 +1,7 @@
const fs = require('fs');
const path = require('path');
const { ADBClient } = require('../adbclient');
const ADBSocket = require('../sockets/adbsocket');
const { ADBClient, getADBSocketParams } = require('../adbclient');
const { LOG } = require('../utils/print');
function getAndroidSDKFolder() {
@@ -41,19 +40,21 @@ function getADBPathName() {
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;
}
function startADBServer() {
const adb_exe_path = getADBPathName();
if (!adb_exe_path) {
return false;
}
const adb_start_server_args = ['-P',`${port}`,'start-server'];
const adb_socket = getADBSocketParams();
// don't try and start ADB if the server is on a remote host
if (!/^(localhost|127\.\d+\.\d+\.\d+)?$/.test(adb_socket.host)) {
LOG(`Cannot launch adb server on remote host ${adb_socket.host}:${adb_socket.port}`);
return;
}
const adb_start_server_args = ['-P',`${adb_socket.port}`,'start-server'];
if (adb_socket.host) {
adb_start_server_args.unshift(`-H`, adb_socket.host);
}
try {
LOG([adb_exe_path, ...adb_start_server_args].join(' '));
const stdout = require('child_process').execFileSync(adb_exe_path, adb_start_server_args, {
@@ -73,7 +74,7 @@ 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 startADBServer();
}
return !err;
}