fix gitignore again

This commit is contained in:
olcxja 2026-05-10 16:36:35 +02:00
commit 5da5c2afe2
3329 changed files with 364540 additions and 3 deletions

View file

@ -0,0 +1,318 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.execAdb = exports.unforwardPorts = exports.forwardPorts = exports.parseAdbDevices = exports.startActivity = exports.parseAdbInstallOutput = exports.ADBEvent = exports.uninstallApp = exports.closeApp = exports.installApk = exports.waitForClose = exports.waitForBoot = exports.waitForDevice = exports.getDeviceProperties = exports.getDeviceProperty = exports.getDevices = void 0;
const child_process_1 = require("child_process");
const Debug = require("debug");
const os = require("os");
const path = require("path");
const split2 = require("split2");
const through2 = require("through2");
const errors_1 = require("../../errors");
const process_1 = require("../../utils/process");
const sdk_1 = require("./sdk");
const modulePrefix = 'native-run:android:utils:adb';
const ADB_GETPROP_MAP = new Map([
['ro.product.manufacturer', 'manufacturer'],
['ro.product.model', 'model'],
['ro.product.name', 'product'],
['ro.build.version.sdk', 'sdkVersion'],
]);
async function getDevices(sdk) {
const debug = Debug(`${modulePrefix}:${getDevices.name}`);
const args = ['devices', '-l'];
debug('Invoking adb with args: %O', args);
const stdout = await execAdb(sdk, args, { timeout: 5000 });
const devices = parseAdbDevices(stdout);
await Promise.all(devices.map(async (device) => {
const properties = await getDeviceProperties(sdk, device);
for (const [prop, deviceProp] of ADB_GETPROP_MAP.entries()) {
const value = properties[prop];
if (value) {
device[deviceProp] = value;
}
}
}));
debug('Found adb devices: %O', devices);
return devices;
}
exports.getDevices = getDevices;
async function getDeviceProperty(sdk, device, property) {
const debug = Debug(`${modulePrefix}:${getDeviceProperty.name}`);
const args = ['-s', device.serial, 'shell', 'getprop', property];
debug('Invoking adb with args: %O', args);
const stdout = await execAdb(sdk, args, { timeout: 5000 });
return stdout.trim();
}
exports.getDeviceProperty = getDeviceProperty;
async function getDeviceProperties(sdk, device) {
const debug = Debug(`${modulePrefix}:${getDeviceProperties.name}`);
const args = ['-s', device.serial, 'shell', 'getprop'];
debug('Invoking adb with args: %O', args);
const stdout = await execAdb(sdk, args, { timeout: 5000 });
const re = /^\[([a-z0-9.]+)\]: \[(.*)\]$/;
const propAllowList = [...ADB_GETPROP_MAP.keys()];
const properties = {};
for (const line of stdout.split(os.EOL)) {
const m = line.match(re);
if (m) {
const [, key, value] = m;
if (propAllowList.includes(key)) {
properties[key] = value;
}
}
}
return properties;
}
exports.getDeviceProperties = getDeviceProperties;
async function waitForDevice(sdk, serial) {
const debug = Debug(`${modulePrefix}:${waitForDevice.name}`);
const args = ['-s', serial, 'wait-for-any-device'];
debug('Invoking adb with args: %O', args);
await execAdb(sdk, args);
debug('Device %s is connected to ADB!', serial);
}
exports.waitForDevice = waitForDevice;
async function waitForBoot(sdk, device) {
const debug = Debug(`${modulePrefix}:${waitForBoot.name}`);
return new Promise((resolve) => {
const interval = setInterval(async () => {
const booted = await getDeviceProperty(sdk, device, 'dev.bootcomplete');
if (booted) {
debug('Device %s is booted!', device.serial);
clearInterval(interval);
resolve();
}
}, 100);
});
}
exports.waitForBoot = waitForBoot;
async function waitForClose(sdk, device, app) {
const debug = Debug(`${modulePrefix}:${waitForClose.name}`);
const args = ['-s', device.serial, 'shell', `ps | grep ${app}`];
return new Promise((resolve) => {
const interval = setInterval(async () => {
try {
debug('Invoking adb with args: %O', args);
await execAdb(sdk, args);
}
catch (e) {
debug('Error received from adb: %O', e);
debug('App %s no longer found in process list for %s', app, device.serial);
clearInterval(interval);
resolve();
}
}, 500);
});
}
exports.waitForClose = waitForClose;
async function installApk(sdk, device, apk) {
const debug = Debug(`${modulePrefix}:${installApk.name}`);
const platformTools = await (0, sdk_1.getSDKPackage)(path.join(sdk.root, 'platform-tools'));
const adbBin = path.join(platformTools.location, 'adb');
const args = ['-s', device.serial, 'install', '-r', '-t', apk];
debug('Invoking adb with args: %O', args);
const p = (0, child_process_1.spawn)(adbBin, args, {
stdio: 'pipe',
env: (0, sdk_1.supplementProcessEnv)(sdk),
});
return new Promise((resolve, reject) => {
p.on('close', (code) => {
if (code === 0) {
resolve();
}
else {
reject(new errors_1.ADBException(`Non-zero exit code from adb: ${code}`));
}
});
p.on('error', (err) => {
debug('adb install error: %O', err);
reject(err);
});
p.stderr.pipe(split2()).pipe(through2((chunk, enc, cb) => {
const line = chunk.toString();
debug('adb install: %O', line);
const event = parseAdbInstallOutput(line);
if (event === ADBEvent.IncompatibleUpdateFailure) {
reject(new errors_1.ADBException(`Encountered adb error: ${ADBEvent[event]}.`, errors_1.ERR_INCOMPATIBLE_UPDATE));
}
else if (event === ADBEvent.NewerVersionOnDeviceFailure) {
reject(new errors_1.ADBException(`Encountered adb error: ${ADBEvent[event]}.`, errors_1.ERR_VERSION_DOWNGRADE));
}
else if (event === ADBEvent.NewerSdkRequiredOnDeviceFailure) {
reject(new errors_1.ADBException(`Encountered adb error: ${ADBEvent[event]}.`, errors_1.ERR_MIN_SDK_VERSION));
}
else if (event === ADBEvent.NoCertificates) {
reject(new errors_1.ADBException(`Encountered adb error: ${ADBEvent[event]}.`, errors_1.ERR_NO_CERTIFICATES));
}
else if (event === ADBEvent.NotEnoughSpace) {
reject(new errors_1.ADBException(`Encountered adb error: ${ADBEvent[event]}.`, errors_1.ERR_NOT_ENOUGH_SPACE));
}
else if (event === ADBEvent.DeviceOffline) {
reject(new errors_1.ADBException(`Encountered adb error: ${ADBEvent[event]}.`, errors_1.ERR_DEVICE_OFFLINE));
}
cb();
}));
});
}
exports.installApk = installApk;
async function closeApp(sdk, device, app) {
const debug = Debug(`${modulePrefix}:${closeApp.name}`);
const args = ['-s', device.serial, 'shell', 'am', 'force-stop', app];
debug('Invoking adb with args: %O', args);
await execAdb(sdk, args);
}
exports.closeApp = closeApp;
async function uninstallApp(sdk, device, app) {
const debug = Debug(`${modulePrefix}:${uninstallApp.name}`);
const args = ['-s', device.serial, 'uninstall', app];
debug('Invoking adb with args: %O', args);
await execAdb(sdk, args);
}
exports.uninstallApp = uninstallApp;
var ADBEvent;
(function (ADBEvent) {
ADBEvent[ADBEvent["IncompatibleUpdateFailure"] = 0] = "IncompatibleUpdateFailure";
ADBEvent[ADBEvent["NewerVersionOnDeviceFailure"] = 1] = "NewerVersionOnDeviceFailure";
ADBEvent[ADBEvent["NewerSdkRequiredOnDeviceFailure"] = 2] = "NewerSdkRequiredOnDeviceFailure";
ADBEvent[ADBEvent["NoCertificates"] = 3] = "NoCertificates";
ADBEvent[ADBEvent["NotEnoughSpace"] = 4] = "NotEnoughSpace";
ADBEvent[ADBEvent["DeviceOffline"] = 5] = "DeviceOffline";
})(ADBEvent = exports.ADBEvent || (exports.ADBEvent = {}));
function parseAdbInstallOutput(line) {
const debug = Debug(`${modulePrefix}:${parseAdbInstallOutput.name}`);
let event;
if (line.includes('INSTALL_FAILED_UPDATE_INCOMPATIBLE')) {
event = ADBEvent.IncompatibleUpdateFailure;
}
else if (line.includes('INSTALL_FAILED_VERSION_DOWNGRADE')) {
event = ADBEvent.NewerVersionOnDeviceFailure;
}
else if (line.includes('INSTALL_FAILED_OLDER_SDK')) {
event = ADBEvent.NewerSdkRequiredOnDeviceFailure;
}
else if (line.includes('INSTALL_PARSE_FAILED_NO_CERTIFICATES')) {
event = ADBEvent.NoCertificates;
}
else if (line.includes('INSTALL_FAILED_INSUFFICIENT_STORAGE') || line.includes('not enough space')) {
event = ADBEvent.NotEnoughSpace;
}
else if (line.includes('device offline')) {
event = ADBEvent.DeviceOffline;
}
if (typeof event !== 'undefined') {
debug('Parsed event from adb install output: %s', ADBEvent[event]);
}
return event;
}
exports.parseAdbInstallOutput = parseAdbInstallOutput;
async function startActivity(sdk, device, packageName, activityName) {
const debug = Debug(`${modulePrefix}:${startActivity.name}`);
const args = ['-s', device.serial, 'shell', 'am', 'start', '-W', '-n', `${packageName}/${activityName}`];
debug('Invoking adb with args: %O', args);
await execAdb(sdk, args, { timeout: 5000 });
}
exports.startActivity = startActivity;
function parseAdbDevices(output) {
const debug = Debug(`${modulePrefix}:${parseAdbDevices.name}`);
const re = /^([\S]+)\s+([a-z\s]+)\s+(.*)$/;
const ipRe = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/;
const lines = output.split(os.EOL);
debug('Parsing adb devices from output lines: %O', lines);
const devices = [];
for (const line of lines) {
if (line && !line.startsWith('List')) {
const m = line.match(re);
if (m) {
const [, serial, state, description] = m;
const properties = description
.split(/\s+/)
.map((prop) => (prop.includes(':') ? prop.split(':') : undefined))
.filter((kv) => typeof kv !== 'undefined' && kv.length >= 2)
.reduce((acc, [k, v]) => {
if (k && v) {
acc[k.trim()] = v.trim();
}
return acc;
}, {});
const isIP = !!serial.match(ipRe);
const isGenericDevice = (properties['device'] || '').startsWith('generic');
const type = 'usb' in properties || isIP || !serial.startsWith('emulator') || !isGenericDevice ? 'hardware' : 'emulator';
const connection = 'usb' in properties ? 'usb' : isIP ? 'tcpip' : null;
devices.push({
serial,
state,
type,
connection,
properties,
// We might not know these yet
manufacturer: '',
model: properties['model'] || '',
product: properties['product'] || '',
sdkVersion: '',
});
}
else {
debug('adb devices output line does not match expected regex: %O', line);
}
}
}
return devices;
}
exports.parseAdbDevices = parseAdbDevices;
async function forwardPorts(sdk, device, ports) {
const debug = Debug(`${modulePrefix}:${forwardPorts.name}`);
const args = ['-s', device.serial, 'reverse', `tcp:${ports.device}`, `tcp:${ports.host}`];
debug('Invoking adb with args: %O', args);
await execAdb(sdk, args, { timeout: 5000 });
}
exports.forwardPorts = forwardPorts;
async function unforwardPorts(sdk, device, ports) {
const debug = Debug(`${modulePrefix}:${unforwardPorts.name}`);
const args = ['-s', device.serial, 'reverse', '--remove', `tcp:${ports.device}`];
debug('Invoking adb with args: %O', args);
await execAdb(sdk, args, { timeout: 5000 });
}
exports.unforwardPorts = unforwardPorts;
async function execAdb(sdk, args, options = {}) {
const debug = Debug(`${modulePrefix}:${execAdb.name}`);
let timer;
const retry = async () => {
const msg = `ADBs is unresponsive after ${options.timeout}ms, killing server and retrying...\n`;
if (process.argv.includes('--json')) {
debug(msg);
}
else {
process.stderr.write(msg);
}
debug('ADB timeout of %O reached, killing server and retrying...', options.timeout);
debug('Invoking adb with args: %O', ['kill-server']);
await execAdb(sdk, ['kill-server']);
debug('Invoking adb with args: %O', ['start-server']);
await execAdb(sdk, ['start-server']);
debug('Retrying...');
return run();
};
const run = async () => {
const platformTools = await (0, sdk_1.getSDKPackage)(path.join(sdk.root, 'platform-tools'));
const adbBin = path.join(platformTools.location, 'adb');
const { stdout } = await (0, process_1.execFile)(adbBin, args, {
env: (0, sdk_1.supplementProcessEnv)(sdk),
});
if (timer) {
clearTimeout(timer);
timer = undefined;
}
return stdout;
};
return new Promise((resolve, reject) => {
if (options.timeout) {
timer = setTimeout(() => retry().then(resolve, reject), options.timeout);
}
run().then(resolve, (err) => {
if (!timer) {
reject(err);
}
});
});
}
exports.execAdb = execAdb;

View file

@ -0,0 +1,36 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getApkInfo = exports.readAndroidManifest = void 0;
const unzip_1 = require("../../utils/unzip");
const binary_xml_parser_1 = require("./binary-xml-parser");
async function readAndroidManifest(apkPath) {
let error;
const chunks = [];
await (0, unzip_1.unzip)(apkPath, async (entry, zipfile, openReadStream) => {
if (entry.fileName === 'AndroidManifest.xml') {
const readStream = await openReadStream(entry);
readStream.on('error', (err) => (error = err));
readStream.on('data', (chunk) => chunks.push(chunk));
readStream.on('end', () => zipfile.close());
}
else {
zipfile.readEntry();
}
});
if (error) {
throw error;
}
const buf = Buffer.concat(chunks);
const manifestBuffer = Buffer.from(buf);
return new binary_xml_parser_1.BinaryXmlParser(manifestBuffer).parse();
}
exports.readAndroidManifest = readAndroidManifest;
async function getApkInfo(apkPath) {
const doc = await readAndroidManifest(apkPath);
const appId = doc.attributes.find((a) => a.name === 'package').value;
const application = doc.childNodes.find((n) => n.nodeName === 'application');
const activity = application.childNodes.find((n) => n.nodeName === 'activity');
const activityName = activity.attributes.find((a) => a.name === 'name').value;
return { appId, activityName };
}
exports.getApkInfo = getApkInfo;

View file

@ -0,0 +1,69 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getInstalledAVDs = exports.getAVDFromINI = exports.getSDKVersionFromTarget = exports.getAVDFromConfigINI = exports.getAVDINIs = exports.isAVDConfigINI = exports.isAVDINI = void 0;
const utils_fs_1 = require("@ionic/utils-fs");
const Debug = require("debug");
const pathlib = require("path");
const ini_1 = require("../../utils/ini");
const modulePrefix = 'native-run:android:utils:avd';
const isAVDINI = (o) => o &&
typeof o['avd.ini.encoding'] === 'string' &&
typeof o['path'] === 'string' &&
typeof o['path.rel'] === 'string' &&
typeof o['target'] === 'string';
exports.isAVDINI = isAVDINI;
const isAVDConfigINI = (o) => o &&
(typeof o['avd.ini.displayname'] === 'undefined' || typeof o['avd.ini.displayname'] === 'string') &&
(typeof o['hw.lcd.density'] === 'undefined' || typeof o['hw.lcd.density'] === 'string') &&
(typeof o['hw.lcd.height'] === 'undefined' || typeof o['hw.lcd.height'] === 'string') &&
(typeof o['hw.lcd.width'] === 'undefined' || typeof o['hw.lcd.width'] === 'string') &&
(typeof o['image.sysdir.1'] === 'undefined' || typeof o['image.sysdir.1'] === 'string');
exports.isAVDConfigINI = isAVDConfigINI;
async function getAVDINIs(sdk) {
const debug = Debug(`${modulePrefix}:${getAVDINIs.name}`);
const contents = await (0, utils_fs_1.readdir)(sdk.avdHome);
const iniFilePaths = contents
.filter((f) => pathlib.extname(f) === '.ini')
.map((f) => pathlib.resolve(sdk.avdHome, f));
debug('Discovered AVD ini files: %O', iniFilePaths);
const iniFiles = await Promise.all(iniFilePaths.map(async (f) => [f, await (0, ini_1.readINI)(f, exports.isAVDINI)]));
const avdInis = iniFiles.filter((c) => typeof c[1] !== 'undefined');
return avdInis;
}
exports.getAVDINIs = getAVDINIs;
function getAVDFromConfigINI(inipath, ini, configini) {
const inibasename = pathlib.basename(inipath);
const id = inibasename.substring(0, inibasename.length - pathlib.extname(inibasename).length);
const name = configini['avd.ini.displayname'] ? String(configini['avd.ini.displayname']) : id.replace(/_/g, ' ');
const screenDPI = configini['hw.lcd.density'] ? Number(configini['hw.lcd.density']) : null;
const screenWidth = configini['hw.lcd.width'] ? Number(configini['hw.lcd.width']) : null;
const screenHeight = configini['hw.lcd.height'] ? Number(configini['hw.lcd.height']) : null;
return {
id,
path: ini.path,
name,
sdkVersion: getSDKVersionFromTarget(ini.target),
screenDPI,
screenWidth,
screenHeight,
};
}
exports.getAVDFromConfigINI = getAVDFromConfigINI;
function getSDKVersionFromTarget(target) {
return target.replace(/^android-(\d+)/, '$1');
}
exports.getSDKVersionFromTarget = getSDKVersionFromTarget;
async function getAVDFromINI(inipath, ini) {
const configini = await (0, ini_1.readINI)(pathlib.resolve(ini.path, 'config.ini'), exports.isAVDConfigINI);
if (configini) {
return getAVDFromConfigINI(inipath, ini, configini);
}
}
exports.getAVDFromINI = getAVDFromINI;
async function getInstalledAVDs(sdk) {
const avdInis = await getAVDINIs(sdk);
const possibleAvds = await Promise.all(avdInis.map(([inipath, ini]) => getAVDFromINI(inipath, ini)));
const avds = possibleAvds.filter((avd) => typeof avd !== 'undefined');
return avds;
}
exports.getInstalledAVDs = getInstalledAVDs;

View file

@ -0,0 +1,593 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BinaryXmlParser = void 0;
/*
Copyright © 2013 CyberAgent, Inc.
Copyright © 2016 The OpenSTF Project
Modifications Copyright © 2018 Drifty Co
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
https://github.com/openstf/adbkit-apkreader/blob/368f6b207c57e82fa7373c1608920ca7f4a8904c/lib/apkreader/parser/binaryxml.js
*/
const assert = require("assert");
// import * as Debug from 'debug';
const errors_1 = require("../../errors");
// const debug = Debug('native-run:android:util:binary-xml-parser');
const NodeType = {
ELEMENT_NODE: 1,
ATTRIBUTE_NODE: 2,
CDATA_SECTION_NODE: 4,
};
const ChunkType = {
NULL: 0x0000,
STRING_POOL: 0x0001,
TABLE: 0x0002,
XML: 0x0003,
XML_FIRST_CHUNK: 0x0100,
XML_START_NAMESPACE: 0x0100,
XML_END_NAMESPACE: 0x0101,
XML_START_ELEMENT: 0x0102,
XML_END_ELEMENT: 0x0103,
XML_CDATA: 0x0104,
XML_LAST_CHUNK: 0x017f,
XML_RESOURCE_MAP: 0x0180,
TABLE_PACKAGE: 0x0200,
TABLE_TYPE: 0x0201,
TABLE_TYPE_SPEC: 0x0202,
};
const StringFlags = {
SORTED: 1 << 0,
UTF8: 1 << 8,
};
// Taken from android.util.TypedValue
const TypedValue = {
COMPLEX_MANTISSA_MASK: 0x00ffffff,
COMPLEX_MANTISSA_SHIFT: 0x00000008,
COMPLEX_RADIX_0p23: 0x00000003,
COMPLEX_RADIX_16p7: 0x00000001,
COMPLEX_RADIX_23p0: 0x00000000,
COMPLEX_RADIX_8p15: 0x00000002,
COMPLEX_RADIX_MASK: 0x00000003,
COMPLEX_RADIX_SHIFT: 0x00000004,
COMPLEX_UNIT_DIP: 0x00000001,
COMPLEX_UNIT_FRACTION: 0x00000000,
COMPLEX_UNIT_FRACTION_PARENT: 0x00000001,
COMPLEX_UNIT_IN: 0x00000004,
COMPLEX_UNIT_MASK: 0x0000000f,
COMPLEX_UNIT_MM: 0x00000005,
COMPLEX_UNIT_PT: 0x00000003,
COMPLEX_UNIT_PX: 0x00000000,
COMPLEX_UNIT_SHIFT: 0x00000000,
COMPLEX_UNIT_SP: 0x00000002,
DENSITY_DEFAULT: 0x00000000,
DENSITY_NONE: 0x0000ffff,
TYPE_ATTRIBUTE: 0x00000002,
TYPE_DIMENSION: 0x00000005,
TYPE_FIRST_COLOR_INT: 0x0000001c,
TYPE_FIRST_INT: 0x00000010,
TYPE_FLOAT: 0x00000004,
TYPE_FRACTION: 0x00000006,
TYPE_INT_BOOLEAN: 0x00000012,
TYPE_INT_COLOR_ARGB4: 0x0000001e,
TYPE_INT_COLOR_ARGB8: 0x0000001c,
TYPE_INT_COLOR_RGB4: 0x0000001f,
TYPE_INT_COLOR_RGB8: 0x0000001d,
TYPE_INT_DEC: 0x00000010,
TYPE_INT_HEX: 0x00000011,
TYPE_LAST_COLOR_INT: 0x0000001f,
TYPE_LAST_INT: 0x0000001f,
TYPE_NULL: 0x00000000,
TYPE_REFERENCE: 0x00000001,
TYPE_STRING: 0x00000003,
};
class BinaryXmlParser {
constructor(buffer, options = {}) {
this.buffer = buffer;
this.cursor = 0;
this.strings = [];
this.resources = [];
this.stack = [];
this.debug = false;
this.debug = options.debug || false;
}
readU8() {
// debug('readU8');
// debug('cursor:', this.cursor);
const val = this.buffer[this.cursor];
// debug('value:', val);
this.cursor += 1;
return val;
}
readU16() {
// debug('readU16');
// debug('cursor:', this.cursor);
const val = this.buffer.readUInt16LE(this.cursor);
// debug('value:', val);
this.cursor += 2;
return val;
}
readS32() {
// debug('readS32');
// debug('cursor:', this.cursor);
const val = this.buffer.readInt32LE(this.cursor);
// debug('value:', val);
this.cursor += 4;
return val;
}
readU32() {
// debug('readU32');
// debug('cursor:', this.cursor);
const val = this.buffer.readUInt32LE(this.cursor);
// debug('value:', val);
this.cursor += 4;
return val;
}
readLength8() {
// debug('readLength8');
let len = this.readU8();
if (len & 0x80) {
len = (len & 0x7f) << 8;
len += this.readU8();
}
// debug('length:', len);
return len;
}
readLength16() {
// debug('readLength16');
let len = this.readU16();
if (len & 0x8000) {
len = (len & 0x7fff) << 16;
len += this.readU16();
}
// debug('length:', len);
return len;
}
readDimension() {
// debug('readDimension');
const dimension = {
value: null,
unit: null,
rawUnit: null,
};
const value = this.readU32();
const unit = dimension.value & 0xff;
dimension.value = value >> 8;
dimension.rawUnit = unit;
switch (unit) {
case TypedValue.COMPLEX_UNIT_MM:
dimension.unit = 'mm';
break;
case TypedValue.COMPLEX_UNIT_PX:
dimension.unit = 'px';
break;
case TypedValue.COMPLEX_UNIT_DIP:
dimension.unit = 'dp';
break;
case TypedValue.COMPLEX_UNIT_SP:
dimension.unit = 'sp';
break;
case TypedValue.COMPLEX_UNIT_PT:
dimension.unit = 'pt';
break;
case TypedValue.COMPLEX_UNIT_IN:
dimension.unit = 'in';
break;
}
return dimension;
}
readFraction() {
// debug('readFraction');
const fraction = {
value: null,
type: null,
rawType: null,
};
const value = this.readU32();
const type = value & 0xf;
fraction.value = this.convertIntToFloat(value >> 4);
fraction.rawType = type;
switch (type) {
case TypedValue.COMPLEX_UNIT_FRACTION:
fraction.type = '%';
break;
case TypedValue.COMPLEX_UNIT_FRACTION_PARENT:
fraction.type = '%p';
break;
}
return fraction;
}
readHex24() {
// debug('readHex24');
const val = (this.readU32() & 0xffffff).toString(16);
return val;
}
readHex32() {
// debug('readHex32');
const val = this.readU32().toString(16);
return val;
}
readTypedValue() {
// debug('readTypedValue');
const typedValue = {
value: null,
type: null,
rawType: null,
};
const start = this.cursor;
let size = this.readU16();
/* const zero = */ this.readU8();
const dataType = this.readU8();
// Yes, there has been a real world APK where the size is malformed.
if (size === 0) {
size = 8;
}
typedValue.rawType = dataType;
switch (dataType) {
case TypedValue.TYPE_INT_DEC:
typedValue.value = this.readS32();
typedValue.type = 'int_dec';
break;
case TypedValue.TYPE_INT_HEX:
typedValue.value = this.readS32();
typedValue.type = 'int_hex';
break;
case TypedValue.TYPE_STRING: {
const ref = this.readS32();
typedValue.value = ref > 0 ? this.strings[ref] : '';
typedValue.type = 'string';
break;
}
case TypedValue.TYPE_REFERENCE: {
const id = this.readU32();
typedValue.value = `resourceId:0x${id.toString(16)}`;
typedValue.type = 'reference';
break;
}
case TypedValue.TYPE_INT_BOOLEAN:
typedValue.value = this.readS32() !== 0;
typedValue.type = 'boolean';
break;
case TypedValue.TYPE_NULL:
this.readU32();
typedValue.value = null;
typedValue.type = 'null';
break;
case TypedValue.TYPE_INT_COLOR_RGB8:
typedValue.value = this.readHex24();
typedValue.type = 'rgb8';
break;
case TypedValue.TYPE_INT_COLOR_RGB4:
typedValue.value = this.readHex24();
typedValue.type = 'rgb4';
break;
case TypedValue.TYPE_INT_COLOR_ARGB8:
typedValue.value = this.readHex32();
typedValue.type = 'argb8';
break;
case TypedValue.TYPE_INT_COLOR_ARGB4:
typedValue.value = this.readHex32();
typedValue.type = 'argb4';
break;
case TypedValue.TYPE_DIMENSION:
typedValue.value = this.readDimension();
typedValue.type = 'dimension';
break;
case TypedValue.TYPE_FRACTION:
typedValue.value = this.readFraction();
typedValue.type = 'fraction';
break;
default: {
// const type = dataType.toString(16);
// debug(`Not sure what to do with typed value of type 0x${type}, falling back to reading an uint32.`);
typedValue.value = this.readU32();
typedValue.type = 'unknown';
}
}
// Ensure we consume the whole value
const end = start + size;
if (this.cursor !== end) {
// const type = dataType.toString(16);
// const diff = end - this.cursor;
// debug(`Cursor is off by ${diff} bytes at ${this.cursor} at supposed end \
// of typed value of type 0x${type}. The typed value started at offset ${start} \
// and is supposed to end at offset ${end}. Ignoring the rest of the value.`);
this.cursor = end;
}
return typedValue;
}
// https://twitter.com/kawasima/status/427730289201139712
convertIntToFloat(int) {
const buf = new ArrayBuffer(4);
new Int32Array(buf)[0] = int;
return new Float32Array(buf)[0];
}
readString(encoding) {
// debug('readString', encoding);
let stringLength;
let byteLength;
let value;
switch (encoding) {
case 'utf-8':
stringLength = this.readLength8();
// debug('stringLength:', stringLength);
byteLength = this.readLength8();
// debug('byteLength:', byteLength);
value = this.buffer.toString(encoding, this.cursor, (this.cursor += byteLength));
// debug('value:', value);
assert.equal(this.readU8(), 0, 'String must end with trailing zero');
return value;
case 'ucs2':
stringLength = this.readLength16();
// debug('stringLength:', stringLength);
byteLength = stringLength * 2;
// debug('byteLength:', byteLength);
value = this.buffer.toString(encoding, this.cursor, (this.cursor += byteLength));
// debug('value:', value);
assert.equal(this.readU16(), 0, 'String must end with trailing zero');
return value;
default:
throw new errors_1.Exception(`Unsupported encoding '${encoding}'`);
}
}
readChunkHeader() {
// debug('readChunkHeader');
const header = {
startOffset: this.cursor,
chunkType: this.readU16(),
headerSize: this.readU16(),
chunkSize: this.readU32(),
};
// debug('startOffset:', header.startOffset);
// debug('chunkType:', header.chunkType);
// debug('headerSize:', header.headerSize);
// debug('chunkSize:', header.chunkSize);
return header;
}
readStringPool(header) {
// debug('readStringPool');
header.stringCount = this.readU32();
// debug('stringCount:', header.stringCount);
header.styleCount = this.readU32();
// debug('styleCount:', header.styleCount);
header.flags = this.readU32();
// debug('flags:', header.flags);
header.stringsStart = this.readU32();
// debug('stringsStart:', header.stringsStart);
header.stylesStart = this.readU32();
// debug('stylesStart:', header.stylesStart);
if (header.chunkType !== ChunkType.STRING_POOL) {
throw new errors_1.Exception('Invalid string pool header');
}
const offsets = [];
for (let i = 0, l = header.stringCount; i < l; ++i) {
// debug('offset:', i);
offsets.push(this.readU32());
}
// const sorted = (header.flags & StringFlags.SORTED) === StringFlags.SORTED;
// debug('sorted:', sorted);
const encoding = (header.flags & StringFlags.UTF8) === StringFlags.UTF8 ? 'utf-8' : 'ucs2';
// debug('encoding:', encoding);
const stringsStart = header.startOffset + header.stringsStart;
this.cursor = stringsStart;
for (let i = 0, l = header.stringCount; i < l; ++i) {
// debug('string:', i);
// debug('offset:', offsets[i]);
this.cursor = stringsStart + offsets[i];
this.strings.push(this.readString(encoding));
}
// Skip styles
this.cursor = header.startOffset + header.chunkSize;
return null;
}
readResourceMap(header) {
// debug('readResourceMap');
const count = Math.floor((header.chunkSize - header.headerSize) / 4);
for (let i = 0; i < count; ++i) {
this.resources.push(this.readU32());
}
return null;
}
readXmlNamespaceStart( /* header */) {
// debug('readXmlNamespaceStart');
this.readU32();
this.readU32();
this.readU32();
this.readU32();
// const line = this.readU32();
// const commentRef = this.readU32();
// const prefixRef = this.readS32();
// const uriRef = this.readS32();
// We don't currently care about the values, but they could
// be accessed like so:
//
// namespaceURI.prefix = this.strings[prefixRef] // if prefixRef > 0
// namespaceURI.uri = this.strings[uriRef] // if uriRef > 0
return null;
}
readXmlNamespaceEnd( /* header */) {
// debug('readXmlNamespaceEnd');
this.readU32();
this.readU32();
this.readU32();
this.readU32();
// const line = this.readU32();
// const commentRef = this.readU32();
// const prefixRef = this.readS32();
// const uriRef = this.readS32();
// We don't currently care about the values, but they could
// be accessed like so:
//
// namespaceURI.prefix = this.strings[prefixRef] // if prefixRef > 0
// namespaceURI.uri = this.strings[uriRef] // if uriRef > 0
return null;
}
readXmlElementStart( /* header */) {
// debug('readXmlElementStart');
const node = {
namespaceURI: null,
nodeType: NodeType.ELEMENT_NODE,
nodeName: null,
attributes: [],
childNodes: [],
};
this.readU32();
this.readU32();
// const line = this.readU32();
// const commentRef = this.readU32();
const nsRef = this.readS32();
const nameRef = this.readS32();
if (nsRef > 0) {
node.namespaceURI = this.strings[nsRef];
}
node.nodeName = this.strings[nameRef];
this.readU16();
this.readU16();
// const attrStart = this.readU16();
// const attrSize = this.readU16();
const attrCount = this.readU16();
// const idIndex = this.readU16();
// const classIndex = this.readU16();
// const styleIndex = this.readU16();
this.readU16();
this.readU16();
this.readU16();
for (let i = 0; i < attrCount; ++i) {
node.attributes.push(this.readXmlAttribute());
}
if (this.document) {
this.parent.childNodes.push(node);
this.parent = node;
}
else {
this.document = this.parent = node;
}
this.stack.push(node);
return node;
}
readXmlAttribute() {
// debug('readXmlAttribute');
const attr = {
namespaceURI: null,
nodeType: NodeType.ATTRIBUTE_NODE,
nodeName: null,
name: null,
value: null,
typedValue: null,
};
const nsRef = this.readS32();
const nameRef = this.readS32();
const valueRef = this.readS32();
if (nsRef > 0) {
attr.namespaceURI = this.strings[nsRef];
}
attr.nodeName = attr.name = this.strings[nameRef];
if (valueRef > 0) {
attr.value = this.strings[valueRef];
}
attr.typedValue = this.readTypedValue();
return attr;
}
readXmlElementEnd( /* header */) {
// debug('readXmlCData');
this.readU32();
this.readU32();
this.readU32();
this.readU32();
// const line = this.readU32();
// const commentRef = this.readU32();
// const nsRef = this.readS32();
// const nameRef = this.readS32();
this.stack.pop();
this.parent = this.stack[this.stack.length - 1];
return null;
}
readXmlCData( /* header */) {
// debug('readXmlCData');
const cdata = {
namespaceURI: null,
nodeType: NodeType.CDATA_SECTION_NODE,
nodeName: '#cdata',
data: null,
typedValue: null,
};
this.readU32();
this.readU32();
// const line = this.readU32();
// const commentRef = this.readU32();
const dataRef = this.readS32();
if (dataRef > 0) {
cdata.data = this.strings[dataRef];
}
cdata.typedValue = this.readTypedValue();
this.parent.childNodes.push(cdata);
return cdata;
}
readNull(header) {
// debug('readNull');
this.cursor += header.chunkSize - header.headerSize;
return null;
}
parse() {
// debug('parse');
const xmlHeader = this.readChunkHeader();
if (xmlHeader.chunkType !== ChunkType.XML) {
throw new errors_1.Exception('Invalid XML header');
}
while (this.cursor < this.buffer.length) {
// debug('chunk');
const start = this.cursor;
const header = this.readChunkHeader();
switch (header.chunkType) {
case ChunkType.STRING_POOL:
this.readStringPool(header);
break;
case ChunkType.XML_RESOURCE_MAP:
this.readResourceMap(header);
break;
case ChunkType.XML_START_NAMESPACE:
this.readXmlNamespaceStart();
break;
case ChunkType.XML_END_NAMESPACE:
this.readXmlNamespaceEnd();
break;
case ChunkType.XML_START_ELEMENT:
this.readXmlElementStart();
break;
case ChunkType.XML_END_ELEMENT:
this.readXmlElementEnd();
break;
case ChunkType.XML_CDATA:
this.readXmlCData();
break;
case ChunkType.NULL:
this.readNull(header);
break;
default:
throw new errors_1.Exception(`Unsupported chunk type '${header.chunkType}'`);
}
// Ensure we consume the whole chunk
const end = start + header.chunkSize;
if (this.cursor !== end) {
// const diff = end - this.cursor;
// const type = header.chunkType.toString(16);
// debug(`Cursor is off by ${diff} bytes at ${this.cursor} at supposed \
// end of chunk of type 0x${type}. The chunk started at offset ${start} and is \
// supposed to end at offset ${end}. Ignoring the rest of the chunk.`);
// this.cursor = end;
}
}
return this.document;
}
}
exports.BinaryXmlParser = BinaryXmlParser;

View file

@ -0,0 +1,204 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseAndroidConsoleResponse = exports.getAVDFromEmulator = exports.parseEmulatorOutput = exports.EmulatorEvent = exports.spawnEmulator = exports.runEmulator = void 0;
const utils_fs_1 = require("@ionic/utils-fs");
const child_process_1 = require("child_process");
const Debug = require("debug");
const net = require("net");
const os = require("os");
const path = require("path");
const split2 = require("split2");
const through2 = require("through2");
const errors_1 = require("../../errors");
const fn_1 = require("../../utils/fn");
const adb_1 = require("./adb");
const sdk_1 = require("./sdk");
const modulePrefix = 'native-run:android:utils:emulator';
/**
* Resolves when emulator is ready and running with the specified AVD.
*/
async function runEmulator(sdk, avd, port) {
try {
await spawnEmulator(sdk, avd, port);
}
catch (e) {
if (!(e instanceof errors_1.EmulatorException) || e.code !== errors_1.ERR_ALREADY_RUNNING) {
throw e;
}
}
const serial = `emulator-${port}`;
const devices = await (0, adb_1.getDevices)(sdk);
const emulator = devices.find((device) => device.serial === serial);
if (!emulator) {
throw new errors_1.EmulatorException(`Emulator not found: ${serial}`);
}
return emulator;
}
exports.runEmulator = runEmulator;
async function spawnEmulator(sdk, avd, port) {
const debug = Debug(`${modulePrefix}:${spawnEmulator.name}`);
const emulator = await (0, sdk_1.getSDKPackage)(path.join(sdk.root, 'emulator'));
const emulatorBin = path.join(emulator.location, 'emulator');
const args = ['-avd', avd.id, '-port', port.toString(), '-verbose'];
debug('Invoking emulator: %O %O', emulatorBin, args);
const p = (0, child_process_1.spawn)(emulatorBin, args, {
detached: true,
stdio: ['ignore', 'pipe', 'pipe'],
env: (0, sdk_1.supplementProcessEnv)(sdk),
});
p.unref();
return new Promise((_resolve, _reject) => {
const resolve = (0, fn_1.once)(() => {
_resolve();
cleanup();
});
const reject = (0, fn_1.once)((err) => {
_reject(err);
cleanup();
});
(0, adb_1.waitForDevice)(sdk, `emulator-${port}`).then(() => resolve(), (err) => reject(err));
const eventParser = through2((chunk, enc, cb) => {
const line = chunk.toString();
debug('Android Emulator: %O', line);
const event = parseEmulatorOutput(line);
if (event === EmulatorEvent.AlreadyRunning) {
reject(new errors_1.EmulatorException(`Emulator already running with AVD [${avd.id}]`, errors_1.ERR_ALREADY_RUNNING));
}
else if (event === EmulatorEvent.UnknownAVD) {
reject(new errors_1.EmulatorException(`Unknown AVD name [${avd.id}]`, errors_1.ERR_UNKNOWN_AVD));
}
else if (event === EmulatorEvent.AVDHomeNotFound) {
reject(new errors_1.EmulatorException(`Emulator cannot find AVD home`, errors_1.ERR_AVD_HOME_NOT_FOUND));
}
cb();
});
const stdoutStream = p.stdout.pipe(split2());
const stderrStream = p.stderr.pipe(split2());
stdoutStream.pipe(eventParser);
stderrStream.pipe(eventParser);
const cleanup = () => {
debug('Unhooking stdout/stderr streams from emulator process');
p.stdout.push(null);
p.stderr.push(null);
};
p.on('close', (code) => {
debug('Emulator closed, exit code %d', code);
if (code) {
reject(new errors_1.EmulatorException(`Non-zero exit code from Emulator: ${code}`, errors_1.ERR_NON_ZERO_EXIT));
}
});
p.on('error', (err) => {
debug('Emulator error: %O', err);
reject(err);
});
});
}
exports.spawnEmulator = spawnEmulator;
var EmulatorEvent;
(function (EmulatorEvent) {
EmulatorEvent[EmulatorEvent["UnknownAVD"] = 0] = "UnknownAVD";
EmulatorEvent[EmulatorEvent["AlreadyRunning"] = 1] = "AlreadyRunning";
EmulatorEvent[EmulatorEvent["AVDHomeNotFound"] = 2] = "AVDHomeNotFound";
})(EmulatorEvent = exports.EmulatorEvent || (exports.EmulatorEvent = {}));
function parseEmulatorOutput(line) {
const debug = Debug(`${modulePrefix}:${parseEmulatorOutput.name}`);
let event;
if (line.includes('Unknown AVD name')) {
event = EmulatorEvent.UnknownAVD;
}
else if (line.includes('another emulator instance running with the current AVD')) {
event = EmulatorEvent.AlreadyRunning;
}
else if (line.includes('Cannot find AVD system path')) {
event = EmulatorEvent.AVDHomeNotFound;
}
if (typeof event !== 'undefined') {
debug('Parsed event from emulator output: %s', EmulatorEvent[event]);
}
return event;
}
exports.parseEmulatorOutput = parseEmulatorOutput;
async function getAVDFromEmulator(emulator, avds) {
const debug = Debug(`${modulePrefix}:${getAVDFromEmulator.name}`);
const emulatorPortRegex = /^emulator-(\d+)$/;
const m = emulator.serial.match(emulatorPortRegex);
if (!m) {
throw new errors_1.EmulatorException(`Emulator ${emulator.serial} does not match expected emulator serial format`);
}
const port = Number.parseInt(m[1], 10);
const host = 'localhost';
const sock = net.createConnection({ host, port });
sock.setEncoding('utf8');
sock.setTimeout(5000);
const readAuthFile = new Promise((resolve, reject) => {
sock.on('connect', () => {
debug('Connected to %s:%d', host, port);
(0, utils_fs_1.readFile)(path.resolve(os.homedir(), '.emulator_console_auth_token'), {
encoding: 'utf8',
}).then((contents) => resolve(contents.trim()), (err) => reject(err));
});
});
let Stage;
(function (Stage) {
Stage[Stage["Initial"] = 0] = "Initial";
Stage[Stage["Auth"] = 1] = "Auth";
Stage[Stage["AuthSuccess"] = 2] = "AuthSuccess";
Stage[Stage["Response"] = 3] = "Response";
Stage[Stage["Complete"] = 4] = "Complete";
})(Stage || (Stage = {}));
return new Promise((resolve, reject) => {
let stage = Stage.Initial;
const timer = setTimeout(() => {
if (stage !== Stage.Complete) {
reject(new errors_1.EmulatorException(`Took too long to get AVD name from Android Emulator Console, something went wrong.`));
}
}, 3000);
const cleanup = (0, fn_1.once)(() => {
clearTimeout(timer);
sock.end();
});
sock.on('timeout', () => {
reject(new errors_1.EmulatorException(`Socket timeout on ${host}:${port}`));
cleanup();
});
sock.pipe(split2()).pipe(through2((chunk, enc, cb) => {
const line = chunk.toString();
debug('Android Console: %O', line);
if (stage === Stage.Initial && line.includes('Authentication required')) {
stage = Stage.Auth;
}
else if (stage === Stage.Auth && line.trim() === 'OK') {
readAuthFile.then((token) => sock.write(`auth ${token}\n`, 'utf8'), (err) => reject(err));
stage = Stage.AuthSuccess;
}
else if (stage === Stage.AuthSuccess && line.trim() === 'OK') {
sock.write('avd name\n', 'utf8');
stage = Stage.Response;
}
else if (stage === Stage.Response) {
const avdId = line.trim();
const avd = avds.find((avd) => avd.id === avdId);
if (avd) {
resolve(avd);
}
else {
reject(new errors_1.EmulatorException(`Unknown AVD name [${avdId}]`, errors_1.ERR_UNKNOWN_AVD));
}
stage = Stage.Complete;
cleanup();
}
cb();
}));
});
}
exports.getAVDFromEmulator = getAVDFromEmulator;
function parseAndroidConsoleResponse(output) {
const debug = Debug(`${modulePrefix}:${parseAndroidConsoleResponse.name}`);
const m = /([\s\S]+)OK\r?\n/g.exec(output);
if (m) {
const [, response] = m;
debug('Parsed response data from Android Console output: %O', response);
return response;
}
}
exports.parseAndroidConsoleResponse = parseAndroidConsoleResponse;

View file

@ -0,0 +1,32 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.avdToTarget = exports.deviceToTarget = exports.getVirtualTargets = exports.getDeviceTargets = void 0;
const adb_1 = require("./adb");
const avd_1 = require("./avd");
async function getDeviceTargets(sdk) {
return (await (0, adb_1.getDevices)(sdk)).filter((device) => device.type === 'hardware').map(deviceToTarget);
}
exports.getDeviceTargets = getDeviceTargets;
async function getVirtualTargets(sdk) {
const avds = await (0, avd_1.getInstalledAVDs)(sdk);
return avds.map(avdToTarget);
}
exports.getVirtualTargets = getVirtualTargets;
function deviceToTarget(device) {
return {
platform: 'android',
model: `${device.manufacturer} ${device.model}`,
sdkVersion: device.sdkVersion,
id: device.serial,
};
}
exports.deviceToTarget = deviceToTarget;
function avdToTarget(avd) {
return {
platform: 'android',
name: avd.name,
sdkVersion: avd.sdkVersion,
id: avd.id,
};
}
exports.avdToTarget = avdToTarget;

View file

@ -0,0 +1,132 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.installApkToDevice = exports.selectVirtualDevice = exports.selectHardwareDevice = exports.selectDeviceByTarget = exports.isLikelyEmulator = exports.findAvailableEmulatorPort = void 0;
const Debug = require("debug");
const errors_1 = require("../../errors");
const log_1 = require("../../utils/log");
const adb_1 = require("./adb");
const emulator_1 = require("./emulator");
const modulePrefix = 'native-run:android:utils:run';
async function findAvailableEmulatorPort(devices, start = 5554, end = 5584) {
const debug = Debug(`${modulePrefix}:${findAvailableEmulatorPort.name}`);
const usedPorts = new Set();
for (const d of devices) {
const m = d.serial.match(/^emulator-(\d+)$/);
if (m) {
usedPorts.add(Number(m[1]));
}
}
for (let port = start; port <= end; port += 2) {
if (!usedPorts.has(port)) {
debug('Available emulator port found: %d', port);
return port;
}
}
debug('No available emulator ports found in range %d-%d; defaulting to 5554', start, end);
return 5554;
}
exports.findAvailableEmulatorPort = findAvailableEmulatorPort;
function isLikelyEmulator(device) {
const serialEmu = /^emulator-(\d+)$/;
if (serialEmu.test(device.serial)) {
return true;
}
if (device.type === 'emulator') {
return true;
}
const props = device.properties || {};
const deviceProp = (props['device'] || '').toLowerCase();
const productProp = (props['product'] || '').toLowerCase();
const model = (device.model || '').toLowerCase();
if (deviceProp.startsWith('emu') || deviceProp.includes('generic')) {
return true;
}
if (productProp.includes('sdk_gphone') || productProp.includes('google_sdk')) {
return true;
}
if (model.includes('android_sdk') || model.includes('sdk_gphone')) {
return true;
}
return false;
}
exports.isLikelyEmulator = isLikelyEmulator;
async function selectDeviceByTarget(sdk, devices, avds, target) {
const debug = Debug(`${modulePrefix}:${selectDeviceByTarget.name}`);
debug('--target %s detected', target);
debug('Checking if device can be found by serial: %s', target);
const device = devices.find((d) => d.serial === target);
if (device) {
debug('Device found by serial: %s', device.serial);
return device;
}
const emulatorDevices = devices.filter(isLikelyEmulator);
const pairAVD = async (emulator) => {
let avd;
try {
avd = await (0, emulator_1.getAVDFromEmulator)(emulator, avds);
debug('Emulator %s is using AVD: %s', emulator.serial, avd.id);
}
catch (e) {
debug('Error with emulator %s: %O', emulator.serial, e);
}
return [emulator, avd];
};
debug('Checking if any of %d running emulators are using AVD by ID: %s', emulatorDevices.length, target);
const emulatorsAndAVDs = await Promise.all(emulatorDevices.map((emulator) => pairAVD(emulator)));
const emulators = emulatorsAndAVDs.filter((t) => typeof t[1] !== 'undefined');
const emulator = emulators.find(([, avd]) => avd.id === target);
if (emulator) {
const [device, avd] = emulator;
debug('Emulator %s found by AVD: %s', device.serial, avd.id);
return device;
}
debug('Checking if AVD can be found by ID: %s', target);
const avd = avds.find((avd) => avd.id === target);
if (avd) {
debug('AVD found by ID: %s', avd.id);
const port = await findAvailableEmulatorPort(devices);
debug('Using emulator port: %d', port);
const device = await (0, emulator_1.runEmulator)(sdk, avd, port);
debug('Emulator ready, running avd: %s on %s', avd.id, device.serial);
return device;
}
}
exports.selectDeviceByTarget = selectDeviceByTarget;
async function selectHardwareDevice(devices) {
const hardwareDevices = devices.filter((d) => d.type === 'hardware');
// If a hardware device is found, we prefer launching to it instead of in an emulator.
if (hardwareDevices.length > 0) {
return hardwareDevices[0]; // TODO: can probably do better analysis on which to use?
}
}
exports.selectHardwareDevice = selectHardwareDevice;
async function selectVirtualDevice(sdk, devices, avds) {
const debug = Debug(`${modulePrefix}:${selectVirtualDevice.name}`);
const emulators = devices.filter((d) => d.type === 'emulator');
// If an emulator is running, use it.
if (emulators.length > 0) {
const [emulator] = emulators;
debug('Found running emulator: %s', emulator.serial);
return emulator;
}
throw new errors_1.AndroidRunException('No target devices/emulators available.', errors_1.ERR_NO_TARGET);
}
exports.selectVirtualDevice = selectVirtualDevice;
async function installApkToDevice(sdk, device, apk, appId) {
(0, log_1.log)(`Installing ${apk}...\n`);
try {
await (0, adb_1.installApk)(sdk, device, apk);
}
catch (e) {
if (e instanceof errors_1.ADBException) {
if (e.code === errors_1.ERR_INCOMPATIBLE_UPDATE || e.code === errors_1.ERR_VERSION_DOWNGRADE) {
(0, log_1.log)(`${e.message} Uninstalling and trying again...\n`);
await (0, adb_1.uninstallApp)(sdk, device, appId);
await (0, adb_1.installApk)(sdk, device, apk);
return;
}
}
throw e;
}
}
exports.installApkToDevice = installApkToDevice;

View file

@ -0,0 +1,47 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.findPackageBySchemaPath = exports.findPackageBySchema = exports.findUnsatisfiedPackages = exports.getAPILevels = void 0;
const Debug = require("debug");
const modulePrefix = 'native-run:android:utils:sdk:api';
async function getAPILevels(packages) {
const debug = Debug(`${modulePrefix}:${getAPILevels.name}`);
const levels = [
...new Set(packages.map((pkg) => pkg.apiLevel).filter((apiLevel) => typeof apiLevel !== 'undefined')),
].sort((a, b) => (a <= b ? 1 : -1));
const apis = levels.map((apiLevel) => ({
apiLevel,
packages: packages.filter((pkg) => pkg.apiLevel === apiLevel),
}));
debug('Discovered installed API Levels: %O', apis.map((api) => ({ ...api, packages: api.packages.map((pkg) => pkg.path) })));
return apis;
}
exports.getAPILevels = getAPILevels;
function findUnsatisfiedPackages(packages, schemas) {
return schemas.filter((pkg) => !findPackageBySchema(packages, pkg));
}
exports.findUnsatisfiedPackages = findUnsatisfiedPackages;
function findPackageBySchema(packages, pkg) {
const apiPkg = findPackageBySchemaPath(packages, pkg.path);
if (apiPkg) {
if (typeof pkg.version === 'string') {
if (pkg.version === apiPkg.version) {
return apiPkg;
}
}
else {
if (apiPkg.version.match(pkg.version)) {
return apiPkg;
}
}
}
}
exports.findPackageBySchema = findPackageBySchema;
function findPackageBySchemaPath(packages, path) {
return packages.find((pkg) => {
if (typeof path !== 'string') {
return !!pkg.path.match(path);
}
return path === pkg.path;
});
}
exports.findPackageBySchemaPath = findPackageBySchemaPath;

View file

@ -0,0 +1,169 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.supplementProcessEnv = exports.resolveAVDHome = exports.resolveEmulatorHome = exports.resolveSDKRoot = exports.getSDKPackage = exports.findAllSDKPackages = exports.getSDK = exports.SDK_DIRECTORIES = void 0;
const utils_fs_1 = require("@ionic/utils-fs");
const Debug = require("debug");
const os = require("os");
const pathlib = require("path");
const errors_1 = require("../../../errors");
const fs_1 = require("../../../utils/fs");
const xml_1 = require("./xml");
const modulePrefix = 'native-run:android:utils:sdk';
const homedir = os.homedir();
exports.SDK_DIRECTORIES = new Map([
['darwin', [pathlib.join(homedir, 'Library', 'Android', 'sdk')]],
['linux', [pathlib.join(homedir, 'Android', 'sdk')]],
['win32', [pathlib.join(process.env.LOCALAPPDATA || pathlib.join(homedir, 'AppData', 'Local'), 'Android', 'Sdk')]],
]);
async function getSDK() {
const root = await resolveSDKRoot();
const emulatorHome = await resolveEmulatorHome();
const avdHome = await resolveAVDHome();
return { root, emulatorHome, avdHome };
}
exports.getSDK = getSDK;
const pkgcache = new Map();
async function findAllSDKPackages(sdk) {
const debug = Debug(`${modulePrefix}:${findAllSDKPackages.name}`);
if (sdk.packages) {
return sdk.packages;
}
const sourcesRe = /^sources\/android-\d+\/.+\/.+/;
debug('Walking %s to discover SDK packages', sdk.root);
const contents = await (0, utils_fs_1.readdirp)(sdk.root, {
filter: (item) => pathlib.basename(item.path) === 'package.xml',
onError: (err) => debug('Error while walking SDK: %O', err),
walkerOptions: {
pathFilter: (p) => {
if ([
'bin',
'bin64',
'lib',
'lib64',
'include',
'clang-include',
'skins',
'data',
'examples',
'resources',
'systrace',
'extras',
// 'm2repository',
].includes(pathlib.basename(p))) {
return false;
}
if (p.match(sourcesRe)) {
return false;
}
return true;
},
},
});
sdk.packages = await Promise.all(contents.map((p) => pathlib.dirname(p)).map((p) => getSDKPackage(p)));
sdk.packages.sort((a, b) => (a.name >= b.name ? 1 : -1));
return sdk.packages;
}
exports.findAllSDKPackages = findAllSDKPackages;
async function getSDKPackage(location) {
const debug = Debug(`${modulePrefix}:${getSDKPackage.name}`);
let pkg = pkgcache.get(location);
if (!pkg) {
const packageXmlPath = pathlib.join(location, 'package.xml');
debug('Parsing %s', packageXmlPath);
try {
const packageXml = await (0, xml_1.readPackageXml)(packageXmlPath);
const name = (0, xml_1.getNameFromPackageXml)(packageXml);
const version = (0, xml_1.getVersionFromPackageXml)(packageXml);
const path = (0, xml_1.getPathFromPackageXml)(packageXml);
const apiLevel = (0, xml_1.getAPILevelFromPackageXml)(packageXml);
pkg = {
path,
location,
version,
name,
apiLevel,
};
}
catch (e) {
debug('Encountered error with %s: %O', packageXmlPath, e);
if (e.code === 'ENOENT') {
throw new errors_1.SDKException(`SDK package not found by location: ${location}.`, errors_1.ERR_SDK_PACKAGE_NOT_FOUND);
}
throw e;
}
pkgcache.set(location, pkg);
}
return pkg;
}
exports.getSDKPackage = getSDKPackage;
async function resolveSDKRoot() {
const debug = Debug(`${modulePrefix}:${resolveSDKRoot.name}`);
debug('Looking for $ANDROID_HOME');
// $ANDROID_HOME is deprecated, but still overrides $ANDROID_SDK_ROOT if
// defined and valid.
if (process.env.ANDROID_HOME && (await (0, fs_1.isDir)(process.env.ANDROID_HOME))) {
debug('Using $ANDROID_HOME at %s', process.env.ANDROID_HOME);
return process.env.ANDROID_HOME;
}
debug('Looking for $ANDROID_SDK_ROOT');
// No valid $ANDROID_HOME, try $ANDROID_SDK_ROOT.
if (process.env.ANDROID_SDK_ROOT && (await (0, fs_1.isDir)(process.env.ANDROID_SDK_ROOT))) {
debug('Using $ANDROID_SDK_ROOT at %s', process.env.ANDROID_SDK_ROOT);
return process.env.ANDROID_SDK_ROOT;
}
const sdkDirs = exports.SDK_DIRECTORIES.get(process.platform);
if (!sdkDirs) {
throw new errors_1.SDKException(`Unsupported platform: ${process.platform}`);
}
debug('Looking at following directories: %O', sdkDirs);
for (const sdkDir of sdkDirs) {
if (await (0, fs_1.isDir)(sdkDir)) {
debug('Using %s', sdkDir);
return sdkDir;
}
}
throw new errors_1.SDKException(`No valid Android SDK root found.`, errors_1.ERR_SDK_NOT_FOUND);
}
exports.resolveSDKRoot = resolveSDKRoot;
async function resolveEmulatorHome() {
const debug = Debug(`${modulePrefix}:${resolveEmulatorHome.name}`);
debug('Looking for $ANDROID_EMULATOR_HOME');
if (process.env.ANDROID_EMULATOR_HOME && (await (0, fs_1.isDir)(process.env.ANDROID_EMULATOR_HOME))) {
debug('Using $ANDROID_EMULATOR_HOME at %s', process.env.ANDROID_EMULATOR_HOME);
return process.env.ANDROID_EMULATOR_HOME;
}
debug('Looking at $HOME/.android');
const homeEmulatorHome = pathlib.join(homedir, '.android');
if (await (0, fs_1.isDir)(homeEmulatorHome)) {
debug('Using $HOME/.android/ at %s', homeEmulatorHome);
return homeEmulatorHome;
}
throw new errors_1.SDKException(`No valid Android Emulator home found.`, errors_1.ERR_EMULATOR_HOME_NOT_FOUND);
}
exports.resolveEmulatorHome = resolveEmulatorHome;
async function resolveAVDHome() {
const debug = Debug(`${modulePrefix}:${resolveAVDHome.name}`);
debug('Looking for $ANDROID_AVD_HOME');
if (process.env.ANDROID_AVD_HOME && (await (0, fs_1.isDir)(process.env.ANDROID_AVD_HOME))) {
debug('Using $ANDROID_AVD_HOME at %s', process.env.ANDROID_AVD_HOME);
return process.env.ANDROID_AVD_HOME;
}
debug('Looking at $HOME/.android/avd');
const homeAvdHome = pathlib.join(homedir, '.android', 'avd');
if (!(await (0, fs_1.isDir)(homeAvdHome))) {
debug('Creating directory: %s', homeAvdHome);
await (0, utils_fs_1.mkdirp)(homeAvdHome);
}
debug('Using $HOME/.android/avd/ at %s', homeAvdHome);
return homeAvdHome;
}
exports.resolveAVDHome = resolveAVDHome;
function supplementProcessEnv(sdk) {
return {
...process.env,
ANDROID_SDK_ROOT: sdk.root,
ANDROID_EMULATOR_HOME: sdk.emulatorHome,
ANDROID_AVD_HOME: sdk.avdHome,
};
}
exports.supplementProcessEnv = supplementProcessEnv;

View file

@ -0,0 +1,58 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getVersionFromPackageXml = exports.getNameFromPackageXml = exports.getPathFromPackageXml = exports.readPackageXml = exports.getAPILevelFromPackageXml = void 0;
const utils_fs_1 = require("@ionic/utils-fs");
const errors_1 = require("../../../errors");
function getAPILevelFromPackageXml(packageXml) {
var _a;
const apiLevel = packageXml.find('./localPackage/type-details/api-level');
return (_a = apiLevel === null || apiLevel === void 0 ? void 0 : apiLevel.text) === null || _a === void 0 ? void 0 : _a.toString();
}
exports.getAPILevelFromPackageXml = getAPILevelFromPackageXml;
async function readPackageXml(path) {
const et = await Promise.resolve().then(() => require('elementtree'));
const contents = await (0, utils_fs_1.readFile)(path, { encoding: 'utf8' });
const etree = et.parse(contents);
return etree;
}
exports.readPackageXml = readPackageXml;
function getPathFromPackageXml(packageXml) {
const localPackage = packageXml.find('./localPackage');
if (!localPackage) {
throw new errors_1.SDKException(`Invalid SDK package.`, errors_1.ERR_INVALID_SDK_PACKAGE);
}
const path = localPackage.get('path');
if (!path) {
throw new errors_1.SDKException(`Invalid SDK package path.`, errors_1.ERR_INVALID_SDK_PACKAGE);
}
return path.toString();
}
exports.getPathFromPackageXml = getPathFromPackageXml;
function getNameFromPackageXml(packageXml) {
const name = packageXml.find('./localPackage/display-name');
if (!(name === null || name === void 0 ? void 0 : name.text)) {
throw new errors_1.SDKException(`Invalid SDK package name.`, errors_1.ERR_INVALID_SDK_PACKAGE);
}
return name.text.toString();
}
exports.getNameFromPackageXml = getNameFromPackageXml;
function getVersionFromPackageXml(packageXml) {
const versionElements = [
packageXml.find('./localPackage/revision/major'),
packageXml.find('./localPackage/revision/minor'),
packageXml.find('./localPackage/revision/micro'),
];
const textFromElement = (e) => ((e === null || e === void 0 ? void 0 : e.text) ? e.text.toString() : '');
const versions = [];
for (const version of versionElements.map(textFromElement)) {
if (!version) {
break;
}
versions.push(version);
}
if (versions.length === 0) {
throw new errors_1.SDKException(`Invalid SDK package version.`, errors_1.ERR_INVALID_SDK_PACKAGE);
}
return versions.join('.');
}
exports.getVersionFromPackageXml = getVersionFromPackageXml;