"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const child_process_1 = require("child_process");
const path_1 = require("path");
const fs_1 = require("fs");
const enums_1 = require("./enums");
const device_1 = require("./device");
const net = require("net");
const utils_1 = require("./utils");
const OFFSET_DI_PIXELS = 16;
class AndroidController {
    static getAllDevices(verbose = false) {
        return __awaiter(this, void 0, void 0, function* () {
            AndroidController.checkAndroid();
            // this should be always first.
            const runningDevices = AndroidController.parseRunningDevicesList(verbose);
            const devices = new Map();
            yield AndroidController.parseEmulators(runningDevices, devices);
            yield AndroidController.parseRealDevices(runningDevices, devices);
            return devices;
        });
    }
    static getPhysicalDensity(device) {
        return parseInt(AndroidController.executeAdbShellCommand(device, "wm density").split(":")[1]) * 0.01;
    }
    static calculateScreenOffset(density) {
        return Math.floor(OFFSET_DI_PIXELS * density);
    }
    static getPixelsOffset(device) {
        return AndroidController.calculateScreenOffset(AndroidController.getPhysicalDensity(device));
    }
    static setEmulatorConfig(device) {
        const density = AndroidController.getPhysicalDensity(device);
        const offsetPixels = AndroidController.getPixelsOffset(device);
        device.config = {
            density: density,
            offsetPixels: offsetPixels,
        };
    }
    static startEmulator(emulator, options = undefined, logPath = undefined) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!emulator.token) {
                emulator.token = AndroidController.emulatorId(emulator.apiLevel) || "5554";
            }
            const listRunningDevices = utils_1.executeCommand(AndroidController.LIST_DEVICES_COMMAND)
                .replace("List of devices attached", "").trim();
            if (listRunningDevices.includes(emulator.token)) {
                AndroidController.kill(emulator);
            }
            if (!listRunningDevices.includes(emulator.token)) {
                const avdsDirectory = process.env["AVDS_STORAGE"] || path_1.join(process.env["HOME"], "/.android/avd");
                const avd = path_1.resolve(avdsDirectory, `${emulator.name}.avd`);
                utils_1.getAllFileNames(avd).filter(f => f.endsWith(".lock")).forEach(f => {
                    try {
                        const path = path_1.resolve(avd, f);
                        console.log(`Try to delete ${path}!`);
                        if (fs_1.existsSync(path)) {
                            utils_1.logWarn(`Deleting ${path}!`);
                            fs_1.unlinkSync(path);
                            utils_1.logWarn(`Deleted ${path}!`);
                        }
                    }
                    catch (error) {
                        utils_1.logWarn(`Failed to delete lock file for ${avd}!`);
                    }
                });
            }
            emulator = yield AndroidController.startEmulatorProcess(emulator, logPath, options);
            const result = (yield AndroidController.waitUntilEmulatorBoot(emulator.token, parseInt(process.env.BOOT_ANDROID_EMULATOR_MAX_TIME) || AndroidController.DEFAULT_BOOT_TIME)) === true ? enums_1.Status.BOOTED : enums_1.Status.SHUTDOWN;
            if (result === enums_1.Status.BOOTED) {
                emulator.status = enums_1.Status.BOOTED;
                emulator.startedAt = Date.now();
            }
            AndroidController.setEmulatorConfig(emulator);
            return emulator;
        });
    }
    static reboot(emulator) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                if (AndroidController.checkIfEmulatorIsResponding(emulator)) {
                    const errorMsgType = AndroidController.getCurrentErrorMessage(emulator);
                    if (errorMsgType) {
                        AndroidController.executeAdbShellCommand(emulator, `am force - stop ${errorMsgType} `);
                        AndroidController.executeAdbShellCommand(emulator, `pm clear ${errorMsgType} `);
                    }
                }
            }
            catch (_a) { }
            AndroidController.executeAdbCommand(emulator, 'reboot bootloader');
            const result = AndroidController.waitUntilEmulatorBoot(emulator.token, AndroidController.DEFAULT_BOOT_TIME);
            if (!result) {
                emulator = yield AndroidController.kill(emulator);
                emulator = yield AndroidController.startEmulator(emulator);
            }
            return emulator;
        });
    }
    static unlock(token, password = undefined) {
        let result = "";
        if (password) {
            result = utils_1.executeCommand(`${AndroidController.sendKeyCommand(token, 82)} && ${AndroidController.ADB} -s ${token} shell input text ${password} && ${AndroidController.sendKeyCommand(token, 66)} `);
        }
        else {
            result = utils_1.executeCommand(`${AndroidController.sendKeyCommand(token, 82)} && ${AndroidController.sendKeyCommand(token, 66)} `);
        }
        if (!(result !== undefined && result !== "")) {
            utils_1.logError("We couldn't unclock the devie: ", result);
        }
    }
    /**
     * Implement kill process
     * @param emulator
     */
    static kill(emulator) {
        let isAlive = true;
        if (emulator.type === enums_1.DeviceType.EMULATOR) {
            if (emulator.token) {
                try {
                    const result = AndroidController.executeAdbCommand(emulator, " emu kill");
                }
                catch (error) { }
            }
            const killEmulatorProcesses = () => {
                if (emulator.pid) {
                    utils_1.killPid(emulator.pid);
                }
                if (!utils_1.isWin()) {
                    let grepForEmulatorProcesses = utils_1.executeCommand(`ps | grep ${emulator.name} `).split("\n");
                    utils_1.executeCommand(`ps | grep ${emulator.token} `).split("\n").forEach(pr => grepForEmulatorProcesses.push(pr));
                    const regExp = /^\d+/;
                    grepForEmulatorProcesses.forEach(processOfEmulator => {
                        if (regExp.test(processOfEmulator)) {
                            const pid = parseInt(regExp.exec(processOfEmulator)[0]);
                            try {
                                utils_1.killPid(pid);
                            }
                            catch (error) {
                                utils_1.logInfo(`Something went wrong trying to kill pid ${pid} that belongs to ${emulator.name} `);
                                utils_1.logInfo(`Please have in mind that this only an info since the pid of process could already be destroied!`);
                            }
                        }
                    });
                }
            };
            killEmulatorProcesses();
            utils_1.logInfo(`Waiting for ${emulator.name} to stop!`);
            const checkIfDeviceIsKilled = token => {
                utils_1.wait(1000);
                return utils_1.executeCommand(AndroidController.LIST_DEVICES_COMMAND).includes(token);
            };
            const startTime = Date.now();
            while (checkIfDeviceIsKilled(emulator.token) && (Date.now() - startTime) <= 10000) {
                utils_1.logWarn(`Retrying kill all processes related to ${emulator.name} `);
                killEmulatorProcesses();
            }
            if (checkIfDeviceIsKilled(emulator.token)) {
                utils_1.logError(`Device: ${emulator.name} is NOT killed!`);
                isAlive = true;
            }
            else {
                utils_1.logInfo(`Device: ${emulator.name} is successfully killed!`);
                isAlive = false;
            }
        }
        if (!isAlive) {
            emulator.status = enums_1.Status.SHUTDOWN;
            emulator.pid = undefined;
        }
        else {
            emulator.status = enums_1.Status.BUSY;
        }
        return emulator;
    }
    static killAll() {
        const script = path_1.resolve(__dirname, "scripts", "killallEmulators.sh");
        if (!utils_1.isWin() && fs_1.existsSync(script)) {
            utils_1.executeCommand(`sh ${script}`);
        }
        utils_1.killProcessByName("qemu-system-i386");
        utils_1.killProcessByName("qemu-system-x86_64");
    }
    static restartDevice(device) {
        return __awaiter(this, void 0, void 0, function* () {
            if (device.type === enums_1.DeviceType.EMULATOR) {
                utils_1.logInfo(`Ensure device: ${device.name} is not booted!`);
                AndroidController.kill(device);
                utils_1.logInfo(`Restarting device ${device.name} `);
                AndroidController.startEmulator(device);
            }
            else {
                utils_1.logError("Not implemented for real device!");
            }
            return device;
        });
    }
    static startAdb() {
        utils_1.logInfo("Start adb");
        utils_1.executeCommand(AndroidController.ADB + " start-server");
    }
    static stopAdb() {
        utils_1.logInfo("Stop adb");
        utils_1.executeCommand(AndroidController.ADB + " kill-server");
    }
    static killAdbProcess() {
        utils_1.killProcessByName("adb.exe");
    }
    static isAppRunning(device, appId) {
        const result = AndroidController.executeAdbShellCommand(device, "ps");
        if (result.includes(appId)) {
            return true;
        }
        else {
            return false;
        }
    }
    static getCurrientFocusedScreen(device) {
        return this.executeAdbCommand(device, " shell dumpsys window windows | grep -E 'mCurrentFocus'", 3000);
    }
    static checkIfEmulatorIsResponding(device) {
        try {
            const androidSettings = "com.android.settings/com.android.settings.Settings";
            AndroidController.executeAdbShellCommand(device, ` am start - n ${androidSettings} `);
            let errorMsg = AndroidController.getCurrientFocusedScreen(device);
            const startTime = Date.now();
            while (Date.now() - startTime <= 3000
                && !errorMsg.toLowerCase()
                    .includes(androidSettings.toLowerCase())) {
                errorMsg = AndroidController.getCurrientFocusedScreen(device);
            }
            if (!errorMsg.toLowerCase()
                .includes(androidSettings.toLowerCase())) {
                utils_1.logWarn("Emulator is not responding!", errorMsg);
                return false;
            }
        }
        catch (error) {
            utils_1.logError('Command timeout recieved', error);
            AndroidController.executeAdbShellCommand(device, " am force-stop  com.android.settings");
            return false;
        }
        AndroidController.executeAdbShellCommand(device, " am force-stop  com.android.settings");
        return true;
    }
    static getCurrentErrorMessage(device) {
        const parts = AndroidController.getCurrientFocusedScreen(device).split(":");
        return parts.length > 1 ? parts[1].trim() : undefined;
    }
    static reinstallApplication(device, appFullName, packageId = undefined) {
        packageId = packageId || AndroidController.getPackageId(appFullName);
        AndroidController.uninstallApp(device, packageId);
        AndroidController.installApp(device, appFullName, packageId);
    }
    static refreshApplication(device, appFullName, packageId = undefined) {
        packageId = packageId || AndroidController.getPackageId(appFullName);
        AndroidController.reinstallApplication(device, appFullName, packageId);
        AndroidController.startApplication(device, packageId);
    }
    static startApplication(device, packageId) {
        const commandToExecute = "monkey -p " + packageId + " 1";
        //const commandToExecute = ` am start - n ${ packageId } `;
        Promise.resolve(AndroidController.executeAdbShellCommand(device, commandToExecute));
    }
    static getInstalledApps(device) {
        const list = AndroidController.executeAdbShellCommand(device, `pm list packages - 3`).split("\n");
        return list;
    }
    static isAppInstalled(device, packageId) {
        let isAppInstalled = AndroidController.getInstalledApps(device).some(pack => pack.includes(packageId));
        return isAppInstalled;
    }
    static installApp(device, testAppName, packageId = undefined) {
        packageId = packageId || AndroidController.getPackageId(testAppName);
        let isAppInstalled = AndroidController.isAppInstalled(device, packageId);
        if (isAppInstalled) {
            utils_1.logInfo("Uninstall a previous version " + packageId + " app.");
            AndroidController.uninstallApp(device, packageId);
        }
        const output = AndroidController.executeAdbCommand(device, ` install - r ${testAppName} `);
        utils_1.logInfo(output);
        isAppInstalled = AndroidController.isAppInstalled(device, packageId);
        if (!isAppInstalled) {
            const errorMsg = `Failed to install ${testAppName} !`;
            utils_1.logError(errorMsg);
            throw new Error(errorMsg);
        }
        return packageId;
    }
    static uninstallApp(device, appId) {
        const isAppInstalled = AndroidController.isAppInstalled(device, appId);
        if (isAppInstalled) {
            AndroidController.stopApplication(device, appId);
            const uninstallResult = AndroidController.executeAdbCommand(device, `uninstall ${appId} `);
            if (uninstallResult.includes("Success")) {
                utils_1.logInfo(appId + " successfully uninstalled.");
            }
            else {
                utils_1.logError("Failed to uninstall " + appId + ". Error: " + uninstallResult);
            }
        }
        else {
            utils_1.logInfo(`Application: ${appId} is not installed!`);
        }
        if (AndroidController.getInstalledApps(device).some(app => app === appId)) {
            utils_1.logError("We couldn't uninstall application!");
        }
    }
    static stopApplication(device, appId) {
        AndroidController.executeAdbShellCommand(device, `am force - stop ${appId} `);
    }
    static executeKeyevent(device, keyevent) {
        if (typeof keyevent === 'string') {
            keyevent = enums_1.AndroidKeyEvent[keyevent];
        }
        AndroidController.executeAdbShellCommand(device, `input keyevent ${keyevent} `);
    }
    static getScreenshot(device, dir, fileName) {
        return __awaiter(this, void 0, void 0, function* () {
            fileName = fileName.endsWith(".pne") ? fileName : `${fileName}.png`;
            const pathToScreenshotPng = `/ sdcard / ${fileName} `;
            AndroidController.executeAdbShellCommand(device, `screencap ${pathToScreenshotPng} `);
            const fullFileName = path_1.resolve(dir, fileName);
            AndroidController.pullFile(device, pathToScreenshotPng, fullFileName);
            return fullFileName;
        });
    }
    static recordVideo(device, dir, fileName, callback) {
        return __awaiter(this, void 0, void 0, function* () {
            const { pathToVideo, devicePath, videoRecoringProcess } = AndroidController.startRecordingVideo(device, dir, fileName);
            new Promise((res, reject) => __awaiter(this, void 0, void 0, function* () {
                callback().then((result) => {
                    videoRecoringProcess.kill("SIGINT");
                    AndroidController.pullFile(device, devicePath, pathToVideo);
                    console.log(result);
                    res(pathToVideo);
                }).catch((error) => {
                    reject(error);
                });
            }));
        });
    }
    static startRecordingVideo(device, dir, fileName) {
        const videoFileName = `${fileName}.mp4`;
        const pathToVideo = path_1.resolve(dir, videoFileName);
        const devicePath = `/ sdcard / ${videoFileName} `;
        const prefix = AndroidController.getTokenPrefix(device);
        const videoRecoringProcess = child_process_1.spawn(AndroidController.ADB, ['-s', `${prefix} ${device.token} `, 'shell', 'screenrecord', `${devicePath} `]);
        if (videoRecoringProcess) {
            AndroidController.runningProcesses.push(videoRecoringProcess.pid);
        }
        return { pathToVideo: pathToVideo, devicePath: devicePath, videoRecoringProcess: videoRecoringProcess };
    }
    static stopRecordingVideo(device, videoRecoringProcess, devicePath, pathToVideo) {
        videoRecoringProcess.kill("SIGINT");
        utils_1.wait(1000);
        AndroidController.pullFile(device, devicePath, pathToVideo);
    }
    static getPackageId(appFullName) {
        return AndroidController.runAaptCommand(appFullName, "package");
    }
    static getLaunchableActivity(appFullName) {
        return AndroidController.runAaptCommand(appFullName, "launchable-activity");
    }
    static pullFile(device, remotePath, destinationFile) {
        const destinationFolder = path_1.dirname(destinationFile);
        // Verify remotePath
        const remoteBasePath = remotePath.substring(0, remotePath.lastIndexOf("/"));
        const sdcardFiles = AndroidController.executeAdbShellCommand(device, "ls -la " + remoteBasePath);
        if (sdcardFiles.includes("No such file or directory")) {
            const error = remoteBasePath + " does not exist.";
            utils_1.logError(error);
            return undefined;
        }
        if (!fs_1.existsSync(destinationFolder)) {
            utils_1.logError(`The folder ${destinationFolder} doesn't exist!`);
            return undefined;
        }
        // Pull files
        const output = AndroidController.executeAdbCommand(device, "pull " + remotePath + " " + destinationFile);
        console.log(output);
        const o = output.toLowerCase();
        if ((o.includes("error")) || (o.includes("failed")) || (o.includes("does not exist"))) {
            const error = "Failed to transfer " + remotePath + " to " + destinationFolder;
            utils_1.logError(error);
            utils_1.logError("Error: " + output);
            return undefined;
        }
        else {
            utils_1.logInfo(remotePath + " transferred to " + destinationFile);
        }
        return destinationFile;
    }
    static pushFile(device, fileName, deviceParh) {
        let output = AndroidController.executeAdbShellCommand(device, "mount -o rw,remount -t rootfs /");
        // Verify remotePath
        const remoteBasePath = deviceParh.substring(0, deviceParh.lastIndexOf("/"));
        const sdcardFiles = AndroidController.executeAdbShellCommand(device, "ls -la " + remoteBasePath);
        if (sdcardFiles.includes("No such file or directory")) {
            const error = remoteBasePath + " does not exist.";
            utils_1.logError(error);
            return undefined;
        }
        // Verify localPath
        fileName = fileName.replace("/", path_1.sep).replace("\\", path_1.sep);
        if (!fs_1.existsSync(fileName)) {
            const error = fileName + " does not exist.";
            console.log(error);
            return undefined;
        }
        // Push files
        output = AndroidController.executeAdbCommand(device, "push " + fileName + " " + deviceParh);
        utils_1.logInfo(output);
        if ((output.toLowerCase().includes("error")) || (output.toLowerCase().includes("failed"))) {
            utils_1.logError("Failed to transfer " + fileName + " to " + deviceParh);
            utils_1.logError("Error: ", output);
            return undefined;
        }
        else {
            console.log(fileName + " transferred to " + deviceParh);
        }
        return fileName;
    }
    static getAaptPath() {
        let aaptPath = "";
        let aaptExecutableName = "aapt";
        if (utils_1.isWin()) {
            aaptExecutableName += ".exe";
        }
        const androidHome = path_1.resolve(AndroidController.ANDROID_HOME, "build-tools");
        const searchedFiles = utils_1.searchFiles(androidHome, aaptExecutableName);
        aaptPath = searchedFiles[searchedFiles.length - 1];
        return aaptPath;
    }
    static runAaptCommand(appFullName, grep) {
        let command = AndroidController.getAaptPath() + " dump badging " + appFullName;
        let result = "";
        try {
            const commandResult = utils_1.executeCommand(command);
            result = (new RegExp(`${grep}` + ":\\s+name='\(\w+\|\.+\)'", `i`)).exec(commandResult)[1];
            result = /(\w+.)+\w/ig.exec((new RegExp(`${grep}` + ":\\s+name='\(\w+\|\.+\)'", `i`)).exec(commandResult)[1])[0];
            result = (new RegExp(`${grep}` + ":\\s+name='\(\(\w+.\)+\)'", `ig`)).exec(commandResult)[1];
        }
        catch (error) {
        }
        return result;
    }
    static startEmulatorProcess(emulator, logPath, options) {
        return __awaiter(this, void 0, void 0, function* () {
            options = options || ["-no-snapshot", "-no-audio", "-wipe-data"];
            if (logPath) {
                options.push(` > ${logPath} 2 >& 1`);
            }
            utils_1.logInfo("Starting emulator with options: ", options);
            const process = child_process_1.spawn(AndroidController.EMULATOR, [" -avd ", emulator.name, "-port ", emulator.token, options], {
                shell: true,
                detached: false
            });
            process.stdout.on("data", (data) => {
                console.log(data.toString());
            });
            process.stdout.on("error", (error) => {
                utils_1.logError(error.toString());
            });
            emulator.pid = process.pid;
            return emulator;
        });
    }
    static waitUntilEmulatorBoot(deviceId, timeOutInMiliseconds) {
        const startTime = Date.now();
        let found = false;
        utils_1.logInfo("Booting emulator ...");
        while ((Date.now() - startTime) <= timeOutInMiliseconds && !found) {
            found = AndroidController.checkIfEmulatorIsRunning(enums_1.DeviceType.EMULATOR + "-" + deviceId);
        }
        if (!found) {
            let error = deviceId + " failed to boot in " + timeOutInMiliseconds + " seconds.";
            utils_1.logError(error, true);
        }
        else {
            utils_1.logInfo("Emilator is booted!");
        }
        return found;
    }
    static checkIfEmulatorIsRunning(token) {
        let isBooted = utils_1.executeCommand(AndroidController.ADB + " -s " + token + " shell getprop sys.boot_completed").trim() === "1";
        if (isBooted) {
            isBooted = utils_1.executeCommand(AndroidController.ADB + " -s " + token + " shell getprop init.svc.bootanim").toLowerCase().trim() === "stopped";
        }
        return isBooted;
    }
    static refreshDeviceStatus(token, verbose = false) {
        return __awaiter(this, void 0, void 0, function* () {
            const emulators = AndroidController.parseRunningDevicesList(verbose);
            const emulator = emulators.filter(e => e.token === token)[0];
            return emulator != null ? emulator.status : enums_1.Status.SHUTDOWN;
        });
    }
    static parseEmulators(runningDevices, emulators = new Map(), verbose = false) {
        return __awaiter(this, void 0, void 0, function* () {
            let availableDevices = false;
            const info = utils_1.executeCommand(AndroidController.LIST_AVDS);
            const infoLines = info.split("\n");
            let emulator = null;
            let status = enums_1.Status.SHUTDOWN;
            // Name: Emulator-Api25-Google
            // Path: /Users/progressuser/.android/avd/Emulator-Api25-Google.avd
            // Target: Google APIs (Google Inc.)
            //         Based on: Android 7.1.1 (Nougat) Tag/ABI: google_apis/x86
            //   Skin: 480x800
            // Sdcard: 12M
            infoLines.forEach(line => {
                if (line.toLowerCase().includes("available android")) {
                    status = enums_1.Status.SHUTDOWN;
                }
                if (line.toLowerCase().includes("following android virtual devices could not be loaded")) {
                    status = enums_1.Status.INVALID;
                }
                if (line.toLowerCase().includes("name")) {
                    const name = line.split("Name: ")[1].trim();
                    emulator = new AndroidDevice(name, undefined, enums_1.DeviceType.EMULATOR, undefined, enums_1.Status.SHUTDOWN);
                }
                if (line.includes("Tag/ABI:")) {
                    //const apiLevel = /\d+((.|,)\d+)?/gi.exec(line.split("Tag/ABI:")[0].trim());
                    const apiLevel = line.substring(line.lastIndexOf("on:") + 3, line.lastIndexOf("Tag/ABI:")).replace(/android|api/ig, "").replace(/\(\w.+\)/, "").trim();
                    emulator.apiLevel = apiLevel;
                }
                if (emulator && emulator.name && emulator.apiLevel) {
                    if (!emulators.has(emulator.name)) {
                        emulators.set(emulator.name, new Array());
                        emulators.get(emulator.name).push(emulator);
                    }
                }
            });
            const busyTokens = new Array();
            for (let index = 0; index < runningDevices.length; index++) {
                const emu = runningDevices[index];
                if (emu.type === enums_1.DeviceType.EMULATOR) {
                    try {
                        const avdInfo = yield AndroidController.sendTelnetCommand(emu.token, "avd name");
                        emulators.forEach((v, k, m) => {
                            if (avdInfo.includes(k)) {
                                v[0].status = enums_1.Status.BOOTED;
                                v[0].token = emu.token;
                                busyTokens.push(emu.token);
                                AndroidController.setEmulatorConfig(v[0]);
                            }
                        });
                    }
                    catch (error) {
                        utils_1.logError(error);
                    }
                }
            }
            if (busyTokens.length === 0) {
                busyTokens.push(5544);
            }
            emulators.forEach((devices, key, map) => {
                devices.forEach(device => {
                    if (!device.token) {
                        const lastToken = Math.max(...busyTokens);
                        const token = lastToken % 2 === 0 ? lastToken + 2 : lastToken + 1;
                        device.token = token.toString();
                        busyTokens.push(token);
                    }
                });
            });
            if (verbose) {
                utils_1.logInfo("Avds list info: ", info);
                utils_1.logInfo("Parsed emulators: ", emulators);
            }
            return emulators;
        });
    }
    /**
 * Send an arbitrary Telnet command to the device under test.
 *
 * @param {string} command - The command to be sent.
 *
 * @return {string} The actual output of the given command.
 */
    static sendTelnetCommand(port, command) {
        return __awaiter(this, void 0, void 0, function* () {
            //console.debug(`Sending telnet command device: ${command} to localhost:${port}`);
            return yield new Promise((resolve, reject) => {
                let conn = net.createConnection(port, 'localhost'), connected = false, readyRegex = /^OK$/m, dataStream = "", res = null;
                conn.on('connect', () => {
                    //console.debug("Socket connection to device created");
                });
                conn.setTimeout(60000, () => {
                });
                conn.on('data', (data) => {
                    let recievedData = data.toString('utf8');
                    if (!connected) {
                        if (readyRegex.test(recievedData)) {
                            connected = true;
                            //console.debug("Socket connection to device ready");
                            conn.write(`${command}\n`);
                        }
                    }
                    else {
                        dataStream += data;
                        if (readyRegex.test(recievedData)) {
                            res = dataStream.replace(readyRegex, "").trim();
                            const resArray = res.trim().split('\n');
                            res = resArray[resArray.length - 1];
                            //console.debug(`Telnet command got response: ${res}`);
                            conn.write("quit\n");
                        }
                    }
                });
                conn.on('error', (err) => {
                    utils_1.logError(`Telnet command error: ${err.message}`);
                    AndroidController.kill({ token: port, type: enums_1.DeviceType.EMULATOR });
                    reject(err);
                });
                conn.on('close', () => {
                    if (res === null) {
                        reject(new Error("Never got a response from command"));
                    }
                    else {
                        resolve(res);
                    }
                });
            });
        });
    }
    ;
    static parseRunningDevicesList(verbose) {
        // examples
        // List of devices attached
        // ce0217125d20e41304     unauthorized usb:337641472X
        // emulator-5566          device product:sdk_phone_x86 model:Android_SDK_built_for_x86 device:generic_x86
        // ce0217125d20e41304     device usb:337641472X product:dreamltexx model:SM_G950F device:dreamlte
        // emulator-5566          device product:sdk_phone_x86 model:Android_SDK_built_for_x86 device:generic_x86
        const runningDevices = utils_1.executeCommand(AndroidController.LIST_DEVICES_COMMAND)
            .replace("List of devices attached", "")
            .trim()
            .split("\n");
        const devices = new Array();
        runningDevices.forEach(line => {
            const parseEmulatorToken = line => {
                return line.split("   ")[0].replace(/\D+/ig, '');
            };
            if (line.trim().includes("device")) {
                if (line.includes(enums_1.DeviceType.EMULATOR.toString().toLowerCase())) {
                    const token = parseEmulatorToken(line);
                    devices.push(new AndroidDevice(undefined, undefined, enums_1.DeviceType.EMULATOR, token, enums_1.Status.BOOTED));
                }
                if (line.includes(enums_1.Status.OFFLINE.toString().toLowerCase())) {
                    const token = parseEmulatorToken(line);
                    devices.push(new AndroidDevice(undefined, undefined, enums_1.DeviceType.EMULATOR, token, enums_1.Status.OFFLINE));
                }
                if (line.includes("usb")) {
                    const token = line.split("   ")[0].trim();
                    const status = enums_1.Status.BOOTED;
                    const name = line.split("model:")[1].trim().split(" ")[0].trim();
                    const apiLevel = utils_1.executeCommand(`${AndroidController.ADB} -s ${token} shell getprop ro.build.version.release`).trim();
                    devices.push(new AndroidDevice(name, apiLevel, enums_1.DeviceType.DEVICE, token, status));
                }
                if (line.includes("unauthorized")) {
                    const status = enums_1.Status.UNAUTORIZED;
                    devices.push(new AndroidDevice(enums_1.Status.UNAUTORIZED, enums_1.Status.UNAUTORIZED, enums_1.DeviceType.DEVICE, "", enums_1.Status.UNAUTORIZED));
                }
            }
        });
        if (verbose) {
            utils_1.logInfo("Running devices: ", runningDevices);
        }
        return devices;
    }
    static parseRealDevices(runningDevices, devices = new Map()) {
        runningDevices.forEach(d => {
            if (d.type === enums_1.DeviceType.DEVICE) {
                devices.set(d.name, new Array());
                devices.get(d.name).push(d);
            }
        });
    }
    static emulatorId(platformVersion) {
        return AndroidController._emulatorIds.get(platformVersion.toString());
    }
    static clearLog(device) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.executeAdbCommand(device, " logcat -c", 5000);
        });
    }
    // public static async getDeviceLog(device: IDevice, shouldCleanLog: boolean) {
    //     await this.executeAdbCommand(device, " logcat ");
    //     if (shouldCleanLog) {
    //         await this.executeAdbCommand(device, " logcat -c");
    //     }
    // }
    static checkAndroid() {
        let avdMangerExt = "";
        let emulatorExt = "";
        if (utils_1.isWin()) {
            avdMangerExt = ".bat";
            emulatorExt = ".exe";
        }
        if (!fs_1.existsSync(AndroidController.AVD_MANAGER + avdMangerExt)) {
            AndroidController.LIST_AVDS = "android list avds ";
        }
        if (!fs_1.existsSync(AndroidController.EMULATOR + emulatorExt)) {
            AndroidController.EMULATOR = "emulator ";
        }
    }
    static executeAdbCommand(device, command, timeout = 720000) {
        const prefix = AndroidController.getTokenPrefix(device);
        const commandToExecute = `${AndroidController.ADB} -s ${prefix}${device.token} ${command}`;
        const result = utils_1.executeCommand(commandToExecute, process.cwd(), timeout);
        return result;
    }
    static executeAdbShellCommand(device, command) {
        const commandToExecute = `shell ${command}`;
        const result = AndroidController.executeAdbCommand(device, commandToExecute);
        return result;
    }
    static getTokenPrefix(device) {
        const result = device.type === enums_1.DeviceType.EMULATOR && !device.token.startsWith("emulator") ? "emulator-" : "";
        return result;
    }
    static getAlwaysFinishActivitiesGlobalSettingValue(device, value) {
        const commandToExecute = `settings get global always_finish_activities`;
        const resultAsString = AndroidController.executeAdbShellCommand(device, commandToExecute).trim();
        const matchResult = /^\d/igm.exec(resultAsString);
        const result = (matchResult != null && matchResult.length > 0) ? matchResult[0] == value : false;
        if (!result) {
            utils_1.logError(resultAsString);
        }
        return result;
    }
    static setDontKeepActivities(value, device) {
        const status = value ? 1 : 0;
        const commandToExecute = `settings put global always_finish_activities ${status}`;
        AndroidController.executeAdbShellCommand(device, commandToExecute);
        if (!AndroidController.getAlwaysFinishActivitiesGlobalSettingValue(device, status)) {
            throw new Error(`Failed to set "Don't keep activities" to ${value}!`);
        }
    }
}
AndroidController.DEFAULT_BOOT_TIME = 180000;
AndroidController.ANDROID_HOME = process.env["ANDROID_HOME"] || "";
AndroidController.EMULATOR = path_1.resolve(AndroidController.ANDROID_HOME, "emulator", "emulator");
AndroidController.ADB = path_1.resolve(AndroidController.ANDROID_HOME, "platform-tools", "adb");
AndroidController.LIST_DEVICES_COMMAND = AndroidController.ADB + " devices -l";
AndroidController.AVD_MANAGER = path_1.resolve(AndroidController.ANDROID_HOME, "tools", "bin", "avdmanager");
AndroidController.LIST_AVDS = AndroidController.AVD_MANAGER + " list avd";
AndroidController._emulatorIds = new Map();
AndroidController.runningProcesses = new Array();
AndroidController.sendKeyCommand = (token, key) => {
    return `${AndroidController.ADB} -s ${token} shell input keyevent ${key}`;
};
exports.AndroidController = AndroidController;
class AndroidDevice extends device_1.Device {
    constructor(name, apiLevel, type, token, status, pid) {
        super(name, apiLevel, type, enums_1.Platform.ANDROID, token, status, pid);
    }
}
exports.AndroidDevice = AndroidDevice;
//# sourceMappingURL=android-controller.js.map