From 1535f133d97b770134b1194e81c9ce8c1476f539 Mon Sep 17 00:00:00 2001 From: Dave Holoway Date: Thu, 23 Apr 2020 16:44:19 +0100 Subject: [PATCH] Support for device picker during launch (#86) --- extension.js | 7 +++++++ package.json | 5 +++-- src/debugMain.js | 34 ++++++++++++++++++++++++++++++++-- src/debugger-types.js | 2 +- src/utils/device.js | 15 +++++++++++---- 5 files changed, 54 insertions(+), 9 deletions(-) diff --git a/extension.js b/extension.js index 8cc94c1..4cf65c3 100644 --- a/extension.js +++ b/extension.js @@ -4,6 +4,7 @@ const vscode = require('vscode'); const { AndroidContentProvider } = require('./src/contentprovider'); const { openLogcatWindow } = require('./src/logcat'); const { selectAndroidProcessID } = require('./src/process-attach'); +const { selectTargetDevice } = require('./src/utils/device'); // this method is called when your extension is activated // your extension is activated the very first time the command is executed @@ -18,6 +19,12 @@ function activate(context) { vscode.commands.registerCommand('android-dev-ext.view_logcat', () => { openLogcatWindow(vscode); }), + // add the device picker handler - used to choose a target device + vscode.commands.registerCommand('PickAndroidDevice', async () => { + const device = await selectTargetDevice(vscode, "Launch", { alwaysShow:true }); + // the debugger requires a string value to be returned + return JSON.stringify(device); + }), // add the process picker handler - used to choose a PID to attach to vscode.commands.registerCommand('PickAndroidProcess', async () => { const o = await selectAndroidProcessID(vscode); diff --git a/package.json b/package.json index ddde565..d2d5a15 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ }, "activationEvents": [ "onCommand:android-dev-ext.view_logcat", + "onCommand:PickAndroidDevice", "onCommand:PickAndroidProcess" ], "repository": { @@ -121,7 +122,7 @@ "targetDevice": { "type": "string", "description": "Target Device ID (as indicated by 'adb devices'). Use this to specify which device is used for deployment when multiple devices are connected.", - "default": "" + "default": "${command:PickAndroidDevice}" }, "trace": { "type": "boolean", @@ -155,7 +156,7 @@ "targetDevice": { "type": "string", "description": "Target Device ID (as indicated by 'adb devices'). Use this to specify which device is used when multiple devices are connected.", - "default": "" + "default": "${command:PickAndroidDevice}" }, "trace": { "type": "boolean", diff --git a/src/debugMain.js b/src/debugMain.js index eeffcfd..3688c61 100644 --- a/src/debugMain.js +++ b/src/debugMain.js @@ -305,6 +305,19 @@ class AndroidDebugSession extends DebugSession { } } + extractTargetDeviceID(s) { + if (!s || typeof s !== 'string') { + return ''; + } + // the device picker returns a stringified object + try { + const o = JSON.parse(s); + return o.serial || s; + } catch { + } + return s; + } + async attachRequest(response, args) { this.debug_mode = 'attach'; if (args && args.trace) { @@ -313,6 +326,14 @@ class AndroidDebugSession extends DebugSession { } D(`Attach: ${JSON.stringify(args)}`); + if (args.targetDevice === 'null') { + // "null" is returned from the device picker if there's an error or if the + // user cancels. + D('targetDevice === "null"'); + this.sendEvent(new TerminatedEvent(false)); + return; + } + if (!args.processId) { this.LOG(`Attach failed: Missing "processId" property in launch.json`); this.sendEvent(new TerminatedEvent(false)); @@ -348,7 +369,7 @@ class AndroidDebugSession extends DebugSession { try { let { processId, targetDevice } = attach_info; if (!targetDevice) { - targetDevice = args.targetDevice; + targetDevice = this.extractTargetDeviceID(args.targetDevice); } // make sure ADB exists and is started and look for a connected device await checkADBStarted(args.autoStartADB !== false); @@ -416,6 +437,14 @@ class AndroidDebugSession extends DebugSession { } D(`Launch: ${JSON.stringify(args)}`); + if (args.targetDevice === 'null') { + // "null" is returned from the device picker if there's an error or if the + // user cancels. + D('targetDevice === "null"'); + this.sendEvent(new TerminatedEvent(false)); + return; + } + // 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); this.apk_fpn = args.apkFile; @@ -465,7 +494,8 @@ class AndroidDebugSession extends DebugSession { // make sure ADB exists and is started and look for a device to install on await checkADBStarted(args.autoStartADB !== false); - this._device = await this.findSuitableDevice(args.targetDevice, true); + const targetDevice = this.extractTargetDeviceID(args.targetDevice); + this._device = await this.findSuitableDevice(targetDevice, true); this._device.adbclient = new ADBClient(this._device.serial); // install the APK we are going to debug diff --git a/src/debugger-types.js b/src/debugger-types.js index 409ca55..8ff7568 100644 --- a/src/debugger-types.js +++ b/src/debugger-types.js @@ -9,7 +9,7 @@ class BuildInfo { * @param {string} pkgname * @param {Map} packages * @param {string} launchActivity - * @param {string[]} amCommandArgs custom arguments passed to `am start` + * @param {string[]} [amCommandArgs] custom arguments passed to `am start` */ constructor(pkgname, packages, launchActivity, amCommandArgs) { this.pkgname = pkgname; diff --git a/src/utils/device.js b/src/utils/device.js index 01d81e5..a076604 100644 --- a/src/utils/device.js +++ b/src/utils/device.js @@ -26,9 +26,10 @@ async function showDevicePicker(vscode, devices) { /** * * @param {import('vscode')} vscode - * @param {'Attach'|'Logcat display'} action + * @param {'Launch'|'Attach'|'Logcat display'} action + * @param {{alwaysShow:boolean}} [options] */ -async function selectTargetDevice(vscode, action) { +async function selectTargetDevice(vscode, action, options) { const devices = await new ADBClient().list_devices(); let device; switch(devices.length) { @@ -36,14 +37,20 @@ async function selectTargetDevice(vscode, action) { vscode.window.showWarningMessage(`${action} failed. No Android devices are connected.`); return null; case 1: - return devices[0]; // only one device - just use it + if (!options || !options.alwaysShow) { + return devices[0]; // only one device - just use it + } + break; } device = await showDevicePicker(vscode, devices); + if (!device) { + return null; // user cancelled + } // 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`); + vscode.window.showInformationMessage(`${action} failed. The target device is disconnected.`); return null; } return device;