UNPKG

appium-adb

Version:

Android Debug Bridge interface

252 lines 11.5 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.packageAndLaunchActivityFromManifest = packageAndLaunchActivityFromManifest; exports.targetSdkVersionFromManifest = targetSdkVersionFromManifest; exports.targetSdkVersionUsingPKG = targetSdkVersionUsingPKG; exports.compileManifest = compileManifest; exports.insertManifest = insertManifest; exports.hasInternetPermissionFromManifest = hasInternetPermissionFromManifest; exports.getAndroidPlatformAndPath = getAndroidPlatformAndPath; const lodash_1 = __importDefault(require("lodash")); const teen_process_1 = require("teen_process"); const logger_js_1 = require("../logger.js"); const helpers_js_1 = require("../helpers.js"); const support_1 = require("@appium/support"); const path_1 = __importDefault(require("path")); /** * Extract package and main activity name from application manifest. * * @this {import('../adb.js').ADB} * @param {string} appPath - The full path to application .apk(s) package * @return {Promise<import('./types').APKInfo>} The parsed application info. * @throws {error} If there was an error while getting the data from the given * application package. */ async function packageAndLaunchActivityFromManifest(appPath) { if (appPath.endsWith(helpers_js_1.APKS_EXTENSION)) { appPath = await this.extractBaseApk(appPath); } const { name: apkPackage, launchableActivity: { name: apkActivity, }, } = await helpers_js_1.readPackageManifest.bind(this)(appPath); logger_js_1.log.info(`Package name: '${apkPackage}'`); logger_js_1.log.info(`Main activity name: '${apkActivity}'`); return { apkPackage, apkActivity }; } /** * Extract target SDK version from application manifest. * * @this {import('../adb.js').ADB} * @param {string} appPath - The full path to .apk(s) package. * @return {Promise<number>} The version of the target SDK. * @throws {error} If there was an error while getting the data from the given * application package. */ async function targetSdkVersionFromManifest(appPath) { logger_js_1.log.debug(`Extracting target SDK version of '${appPath}'`); const originalAppPath = appPath; if (appPath.endsWith(helpers_js_1.APKS_EXTENSION)) { appPath = await this.extractBaseApk(appPath); } const { targetSdkVersion } = await helpers_js_1.readPackageManifest.bind(this)(appPath); if (!targetSdkVersion) { throw new Error(`Cannot extract targetSdkVersion of '${originalAppPath}'. Does ` + `the package manifest define it?`); } return targetSdkVersion; } /** * Extract target SDK version from package information. * * @this {import('../adb.js').ADB} * @param {string} pkg - The class name of the package installed on the device under test. * @param {string?} [cmdOutput=null] - Optional parameter containing the output of * _dumpsys package_ command. It may speed up the method execution. * @return {Promise<number>} The version of the target SDK. */ async function targetSdkVersionUsingPKG(pkg, cmdOutput = null) { const stdout = cmdOutput || await this.shell(['dumpsys', 'package', pkg]); const targetSdkVersionMatch = new RegExp(/targetSdk=([^\s\s]+)/g).exec(stdout); return targetSdkVersionMatch && targetSdkVersionMatch.length >= 2 ? parseInt(targetSdkVersionMatch[1], 10) : 0; } /** * Create binary representation of package manifest (usually AndroidManifest.xml). * `${manifest}.apk` file will be created as the result of this method * containing the compiled manifest. * * @this {import('../adb.js').ADB} * @param {string} manifest - Full path to the initial manifest template * @param {string} manifestPackage - The name of the manifest package * @param {string} targetPackage - The name of the destination package */ async function compileManifest(manifest, manifestPackage, targetPackage) { const { platform, platformPath } = await getAndroidPlatformAndPath(/** @type {string} */ (this.sdkRoot)); if (!platform || !platformPath) { throw new Error('Cannot compile the manifest. The required platform does not exist (API level >= 17)'); } const resultPath = `${manifest}.apk`; const androidJarPath = path_1.default.resolve(platformPath, 'android.jar'); if (await support_1.fs.exists(resultPath)) { await support_1.fs.rimraf(resultPath); } try { await this.initAapt2(); // https://developer.android.com/studio/command-line/aapt2 const args = [ 'link', '-o', resultPath, '--manifest', manifest, '--rename-manifest-package', manifestPackage, '--rename-instrumentation-target-package', targetPackage, '-I', androidJarPath, '-v', ]; logger_js_1.log.debug(`Compiling the manifest using '${support_1.util.quote([ ( /** @type {import('./types').StringRecord} */(this.binaries)).aapt2, ...args ])}'`); await (0, teen_process_1.exec)(( /** @type {import('./types').StringRecord} */(this.binaries)).aapt2, args); } catch (e) { logger_js_1.log.debug('Cannot compile the manifest using aapt2. Defaulting to aapt. ' + `Original error: ${e.stderr || e.message}`); await this.initAapt(); const args = [ 'package', '-M', manifest, '--rename-manifest-package', manifestPackage, '--rename-instrumentation-target-package', targetPackage, '-I', androidJarPath, '-F', resultPath, '-f', ]; logger_js_1.log.debug(`Compiling the manifest using '${support_1.util.quote([ ( /** @type {import('./types').StringRecord} */(this.binaries)).aapt, ...args ])}'`); try { await (0, teen_process_1.exec)(( /** @type {import('./types').StringRecord} */(this.binaries)).aapt, args); } catch (e1) { throw new Error(`Cannot compile the manifest. Original error: ${e1.stderr || e1.message}`); } } logger_js_1.log.debug(`Compiled the manifest at '${resultPath}'`); } /** * Replace/insert the specially precompiled manifest file into the * particular package. * * @this {import('../adb.js').ADB} * @param {string} manifest - Full path to the precompiled manifest * created by `compileManifest` method call * without .apk extension * @param {string} srcApk - Full path to the existing valid application package, where * this manifest has to be insetred to. This package * will NOT be modified. * @param {string} dstApk - Full path to the resulting package. * The file will be overridden if it already exists. */ async function insertManifest(manifest, srcApk, dstApk) { logger_js_1.log.debug(`Inserting manifest '${manifest}', src: '${srcApk}', dst: '${dstApk}'`); await support_1.zip.assertValidZip(srcApk); await (0, helpers_js_1.unzipFile)(`${manifest}.apk`); const manifestName = path_1.default.basename(manifest); try { await this.initAapt(); await support_1.fs.copyFile(srcApk, dstApk); logger_js_1.log.debug('Moving manifest'); try { await (0, teen_process_1.exec)(( /** @type {import('./types').StringRecord} */(this.binaries)).aapt, [ 'remove', dstApk, manifestName ]); } catch { } await (0, teen_process_1.exec)(( /** @type {import('./types').StringRecord} */(this.binaries)).aapt, [ 'add', dstApk, manifestName ], { cwd: path_1.default.dirname(manifest) }); } catch (e) { logger_js_1.log.debug('Cannot insert manifest using aapt. Defaulting to zip. ' + `Original error: ${e.stderr || e.message}`); const tmpRoot = await support_1.tempDir.openDir(); try { // Unfortunately NodeJS does not provide any reliable methods // to replace files inside zip archives without loading the // whole archive content into RAM logger_js_1.log.debug(`Extracting the source apk at '${srcApk}'`); await support_1.zip.extractAllTo(srcApk, tmpRoot); logger_js_1.log.debug('Moving manifest'); await support_1.fs.mv(manifest, path_1.default.resolve(tmpRoot, manifestName)); logger_js_1.log.debug(`Collecting the destination apk at '${dstApk}'`); await support_1.zip.toArchive(dstApk, { cwd: tmpRoot, }); } finally { await support_1.fs.rimraf(tmpRoot); } } logger_js_1.log.debug(`Manifest insertion into '${dstApk}' is completed`); } /** * Check whether package manifest contains Internet permissions. * * @this {import('../adb.js').ADB} * @param {string} appPath - The full path to .apk(s) package. * @return {Promise<boolean>} True if the manifest requires Internet access permission. */ async function hasInternetPermissionFromManifest(appPath) { logger_js_1.log.debug(`Checking if '${appPath}' requires internet access permission in the manifest`); if (appPath.endsWith(helpers_js_1.APKS_EXTENSION)) { appPath = await this.extractBaseApk(appPath); } const { usesPermissions } = await helpers_js_1.readPackageManifest.bind(this)(appPath); return usesPermissions.some((/** @type {string} */ name) => name === 'android.permission.INTERNET'); } // #region Private functions /** * Retrieve the path to the recent installed Android platform. * * @param {string} sdkRoot * @return {Promise<import('./types').PlatformInfo>} The resulting path to the newest installed platform. */ async function getAndroidPlatformAndPath(sdkRoot) { const propsPaths = await support_1.fs.glob('*/build.prop', { cwd: path_1.default.resolve(sdkRoot, 'platforms'), absolute: true, }); /** @type {Record<string, import('./types').PlatformInfo>} */ const platformsMapping = {}; for (const propsPath of propsPaths) { const propsContent = await support_1.fs.readFile(propsPath, 'utf-8'); const platformPath = path_1.default.dirname(propsPath); const platform = path_1.default.basename(platformPath); const match = /ro\.build\.version\.sdk=(\d+)/.exec(propsContent); if (!match) { logger_js_1.log.warn(`Cannot read the SDK version from '${propsPath}'. Skipping '${platform}'`); continue; } platformsMapping[parseInt(match[1], 10)] = { platform, platformPath, }; } if (lodash_1.default.isEmpty(platformsMapping)) { logger_js_1.log.warn(`Found zero platform folders at '${path_1.default.resolve(sdkRoot, 'platforms')}'. ` + `Do you have any Android SDKs installed?`); return { platform: null, platformPath: null, }; } const recentSdkVersion = lodash_1.default.keys(platformsMapping).sort().reverse()[0]; const result = platformsMapping[recentSdkVersion]; logger_js_1.log.debug(`Found the most recent Android platform: ${JSON.stringify(result)}`); return result; } // #endregion //# sourceMappingURL=android-manifest.js.map