"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 glob_1 = require("glob");
const path_1 = require("path");
const fs_1 = require("fs");
const enums_1 = require("./enums");
const net = require("net");
const utils_1 = require("./utils");
const device_controller_1 = require("./device-controller");
const OFFSET_DI_PIXELS = 16;
class AndroidController {
    static getAndroidHome() {
        if (process.env["ANDROID_HOME"]) {
            return process.env["ANDROID_HOME"];
        }
        let androidHome = `${process.env["HOME"]}/Library/Android/sdk`;
        if (fs_1.existsSync(androidHome)) {
            return androidHome;
        }
        androidHome = `/usr/local/share/android-sdk`;
        if (fs_1.existsSync(androidHome)) {
            return androidHome;
        }
        androidHome = `${process.env["HOME"]}/Android/Sdk`;
        if (fs_1.existsSync(androidHome)) {
            return androidHome;
        }
    }
    static getAllDevices(verbose = false) {
        return __awaiter(this, void 0, void 0, function* () {
            // this should be always first.
            const runningDevices = AndroidController.parseRunningDevicesList(verbose);
            const devices = AndroidController.emulators;
            yield AndroidController.parseEmulators(runningDevices, devices);
            yield AndroidController.parseRealDevices(runningDevices, devices);
            return devices;
        });
    }
    static getPhysicalDensity(device) {
        return parseInt(AndroidController.executeAdbShellCommand(device, "wm density", 2000).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 cleanLockFiles(emulator) {
        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 => AndroidController.lockFilesPredicate(f))
            .forEach(f => {
            try {
                const path = path_1.resolve(avd, f);
                if (fs_1.existsSync(path)) {
                    fs_1.unlinkSync(path);
                }
            }
            catch (error) {
                utils_1.logWarn(`Failed to delete lock file for ${avd}!`);
            }
        });
    }
    static getSecurity(emulator) {
        return __awaiter(this, void 0, void 0, function* () {
            const receivedData = yield AndroidController.sendEmulatorConsoleCommands(emulator, {
                port: emulator.token,
                commands: undefined,
                shouldFailOnError: true,
                matchExit: /\w+/ig,
                retries: 10,
                getAllData: true
            });
            const emulator_console_auth_token_line = receivedData && receivedData.split("\n")
                .filter(l => l.includes("emulator_console_auth_token"))[0];
            const line = emulator_console_auth_token_line && emulator_console_auth_token_line.trim().replace('\'', "").replace('\'', "");
            let security;
            if (fs_1.existsSync(line)) {
                security = fs_1.readFileSync(line, "UTF8");
            }
            security = security || fs_1.readFileSync(`${process.env["HOME"]}/.emulator_console_auth_token`, "UTF8").toString().trim();
            return security;
        });
    }
    static startEmulator(emulator, startEmulatorOptions) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!startEmulatorOptions || !startEmulatorOptions.options) {
                startEmulatorOptions = new StartEmulatorOptions();
                startEmulatorOptions.options = Array.from(AndroidController.NO_WIPE_DATA_NO_SNAPSHOT_SAVE);
                startEmulatorOptions.retries = 3;
            }
            if (!emulator.token) {
                emulator.token = emulator.apiLevel ? (AndroidController.emulatorId(emulator.apiLevel) || "5554") : "5554";
            }
            // trying to find device since name and apiLevel are mandatory;
            if (!emulator.name || !emulator.apiLevel) {
                emulator.type = enums_1.DeviceType.EMULATOR;
                emulator.platform = enums_1.Platform.ANDROID;
                let searchQuery = {};
                Object.assign(searchQuery, emulator);
                delete searchQuery.info;
                delete searchQuery.startedAt;
                delete searchQuery.busySince;
                delete searchQuery.parentProcessPid;
                delete searchQuery.process;
                delete searchQuery.pid;
                delete searchQuery.config;
                delete searchQuery.token;
                const devices = (yield device_controller_1.DeviceController.getDevices(searchQuery));
                if (devices && devices.length > 0) {
                    utils_1.copyIDeviceQuery(devices[0], emulator);
                }
                else {
                    utils_1.logError("Requested device is missing", emulator);
                }
            }
            if (!emulator.name) {
                utils_1.logError("Please provide emulator name");
                return {};
            }
            // kill emulator instance in case some process are still alive
            yield AndroidController.kill(emulator, false, startEmulatorOptions.retries, startEmulatorOptions.shouldHardResetDevices);
            // clean lock files
            AndroidController.cleanLockFiles(emulator);
            emulator.type = enums_1.DeviceType.EMULATOR;
            emulator = yield AndroidController.startEmulatorProcess(emulator, startEmulatorOptions.logPath, startEmulatorOptions.options);
            let result = ((yield AndroidController.waitUntilEmulatorBoot(emulator, startEmulatorOptions.defaultBootTime || AndroidController.DEFAULT_BOOT_TIME)) === true) ? enums_1.Status.BOOTED : enums_1.Status.SHUTDOWN;
            let security;
            let snapshot = AndroidController.DEFAULT_SNAPSHOT_NAME;
            // Check for snapshot. In case snapshot option is provided but doesn't exists, it will be created.  
            if (startEmulatorOptions.options && startEmulatorOptions.options.indexOf("-snapshot") >= 0) {
                const snapshotName = startEmulatorOptions.options[startEmulatorOptions.options.indexOf("-snapshot") + 1];
                security = yield AndroidController.getSecurity(emulator);
                const availableSnapshots = yield AndroidController.sendEmulatorConsoleCommands(emulator, {
                    port: emulator.token,
                    commands: [`auth ${security}`, "avd snapshot list"],
                    shouldFailOnError: true,
                    matchExit: /\w+/ig,
                    retries: 10,
                    getAllData: true
                });
                if (!availableSnapshots || !availableSnapshots.includes(snapshotName)) {
                    utils_1.logWarn(`This snapshot is not available. We are going to recreate a clean one with the same name`);
                    snapshot = snapshotName;
                    yield AndroidController.kill(emulator);
                    AndroidController.cleanLockFiles(emulator);
                    emulator = yield AndroidController.startEmulatorProcess(emulator, startEmulatorOptions.logPath, AndroidController.NO_SNAPSHOT_LOAD_NO_SNAPSHOT_SAVE);
                    result = (yield AndroidController.waitUntilEmulatorBoot(emulator, parseInt(process.env.BOOT_ANDROID_EMULATOR_MAX_TIME) || AndroidController.DEFAULT_BOOT_TIME)) === true ? enums_1.Status.BOOTED : enums_1.Status.SHUTDOWN;
                    startEmulatorOptions.options.push("-wipe-data");
                }
            }
            if (result === enums_1.Status.BOOTED
                && startEmulatorOptions.options.indexOf("-wipe-data") >= 0
                && startEmulatorOptions.options.indexOf("-snapshot") >= 0) {
                console.log(`Reset emulator and save snapshot ${snapshot}!`);
                security = security || (yield AndroidController.getSecurity(emulator));
                yield AndroidController.sendEmulatorConsoleCommands(emulator, {
                    port: emulator.token,
                    commands: [`auth ${security}`, `avd snapshot save ${snapshot}`],
                    shouldFailOnError: true,
                    matchExit: /^\s*$/,
                    retries: 10,
                    getAllData: false
                });
                yield AndroidController.kill(emulator);
                AndroidController.NO_WIPE_DATA_NO_SNAPSHOT_SAVE[AndroidController.NO_WIPE_DATA_NO_SNAPSHOT_SAVE.indexOf("-snapshot") + 1] = snapshot;
                startEmulatorOptions.options = Array.from(AndroidController.NO_WIPE_DATA_NO_SNAPSHOT_SAVE);
                yield AndroidController.startEmulator(emulator, startEmulatorOptions);
            }
            if (!result || result !== enums_1.Status.BOOTED) {
                yield AndroidController.kill(emulator);
                utils_1.logWarn("Trying to boot emulator again!");
                startEmulatorOptions.retries--;
                const newOptions = Array.from(AndroidController.NO_SNAPSHOT_LOAD_NO_SNAPSHOT_SAVE);
                newOptions.push("-wipe-data");
                startEmulatorOptions.options = newOptions;
                emulator = yield AndroidController.startEmulator(emulator, startEmulatorOptions);
            }
            if (result === enums_1.Status.BOOTED) {
                emulator.status = enums_1.Status.BOOTED;
                emulator.startedAt = Date.now();
                if (!emulator.apiLevel || !emulator.releaseVersion) {
                    emulator.releaseVersion = AndroidController.executeAdbShellCommand(emulator, ` getprop ro.build.version.release`).trim();
                    emulator.apiLevel = AndroidController.executeAdbShellCommand(emulator, ` getprop ro.build.version.sdk`).trim();
                }
            }
            if (!emulator.config || !emulator.config.offsetPixels) {
                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) { }
            let result;
            try {
                AndroidController.executeAdbCommand(emulator, 'reboot bootloader');
                result = yield AndroidController.waitUntilEmulatorBoot(emulator, AndroidController.DEFAULT_BOOT_TIME / 3);
            }
            catch (_b) { }
            if (!result) {
                emulator = yield AndroidController.kill(emulator);
                const options = {
                    options: ["-wipe-data", "-no-snapshot-load", "-no-boot-anim", "-no-audio"],
                    retries: 3,
                };
                emulator = yield AndroidController.startEmulator(emulator, options);
            }
            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, verbose = true, retries = 3, hardKillByName = true) {
        return __awaiter(this, void 0, void 0, function* () {
            let isAlive = true;
            if (emulator.type !== enums_1.DeviceType.DEVICE) {
                if (emulator.token) {
                    try {
                        AndroidController.executeAdbCommand(emulator, " emu kill");
                    }
                    catch (error) { }
                }
                try {
                    if (!utils_1.isWin()) {
                        emulator.pid && utils_1.killAllProcessAndRelatedCommand(emulator.pid);
                        if (hardKillByName) {
                            utils_1.killAllProcessAndRelatedCommand(emulator.name);
                        }
                    }
                    emulator.pid && utils_1.killPid(+emulator.pid);
                    utils_1.killProcessByName("emulator64-crash-service");
                }
                catch (error) { }
                if (verbose) {
                    utils_1.logInfo(`Waiting for ${emulator.name || emulator.token} to stop!`);
                }
                const checkIfDeviceIsKilled = token => {
                    return utils_1.executeCommand(AndroidController.LIST_DEVICES_COMMAND).includes(token);
                };
                const startTime = Date.now();
                while (checkIfDeviceIsKilled(emulator.token) && (Date.now() - startTime) <= 10000 && retries >= 0) { }
                if (checkIfDeviceIsKilled(emulator.token)) {
                    utils_1.logError(`Device: ${emulator.name} is NOT killed!`);
                    retries--;
                    isAlive = true;
                    if (verbose) {
                        utils_1.logWarn(`Retrying kill all processes related to ${emulator.name}`);
                    }
                    yield AndroidController.kill(emulator, verbose, retries);
                }
                else {
                    if (verbose) {
                        utils_1.logInfo(`Device: ${emulator.name || emulator.token} is successfully killed!`);
                    }
                    isAlive = false;
                }
            }
            if (!isAlive) {
                emulator.status = enums_1.Status.SHUTDOWN;
                emulator.pid = undefined;
            }
            else {
                emulator.status = enums_1.Status.INVALID;
            }
            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}`);
        }
        AndroidController.stopAdb();
        utils_1.killProcessByName("qemu-system-i386");
        utils_1.killProcessByName("qemu-system-x86_64");
        AndroidController.startAdb();
    }
    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!`);
                yield AndroidController.kill(device);
                utils_1.logInfo(`Restarting device ${device.name}`);
                const options = {
                    options: AndroidController.NO_SNAPSHOT_LOAD_NO_SNAPSHOT_SAVE,
                    retries: 3,
                };
                yield AndroidController.startEmulator(device, options);
            }
            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 getCurrentFocusedScreen(device, commandTimeout = 1000) {
        return this.executeAdbCommand(device, " shell dumpsys window windows | grep -E 'mCurrentFocus'", commandTimeout);
    }
    static checkApiLevelIsLessThan(device, apiLevel) {
        let dApiLevel = device.apiLevel;
        if (!device.releaseVersion) {
            dApiLevel = /\d.\d/ig.exec(device.apiLevel)[0];
        }
        return isNaN(+dApiLevel) || +dApiLevel < apiLevel;
    }
    static checkIfEmulatorIsResponding(device) {
        try {
            const androidSettings = "com.android.settings/com.android.settings.Settings";
            AndroidController.executeAdbShellCommand(device, ` am start -n ${androidSettings}`, 15000);
            let errorMsg = AndroidController.getCurrentFocusedScreen(device);
            const startTime = Date.now();
            while (Date.now() - startTime <= 5000
                && !errorMsg.toLowerCase()
                    .includes(androidSettings.toLowerCase())) {
                errorMsg = AndroidController.getCurrentFocusedScreen(device);
            }
            if (!errorMsg.toLowerCase()
                .includes(androidSettings.toLowerCase())) {
                utils_1.logWarn("Emulator is not responding!", errorMsg);
                if (AndroidController.checkApiLevelIsLessThan(device, 19)) {
                    console.log(`Skip check if device is responding since api level is lower than 19/ 5.0`);
                    return true;
                }
                return false;
            }
        }
        catch (error) {
            utils_1.logError('Command timeout received', error);
            AndroidController.executeAdbShellCommand(device, " am force-stop com.android.settings", 15000);
            return false;
        }
        try {
            AndroidController.executeAdbShellCommand(device, " am force-stop com.android.settings", 15000);
        }
        catch (error) { }
        return true;
    }
    static getCurrentErrorMessage(device) {
        const parts = AndroidController.getCurrentFocusedScreen(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(".png") ? 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 parsePlatforms() {
        const platformsFolder = path_1.resolve(AndroidController.ANDROID_HOME, "system-images");
        const platforms = new Map();
        const files = glob_1.glob.sync(`${platformsFolder}/*`);
        for (let index = 0; index < files.length; index++) {
            const f = files[index];
            const versions = {};
            const file = glob_1.glob.sync(`${f}/*/*/build.prop`)[0];
            const fileContent = fs_1.readFileSync(file, "UTF8");
            const fileData = fileContent.split("\n");
            for (let i = 0; i < fileData.length; i++) {
                const line = fileData[i];
                if (line) {
                    if (line.includes("ro.build.version.sdk") || line.includes("AndroidVersion.ApiLevel")) {
                        // versions.sdk = /\d+(\.\d)?(\.\d)?/.exec(line)[0];
                        versions.sdk = line.split("=")[1].trim();
                    }
                    if (line.includes("ro.build.version.release") || line.includes("Platform.Version")) {
                        // versions.releaseVersion = /\d+(\.\d)?(\.\d)?/.exec(line)[0];
                        versions.releaseVersion = line.split("=")[1].trim();
                    }
                    if (versions.sdk && versions.releaseVersion) {
                        i = fileData.length;
                    }
                }
            }
            const platformName = path_1.basename(f);
            if (!platforms.has(platformName) && versions.sdk && versions.releaseVersion) {
                platforms.set(platformName, versions);
            }
        }
        return platforms;
    }
    static parseEmulatorsAvds() {
        const platforms = AndroidController.parsePlatforms();
        const avdsDirectory = process.env["AVDS_STORAGE"] || path_1.join(process.env["HOME"], "/.android/avd");
        const emulators = new Array();
        fs_1.readdirSync(avdsDirectory)
            .filter(f => f.endsWith(".ini"))
            .forEach(f => {
            fs_1.readFileSync(path_1.resolve(avdsDirectory, f), "UTF8")
                .split("\n")
                .forEach(line => {
                if (line.includes("target=")) {
                    const buildPlatform = line.split("=")[1].trim();
                    const versions = platforms.get(buildPlatform);
                    const name = f.replace(".ini", "");
                    const emu = {
                        name: name,
                        apiLevel: versions.sdk,
                        releaseVersion: versions.releaseVersion,
                        platform: enums_1.Platform.ANDROID,
                        type: enums_1.DeviceType.EMULATOR,
                        status: enums_1.Status.SHUTDOWN
                    };
                    emulators.push(emu);
                }
            });
        });
        const files = glob_1.glob.sync(`${avdsDirectory}/*.avd/config.ini`);
        for (let index = 0; index < files.length; index++) {
            const file = files[index];
            const fileContent = fs_1.readFileSync(path_1.resolve(file), "UTF8");
            const fileData = fileContent.split("\n");
            const config = {};
            for (let i = 0; i < fileData.length; i++) {
                const line = fileData[i];
                if (line) {
                    if (line && line.includes("hw.lcd.density=")) {
                        config.density = +(line.split("=")[1]) * 0.01;
                        config.offsetPixels = AndroidController.calculateScreenOffset(config.density);
                    }
                    if (line && line.includes("skin.name=")) {
                        const [width, height] = line.split("=")[1].split("x");
                        config.screen = { width: width, height: height };
                    }
                    if (config.offsetPixels && config.density && config.screen) {
                        i = fileData.length;
                        const emu = emulators.filter(e => e.name === path_1.basename(path_1.dirname(file)).replace(".avd", ""))[0];
                        emu.config = config;
                    }
                }
            }
        }
        return emulators;
    }
    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-audio", "-no-snapshot-save", "-no-boot-anim"];
            if (logPath) {
                options.push(` > ${logPath} 2 >& 1`);
            }
            utils_1.logInfo(`Starting emulator with options: -avd ${emulator.name} -port ${emulator.token}`, 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", (data) => {
                console.log(data.toString());
            });
            emulator.pid = process.pid;
            emulator.process = process;
            return emulator;
        });
    }
    static waitUntilEmulatorBoot(device, timeOutInMilliseconds) {
        return __awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => {
                const startTime = Date.now();
                let found = false;
                utils_1.logInfo("Booting emulator ...");
                const abortWatch = setTimeout(function () {
                    clearTimeout(abortWatch);
                    utils_1.logError("Received timeout: ", timeOutInMilliseconds);
                    return resolve(false);
                }, timeOutInMilliseconds);
                while ((Date.now() - startTime) <= timeOutInMilliseconds && !found) {
                    found = AndroidController.checkIfEmulatorIsRunning(enums_1.DeviceType.EMULATOR + "-" + device.token);
                }
                found = AndroidController.checkIfEmulatorIsResponding(device);
                if (!found) {
                    utils_1.logError(`${device.token} failed to boot in ${timeOutInMilliseconds} milliseconds`, true);
                }
                else {
                    utils_1.logInfo("Emulator is booted!");
                }
                clearTimeout(abortWatch);
                return resolve(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 sendEmulatorConsoleCommands(emulator, options) {
        return __awaiter(this, void 0, void 0, function* () {
            const isValidResult = (avdInfo, matchExit) => {
                let result = false;
                if (matchExit) {
                    result = matchExit.test(avdInfo);
                    if (!result) {
                        console.log(result);
                    }
                    matchExit.lastIndex = 0;
                    return result;
                }
                return !avdInfo && avdInfo !== null;
            };
            let copiedOptions = {};
            Object.assign(copiedOptions, options);
            copiedOptions.commands = options.commands && Array.from(options.commands);
            let avdInfo = yield AndroidController.sendTelnetCommand(copiedOptions);
            let isValid = false;
            if (!isValidResult(avdInfo, copiedOptions.matchExit)) {
                options.retries--;
                if (options.retries === 5) {
                    yield AndroidController.reboot(emulator);
                }
                avdInfo = yield AndroidController.sendEmulatorConsoleCommands(emulator, options);
            }
            else {
                isValid = true;
            }
            if (!isValid && !isValidResult(avdInfo, copiedOptions.matchExit)) {
                utils_1.logError(`There is a problem with emulators console and probably
            we could not establish connection with telnet localhost ${emulator.token}`);
            }
            return avdInfo;
        });
    }
    static parseEmulators(runningDevices, emulators = new Array(), verbose = false) {
        return __awaiter(this, void 0, void 0, function* () {
            let emulatorsAvds;
            if (AndroidController.emulators.length > 0) {
                emulators = AndroidController.emulators;
            }
            else {
                const emulatorsAvds = AndroidController.parseEmulatorsAvds();
                emulators.push(...emulatorsAvds);
            }
            const busyTokens = new Array();
            emulators.forEach(d => d.status = enums_1.Status.SHUTDOWN);
            for (let index = 0; index < runningDevices.length; index++) {
                const emu = runningDevices[index];
                if (emu.type === enums_1.DeviceType.EMULATOR) {
                    try {
                        let avdInfo = yield AndroidController.sendEmulatorConsoleCommands(emu, {
                            port: emu.token,
                            commands: ["avd name"],
                            shouldFailOnError: true,
                            matchExit: /\w+/ig,
                            retries: 10,
                            getAllData: false
                        });
                        avdInfo = avdInfo && avdInfo.trim();
                        if (!avdInfo || avdInfo === null) {
                            utils_1.logError("Something went wrong. We couldn't parse the emulator name!");
                        }
                        const emulatorsToFilter = emulatorsAvds || AndroidController.emulators;
                        let rEmu = emulatorsToFilter.filter(avd => avd.name === avdInfo)[0];
                        if (!rEmu) {
                            utils_1.logError(`Something went wrong. We couldn't parse the running emulator ${emu.token}/ ${avdInfo} !`, emu.token);
                        }
                        rEmu.status = enums_1.Status.BOOTED;
                        rEmu.token = emu.token;
                        rEmu.releaseVersion = emu.releaseVersion;
                        rEmu.apiLevel = emu.apiLevel;
                        busyTokens.push(emu.token);
                    }
                    catch (error) {
                        utils_1.logError(error);
                    }
                }
            }
            if (busyTokens.length === 0) {
                busyTokens.push(5544);
            }
            emulators.forEach((device) => {
                if (!device.token) {
                    const token = AndroidController.getTokenForEmulator(busyTokens);
                    device.token = token.toString();
                    busyTokens.push(token);
                }
            });
            if (AndroidController.emulators.length === 0) {
                AndroidController.emulators = emulators;
            }
            return emulators;
        });
    }
    static getTokenForEmulator(busyTokens) {
        const lastToken = Math.max(...busyTokens);
        const token = lastToken % 2 === 0 ? lastToken + 2 : lastToken + 1;
        return token;
    }
    /**
 * 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(options) {
        return __awaiter(this, void 0, void 0, function* () {
            //console.debug(`Sending telnet command device: ${command} to localhost:${port}`);
            var index = 0;
            return yield new Promise((resolve, reject) => {
                let conn = net.createConnection(+options.port, 'localhost'), readyRegex = /^OK$/m, allData = "", res = null;
                conn.on('connect', () => {
                });
                conn.setTimeout(60000, () => {
                });
                conn.on('data', (data) => {
                    let receivedData = data.toString('utf8');
                    allData += receivedData;
                    if (!options.commands) {
                        res = receivedData;
                        conn.write("quit\n");
                    }
                    if (readyRegex.test(receivedData) && options.commands && options.commands.length > 0) {
                        const el = options.commands[index];
                        conn.write(`${el}\n`);
                    }
                    if (options.commands && options.commands.length === 0 && readyRegex.test(receivedData)) {
                        res = receivedData.replace(readyRegex, "").trim();
                        if (options.getAllData) {
                            res = allData;
                        }
                        conn.write("quit\n");
                    }
                    if (options.commands && readyRegex.test(receivedData)) {
                        options.commands.shift();
                    }
                });
                conn.on('error', (err) => {
                    resolve(res);
                });
                conn.on('close', () => {
                    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")) {
                let token = line.split("   ")[0].trim();
                const releaseVersion = utils_1.executeCommand(`${AndroidController.ADB} -s ${token} shell getprop ro.build.version.release`).trim();
                const apiLevel = utils_1.executeCommand(`${AndroidController.ADB} -s ${token} shell getprop ro.build.version.sdk`).trim();
                if (line.startsWith(enums_1.DeviceType.EMULATOR.toString().toLowerCase())) {
                    token = parseEmulatorToken(line);
                    devices.push({
                        token: token,
                        apiLevel: apiLevel,
                        releaseVersion: releaseVersion,
                        status: enums_1.Status.BOOTED,
                        type: enums_1.DeviceType.EMULATOR,
                        platform: enums_1.Platform.ANDROID
                    });
                }
                if (line.includes(enums_1.Status.OFFLINE.toString().toLowerCase())) {
                    token = parseEmulatorToken(line);
                    devices.push({
                        token: token,
                        apiLevel: apiLevel,
                        releaseVersion: releaseVersion,
                        status: enums_1.Status.OFFLINE,
                        type: enums_1.DeviceType.EMULATOR,
                    });
                }
                if (line.includes("usb") || line.includes("vbox86p")) {
                    const status = enums_1.Status.BOOTED;
                    const name = line.split("model:")[1].trim().split(" ")[0].trim();
                    devices.push({
                        name: name,
                        token: token,
                        apiLevel: apiLevel,
                        releaseVersion: releaseVersion,
                        status: status,
                        type: enums_1.DeviceType.DEVICE
                    });
                }
                if (line.includes("unauthorized")) {
                    devices.push({
                        status: enums_1.Status.UNAUTORIZED,
                        type: enums_1.DeviceType.DEVICE
                    });
                }
            }
        });
        if (verbose) {
            utils_1.logInfo("Running devices: ", runningDevices);
        }
        return devices;
    }
    static parseRealDevices(runningDevices, devices = new Array()) {
        runningDevices.forEach(d => {
            if (d.type === enums_1.DeviceType.DEVICE) {
                devices.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);
        });
    }
    static executeAdbCommand(device, command, timeout = AndroidController.DEFAULT_BOOT_TIME) {
        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, timeout = AndroidController.DEFAULT_BOOT_TIME) {
        const commandToExecute = `shell ${command}`;
        const result = AndroidController.executeAdbCommand(device, commandToExecute, timeout);
        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.ANDROID_HOME = AndroidController.getAndroidHome();
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._emulatorIds = new Map();
AndroidController.lockFilesPredicate = f => { return f.endsWith(".lock") || f.startsWith("snapshot.lock."); };
AndroidController.emulators = new Array();
AndroidController.DEFAULT_BOOT_TIME = 150000;
AndroidController.DEFAULT_SNAPSHOT_NAME = "clean_boot";
AndroidController.NO_SNAPSHOT_LOAD_NO_SNAPSHOT_SAVE = ["-no-audio", "-no-boot-anim", "-wipe-data", "-no-snapshot-load", "-no-snapshot-save"];
AndroidController.NO_WIPE_DATA_NO_SNAPSHOT_SAVE = ["-snapshot", AndroidController.DEFAULT_SNAPSHOT_NAME, "-no-snapshot-save"];
AndroidController.runningProcesses = new Array();
AndroidController.sendKeyCommand = (token, key) => {
    return `${AndroidController.ADB} -s ${token} shell input keyevent ${key}`;
};
exports.AndroidController = AndroidController;
class StartEmulatorOptions {
    constructor() {
        this.hardKillByName = false;
        this.defaultBootTime = +process.env.BOOT_ANDROID_EMULATOR_MAX_TIME || +AndroidController.DEFAULT_BOOT_TIME;
    }
}
exports.StartEmulatorOptions = StartEmulatorOptions;
//# sourceMappingURL=android-controller.js.map