private attachAndroid()

in src/debugger/cordovaDebugSession.ts [1448:1586]


    private attachAndroid(attachArgs: ICordovaAttachRequestArgs): Promise<ICordovaAttachRequestArgs> {
        let errorLogger = (message: string) => this.outputLogger(message, true);

        // Determine which device/emulator we are targeting
        let resolveTagetPromise = new Promise<string>(async (resolve, reject) => {
            try {
                const devicesOutput = await this.runAdbCommand(["devices"], errorLogger);
                try {
                    const result = await this.resolveAndroidTarget(attachArgs, true);
                    if (!result) {
                        errorLogger(devicesOutput);
                        reject(new Error(`Unable to find target ${attachArgs.target}`));
                    }
                    resolve(result.id);
                } catch (error) {
                    reject(error);
                }
            } catch (error) {
                let errorCode: string = (<any>error).code;
                if (errorCode && errorCode === "ENOENT") {
                    throw new Error(localize("UnableToFindAdb", "Unable to find adb. Please ensure it is in your PATH and re-open Visual Studio Code"));
                }
                throw error;
            }
        });

        let packagePromise: Promise<string> = fs.promises.readFile(path.join(attachArgs.cwd, ANDROID_MANIFEST_PATH))
            .catch((err) => {
                if (err && err.code === "ENOENT") {
                    return fs.promises.readFile(path.join(attachArgs.cwd, ANDROID_MANIFEST_PATH_8));
                }
                throw err;
            })
            .then((manifestContents) => {
                let parsedFile = elementtree.XML(manifestContents.toString());
                let packageKey = "package";
                return parsedFile.attrib[packageKey];
            });

        return Promise.all([packagePromise, resolveTagetPromise])
            .then(([appPackageName, targetDevice]) => {
                let pidofCommandArguments = ["-s", targetDevice, "shell", "pidof", appPackageName];
                let getPidCommandArguments = ["-s", targetDevice, "shell", "ps"];
                let getSocketsCommandArguments = ["-s", targetDevice, "shell", "cat /proc/net/unix"];

                let findAbstractNameFunction = () =>
                    // Get the pid from app package name
                    this.runAdbCommand(pidofCommandArguments, errorLogger)
                        .then((pid) => {
                            if (pid && /^[0-9]+$/.test(pid.trim())) {
                                return pid.trim();
                            }

                            throw Error(CordovaDebugSession.pidofNotFoundError);

                        }).catch((err) => {
                            if (err.message !== CordovaDebugSession.pidofNotFoundError) {
                                return;
                            }

                            return this.runAdbCommand(getPidCommandArguments, errorLogger)
                                .then((psResult) => {
                                    const lines = psResult.split("\n");
                                    const keys = lines.shift().split(PS_FIELDS_SPLITTER_RE);
                                    const nameIdx = keys.indexOf("NAME");
                                    const pidIdx = keys.indexOf("PID");
                                    for (const line of lines) {
                                        const fields = line.trim().split(PS_FIELDS_SPLITTER_RE).filter(field => !!field);
                                        if (fields.length < nameIdx) {
                                            continue;
                                        }
                                        if (fields[nameIdx] === appPackageName) {
                                            return fields[pidIdx];
                                        }
                                    }
                                });
                        })
                        // Get the "_devtools_remote" abstract name by filtering /proc/net/unix with process inodes
                        .then(pid =>
                            this.runAdbCommand(getSocketsCommandArguments, errorLogger)
                                .then((getSocketsResult) => {
                                    const lines = getSocketsResult.split("\n");
                                    const keys = lines.shift().split(/[\s\r]+/);
                                    const flagsIdx = keys.indexOf("Flags");
                                    const stIdx = keys.indexOf("St");
                                    const pathIdx = keys.indexOf("Path");
                                    for (const line of lines) {
                                        const fields = line.split(/[\s\r]+/);
                                        if (fields.length < 8) {
                                            continue;
                                        }
                                        // flag = 00010000 (16) -> accepting connection
                                        // state = 01 (1) -> unconnected
                                        if (fields[flagsIdx] !== "00010000" || fields[stIdx] !== "01") {
                                            continue;
                                        }
                                        const pathField = fields[pathIdx];
                                        if (pathField.length < 1 || pathField[0] !== "@") {
                                            continue;
                                        }
                                        if (pathField.indexOf("_devtools_remote") === -1) {
                                            continue;
                                        }

                                        if (pathField === `@webview_devtools_remote_${pid}`) {
                                            // Matches the plain cordova webview format
                                            return pathField.substr(1);
                                        }

                                        if (pathField === `@${appPackageName}_devtools_remote`) {
                                            // Matches the crosswalk format of "@PACKAGENAME_devtools_remote
                                            return pathField.substr(1);
                                        }
                                        // No match, keep searching
                                    }
                                })
                        );

                return retryAsync(
                    findAbstractNameFunction,
                    (match) => !!match,
                    5,
                    1,
                    5000,
                    localize("UnableToFindLocalAbstractName", "Unable to find 'localabstract' name of Cordova app"),
                    this.cancellationTokenSource.token
                )
                    .then((abstractName) => {
                        // Configure port forwarding to the app
                        let forwardSocketCommandArguments = ["-s", targetDevice, "forward", `tcp:${attachArgs.port}`, `localabstract:${abstractName}`];
                        this.outputLogger(localize("ForwardingDebugPort", "Forwarding debug port"));
                        return this.runAdbCommand(forwardSocketCommandArguments, errorLogger).then(() => {
                            this.adbPortForwardingInfo = { targetDevice, port: attachArgs.port };
                        });
                    });
            }).then(() => {
                return attachArgs;
            });
    }