appium-adb
Version:
Android Debug Bridge interface
250 lines • 10.7 kB
JavaScript
;
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_1 = require("../logger");
const helpers_1 = require("../helpers");
const support_1 = require("@appium/support");
const node_path_1 = __importDefault(require("node:path"));
/**
* Extract package and main activity name from application manifest.
*
* @param appPath - The full path to application .apk(s) package
* @return 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_1.APKS_EXTENSION)) {
appPath = await this.extractBaseApk(appPath);
}
const { name: apkPackage, launchableActivity: { name: apkActivity }, } = await helpers_1.readPackageManifest.bind(this)(appPath);
logger_1.log.info(`Package name: '${apkPackage}'`);
logger_1.log.info(`Main activity name: '${apkActivity}'`);
return { apkPackage, apkActivity };
}
/**
* Extract target SDK version from application manifest.
*
* @param appPath - The full path to .apk(s) package.
* @return 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_1.log.debug(`Extracting target SDK version of '${appPath}'`);
const originalAppPath = appPath;
if (appPath.endsWith(helpers_1.APKS_EXTENSION)) {
appPath = await this.extractBaseApk(appPath);
}
const { targetSdkVersion } = await helpers_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.
*
* @param pkg - The class name of the package installed on the device under test.
* @param cmdOutput - Optional parameter containing the output of
* _dumpsys package_ command. It may speed up the method execution.
* @return 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.
*
* @param manifest - Full path to the initial manifest template
* @param manifestPackage - The name of the manifest package
* @param targetPackage - The name of the destination package
*/
async function compileManifest(manifest, manifestPackage, targetPackage) {
const { platform, platformPath } = await getAndroidPlatformAndPath(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 = node_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 binaries = this.binaries;
const args = [
'link',
'-o',
resultPath,
'--manifest',
manifest,
'--rename-manifest-package',
manifestPackage,
'--rename-instrumentation-target-package',
targetPackage,
'-I',
androidJarPath,
'-v',
];
logger_1.log.debug(`Compiling the manifest using '${support_1.util.quote([binaries.aapt2, ...args])}'`);
await (0, teen_process_1.exec)(binaries.aapt2, args);
}
catch (e) {
logger_1.log.debug('Cannot compile the manifest using aapt2. Defaulting to aapt. ' +
`Original error: ${e.message || e.stderr}`);
await this.initAapt();
const binaries = this.binaries;
const args = [
'package',
'-M',
manifest,
'--rename-manifest-package',
manifestPackage,
'--rename-instrumentation-target-package',
targetPackage,
'-I',
androidJarPath,
'-F',
resultPath,
'-f',
];
logger_1.log.debug(`Compiling the manifest using '${support_1.util.quote([binaries.aapt, ...args])}'`);
try {
await (0, teen_process_1.exec)(binaries.aapt, args);
}
catch (e1) {
throw new Error(`Cannot compile the manifest. Original error: ${e1.message || e1.stderr}`);
}
}
logger_1.log.debug(`Compiled the manifest at '${resultPath}'`);
}
/**
* Replace/insert the specially precompiled manifest file into the
* particular package.
*
* @param manifest - Full path to the precompiled manifest
* created by `compileManifest` method call
* without .apk extension
* @param srcApk - Full path to the existing valid application package, where
* this manifest has to be insetred to. This package
* will NOT be modified.
* @param dstApk - Full path to the resulting package.
* The file will be overridden if it already exists.
*/
async function insertManifest(manifest, srcApk, dstApk) {
logger_1.log.debug(`Inserting manifest '${manifest}', src: '${srcApk}', dst: '${dstApk}'`);
await support_1.zip.assertValidZip(srcApk);
await (0, helpers_1.unzipFile)(`${manifest}.apk`);
const manifestName = node_path_1.default.basename(manifest);
try {
await this.initAapt();
const binaries = this.binaries;
await support_1.fs.copyFile(srcApk, dstApk);
logger_1.log.debug('Moving manifest');
try {
await (0, teen_process_1.exec)(binaries.aapt, ['remove', dstApk, manifestName]);
}
catch { }
await (0, teen_process_1.exec)(binaries.aapt, ['add', dstApk, manifestName], {
cwd: node_path_1.default.dirname(manifest),
});
}
catch (e) {
logger_1.log.debug('Cannot insert manifest using aapt. Defaulting to zip. ' +
`Original error: ${e.message || e.stderr}`);
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_1.log.debug(`Extracting the source apk at '${srcApk}'`);
await support_1.zip.extractAllTo(srcApk, tmpRoot);
logger_1.log.debug('Moving manifest');
await support_1.fs.mv(manifest, node_path_1.default.resolve(tmpRoot, manifestName));
logger_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_1.log.debug(`Manifest insertion into '${dstApk}' is completed`);
}
/**
* Check whether package manifest contains Internet permissions.
*
* @param appPath - The full path to .apk(s) package.
* @return True if the manifest requires Internet access permission.
*/
async function hasInternetPermissionFromManifest(appPath) {
logger_1.log.debug(`Checking if '${appPath}' requires internet access permission in the manifest`);
if (appPath.endsWith(helpers_1.APKS_EXTENSION)) {
appPath = await this.extractBaseApk(appPath);
}
const { usesPermissions } = await helpers_1.readPackageManifest.bind(this)(appPath);
return usesPermissions.some((name) => name === 'android.permission.INTERNET');
}
// #region Private functions
/**
* Retrieve the path to the recent installed Android platform.
*
* @param sdkRoot
* @return The resulting path to the newest installed platform.
*/
async function getAndroidPlatformAndPath(sdkRoot) {
const propsPaths = await support_1.fs.glob('*/build.prop', {
cwd: node_path_1.default.resolve(sdkRoot, 'platforms'),
absolute: true,
});
const platformsMapping = {};
for (const propsPath of propsPaths) {
const propsContent = await support_1.fs.readFile(propsPath, 'utf-8');
const platformPath = node_path_1.default.dirname(propsPath);
const platform = node_path_1.default.basename(platformPath);
const match = /ro\.build\.version\.sdk=(\d+)/.exec(propsContent);
if (!match) {
logger_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_1.log.warn(`Found zero platform folders at '${node_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_1.log.debug(`Found the most recent Android platform: ${JSON.stringify(result)}`);
return result;
}
// #endregion
//# sourceMappingURL=android-manifest.js.map