"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    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) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AndroidApplicationManager = void 0;
const os_1 = require("os");
const application_manager_base_1 = require("../application-manager-base");
const constants_1 = require("../../constants");
const helpers_1 = require("../../helpers");
const decorators_1 = require("../../decorators");
const path_1 = require("path");
const _ = require("lodash");
const constants_2 = require("../../../constants");
class AndroidApplicationManager extends application_manager_base_1.ApplicationManagerBase {
    constructor(adb, identifier, $androidBundleToolService, $fs, $options, $logcatHelper, $androidProcessService, $httpClient, $deviceLogProvider, $errors, $logger, $hooksService) {
        super($logger, $hooksService, $deviceLogProvider);
        this.adb = adb;
        this.identifier = identifier;
        this.$androidBundleToolService = $androidBundleToolService;
        this.$fs = $fs;
        this.$options = $options;
        this.$logcatHelper = $logcatHelper;
        this.$androidProcessService = $androidProcessService;
        this.$httpClient = $httpClient;
        this.$deviceLogProvider = $deviceLogProvider;
        this.$errors = $errors;
        this.PID_CHECK_INTERVAL = 100;
        this.PID_CHECK_TIMEOUT = 10000;
    }
    getInstalledApplications() {
        return __awaiter(this, void 0, void 0, function* () {
            const result = (yield this.adb.executeShellCommand(["pm", "list", "packages"])) || "";
            const regex = /package:(.+)/;
            return result
                .split(os_1.EOL)
                .map((packageString) => {
                const match = packageString.match(regex);
                return match ? match[1] : null;
            })
                .filter((parsedPackage) => parsedPackage !== null);
        });
    }
    installApplication(packageFilePath, appIdentifier, buildData) {
        return __awaiter(this, void 0, void 0, function* () {
            if (appIdentifier) {
                const deviceRootPath = `${constants_1.LiveSyncPaths.ANDROID_TMP_DIR_NAME}/${appIdentifier}`;
                yield this.adb.executeShellCommand(["rm", "-rf", deviceRootPath]);
            }
            const { dir, name, ext } = (0, path_1.parse)(packageFilePath);
            if (ext === constants_2.AAB_EXTENSION_NAME) {
                const apksOutputPath = (0, path_1.join)(dir, name) + constants_2.APKS_EXTENSION_NAME;
                if (!this.hasValidApksFile(packageFilePath, apksOutputPath)) {
                    yield this.$androidBundleToolService.buildApks({
                        aabFilePath: packageFilePath,
                        apksOutputPath,
                        signingData: buildData,
                    });
                }
                yield this.$androidBundleToolService.installApks({
                    apksFilePath: apksOutputPath,
                    deviceId: this.identifier,
                });
            }
            else {
                return this.adb.executeCommand(["install", "-r", `${packageFilePath}`]);
            }
        });
    }
    uninstallApplication(appIdentifier) {
        return this.adb.executeShellCommand(["pm", "uninstall", `${appIdentifier}`], { treatErrorsAsWarnings: true });
    }
    startApplication(appData) {
        return __awaiter(this, void 0, void 0, function* () {
            if (appData.waitForDebugger) {
                yield this.adb.executeShellCommand([
                    `cat /dev/null > ${constants_1.LiveSyncPaths.ANDROID_TMP_DIR_NAME}/${appData.appId}-debugbreak`,
                ]);
            }
            yield this.adb.executeShellCommand([
                `cat /dev/null > ${constants_1.LiveSyncPaths.ANDROID_TMP_DIR_NAME}/${appData.appId}-debugger-started`,
            ]);
            const appIdentifier = appData.appId;
            const pmDumpOutput = yield this.adb.executeShellCommand([
                "pm",
                "dump",
                appIdentifier,
                "|",
                "grep",
                "-A",
                "1",
                "MAIN",
            ]);
            const activityMatch = this.getFullyQualifiedActivityRegex(appIdentifier);
            const match = activityMatch.exec(pmDumpOutput);
            const possibleIdentifier = match && match[0];
            if (possibleIdentifier) {
                yield this.adb.executeShellCommand([
                    "am",
                    "start",
                    "-n",
                    possibleIdentifier,
                ]);
            }
            else {
                this.$logger.trace(`Tried starting activity for: ${appIdentifier}, using activity manager but failed.`);
                yield this.adb.executeShellCommand([
                    "monkey",
                    "-p",
                    appIdentifier,
                    "-c",
                    "android.intent.category.LAUNCHER",
                    "1",
                ]);
            }
            yield this.onAppLaunch(appData);
        });
    }
    onAppLaunch(appData) {
        return __awaiter(this, void 0, void 0, function* () {
            const appIdentifier = appData.appId;
            if (!this.$options.justlaunch && !appData.justLaunch) {
                const deviceIdentifier = this.identifier;
                const processIdentifier = yield this.getAppProcessId(deviceIdentifier, appIdentifier);
                if (processIdentifier) {
                    this.$deviceLogProvider.setApplicationPidForDevice(deviceIdentifier, processIdentifier);
                    this.$deviceLogProvider.setApplicationIdForDevice(deviceIdentifier, appIdentifier);
                    this.$deviceLogProvider.setProjectDirForDevice(deviceIdentifier, appData.projectDir);
                    yield this.$logcatHelper.start({
                        deviceIdentifier: this.identifier,
                        pid: processIdentifier,
                        appId: appIdentifier,
                        onAppRestarted: () => {
                            this.onAppLaunch(appData);
                        },
                    });
                }
                else {
                    yield this.$logcatHelper.dump(this.identifier);
                    this.$errors.fail(`Unable to find running "${appIdentifier}" application on device "${deviceIdentifier}".`);
                }
            }
        });
    }
    getAppProcessId(deviceIdentifier, appIdentifier) {
        return __awaiter(this, void 0, void 0, function* () {
            const appIdCheckStartTime = new Date().getTime();
            let processIdentifier = "";
            let hasTimedOut = false;
            while (!processIdentifier && !hasTimedOut) {
                processIdentifier = yield this.$androidProcessService.getAppProcessId(deviceIdentifier, appIdentifier);
                if (!processIdentifier) {
                    this.$logger.trace(`Wasn't able to get pid of the app. Sleeping for "${this.PID_CHECK_INTERVAL}ms".`);
                    yield (0, helpers_1.sleep)(this.PID_CHECK_INTERVAL);
                    hasTimedOut =
                        new Date().getTime() - appIdCheckStartTime > this.PID_CHECK_TIMEOUT;
                }
            }
            return processIdentifier;
        });
    }
    stopApplication(appData) {
        this.$logcatHelper.stop(this.identifier);
        this.$deviceLogProvider.setApplicationPidForDevice(this.identifier, null);
        this.$deviceLogProvider.setProjectDirForDevice(this.identifier, null);
        return this.adb.executeShellCommand([
            "am",
            "force-stop",
            `${appData.appId}`,
        ]);
    }
    getDebuggableApps() {
        return this.$androidProcessService.getDebuggableApps(this.identifier);
    }
    getDebuggableAppViews(appIdentifiers) {
        return __awaiter(this, void 0, void 0, function* () {
            const mappedAppIdentifierPorts = yield this.$androidProcessService.getMappedAbstractToTcpPorts(this.identifier, appIdentifiers, constants_1.TARGET_FRAMEWORK_IDENTIFIERS.Cordova), applicationViews = {};
            yield Promise.all(_.map(mappedAppIdentifierPorts, (port, appIdentifier) => __awaiter(this, void 0, void 0, function* () {
                applicationViews[appIdentifier] = [];
                const localAddress = `http://127.0.0.1:${port}/json`;
                try {
                    if (port) {
                        const apps = (yield this.$httpClient.httpRequest(localAddress))
                            .body;
                        applicationViews[appIdentifier] = JSON.parse(apps);
                    }
                }
                catch (err) {
                    this.$logger.trace(`Error while checking ${localAddress}. Error is: ${err.message}`);
                }
            })));
            return applicationViews;
        });
    }
    getFullyQualifiedActivityRegex(appIdentifier) {
        const packageActivitySeparator = "\\/";
        const fullJavaClassName = "([a-zA-Z_0-9]*\\.)*[A-Z_$]($[A-Z_$]|[$_\\w_])*";
        return new RegExp(`${(0, helpers_1.regExpEscape)(appIdentifier)}${packageActivitySeparator}${fullJavaClassName}`, `m`);
    }
    hasValidApksFile(aabFilaPath, apksFilePath) {
        let isValid = false;
        if (this.$fs.exists(apksFilePath)) {
            const lastUpdatedApks = this.$fs.getFsStats(apksFilePath).ctime.getTime();
            const lastUpdatedAab = this.$fs.getFsStats(aabFilaPath).ctime.getTime();
            isValid = lastUpdatedApks >= lastUpdatedAab;
        }
        return isValid;
    }
}
__decorate([
    (0, helpers_1.hook)("install")
], AndroidApplicationManager.prototype, "installApplication", null);
__decorate([
    (0, decorators_1.cache)()
], AndroidApplicationManager.prototype, "getFullyQualifiedActivityRegex", null);
exports.AndroidApplicationManager = AndroidApplicationManager;
