nativescript
Version: 
Command-line interface for building NativeScript projects
226 lines • 11 kB
JavaScript
;
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;
};
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; // 10 secs
    }
    async getInstalledApplications() {
        const result = (await 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);
    }
    async installApplication(packageFilePath, appIdentifier, buildData) {
        if (appIdentifier) {
            const deviceRootPath = `${constants_1.LiveSyncPaths.ANDROID_TMP_DIR_NAME}/${appIdentifier}`;
            await 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)) {
                await this.$androidBundleToolService.buildApks({
                    aabFilePath: packageFilePath,
                    apksOutputPath,
                    signingData: buildData,
                });
            }
            await this.$androidBundleToolService.installApks({
                apksFilePath: apksOutputPath,
                deviceId: this.identifier,
            });
        }
        else {
            return this.adb.executeCommand(["install", "-r", `${packageFilePath}`]);
        }
    }
    uninstallApplication(appIdentifier) {
        // Need to set the treatErrorsAsWarnings to true because when using tns run command if the application is not installed on the device it will throw error
        return this.adb.executeShellCommand(["pm", "uninstall", `${appIdentifier}`], { treatErrorsAsWarnings: true });
    }
    async startApplication(appData) {
        if (appData.waitForDebugger) {
            await this.adb.executeShellCommand([
                `cat /dev/null > ${constants_1.LiveSyncPaths.ANDROID_TMP_DIR_NAME}/${appData.appId}-debugbreak`,
            ]);
        }
        // If the app is debuggable, the Runtime will update the file when its ready for debugging
        // and we will be able to take decisions and synchronize the debug experience based on the content
        await this.adb.executeShellCommand([
            `cat /dev/null > ${constants_1.LiveSyncPaths.ANDROID_TMP_DIR_NAME}/${appData.appId}-debugger-started`,
        ]);
        /*
        Example "pm dump <app_identifier> | grep -A 1 MAIN" output"
            android.intent.action.MAIN:
            3b2df03 org.nativescript.cliapp/com.tns.NativeScriptActivity filter 50dd82e
            Action: "android.intent.action.MAIN"
            Category: "android.intent.category.LAUNCHER"
            --
            intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=org.nativescript.cliapp/com.tns.NativeScriptActivity}
            realActivity=org.nativescript.cliapp/com.tns.NativeScriptActivity
            --
            Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=org.nativescript.cliapp/com.tns.NativeScriptActivity }
            frontOfTask=true task=TaskRecord{fe592ac #449 A=org.nativescript.cliapp U=0 StackId=1 sz=1}
        */
        const appIdentifier = appData.appId;
        const pmDumpOutput = await 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) {
            await this.adb.executeShellCommand([
                "am",
                "start",
                "-n",
                possibleIdentifier,
            ]);
        }
        else {
            this.$logger.trace(`Tried starting activity for: ${appIdentifier}, using activity manager but failed.`);
            await this.adb.executeShellCommand([
                "monkey",
                "-p",
                appIdentifier,
                "-c",
                "android.intent.category.LAUNCHER",
                "1",
            ]);
        }
        await this.onAppLaunch(appData);
    }
    async onAppLaunch(appData) {
        const appIdentifier = appData.appId;
        if (!this.$options.justlaunch && !appData.justLaunch) {
            const deviceIdentifier = this.identifier;
            const processIdentifier = await this.getAppProcessId(deviceIdentifier, appIdentifier);
            if (processIdentifier) {
                this.$deviceLogProvider.setApplicationPidForDevice(deviceIdentifier, processIdentifier);
                this.$deviceLogProvider.setApplicationIdForDevice(deviceIdentifier, appIdentifier);
                this.$deviceLogProvider.setProjectDirForDevice(deviceIdentifier, appData.projectDir);
                await this.$logcatHelper.start({
                    deviceIdentifier: this.identifier,
                    pid: processIdentifier,
                    appId: appIdentifier,
                    onAppRestarted: () => {
                        // If the app restarts, we update the PID and
                        // restart log helper.
                        this.onAppLaunch(appData);
                    },
                });
            }
            else {
                await this.$logcatHelper.dump(this.identifier);
                this.$errors.fail(`Unable to find running "${appIdentifier}" application on device "${deviceIdentifier}".`);
            }
        }
    }
    async getAppProcessId(deviceIdentifier, appIdentifier) {
        const appIdCheckStartTime = new Date().getTime();
        let processIdentifier = "";
        let hasTimedOut = false;
        while (!processIdentifier && !hasTimedOut) {
            processIdentifier = await 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".`);
                await (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);
    }
    async getDebuggableAppViews(appIdentifiers) {
        const mappedAppIdentifierPorts = await this.$androidProcessService.getMappedAbstractToTcpPorts(this.identifier, appIdentifiers, constants_1.TARGET_FRAMEWORK_IDENTIFIERS.Cordova), applicationViews = {};
        await Promise.all(_.map(mappedAppIdentifierPorts, async (port, appIdentifier) => {
            applicationViews[appIdentifier] = [];
            const localAddress = `http://127.0.0.1:${port}/json`;
            try {
                if (port) {
                    const apps = (await 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;
    }
}
exports.AndroidApplicationManager = AndroidApplicationManager;
__decorate([
    (0, helpers_1.hook)("install")
], AndroidApplicationManager.prototype, "installApplication", null);
__decorate([
    (0, decorators_1.cache)()
], AndroidApplicationManager.prototype, "getFullyQualifiedActivityRegex", null);
//# sourceMappingURL=android-application-manager.js.map