appium-android-driver
Version:
Android UiAutomator and Chrome support for Appium
128 lines (116 loc) • 4.49 kB
text/typescript
import {errors} from 'appium/driver';
import path from 'node:path';
import {fs, tempDir} from '@appium/support';
import crypto from 'node:crypto';
import _ from 'lodash';
import type {AndroidDriver} from '../driver';
const EMULATOR_RESOURCES_ROOT = ['emulator', 'resources'];
const CONFIG_NAME = 'Toren1BD.posters';
const DEFAULT_CONFIG_PAYLOAD_PREFIX = `poster wall
size 2 2
position -0.807 0.320 5.316
rotation 0 -150 0
default poster.png
poster table`;
const DEFAULT_CONFIG_PAYLOAD = `${DEFAULT_CONFIG_PAYLOAD_PREFIX}
size 1 1
position 0 0 -1.5
rotation 0 0 0
`;
const PNG_MAGIC = '89504e47';
const PNG_MAGIC_LENGTH = 4;
/**
* Updates the emulator configuration to show the
* image using the given properties when one opens the Camera app.
*
* @param sdkRoot ADB SDK root folder path
* @returns Promise that resolves to `true` if emulator services restart is needed
* to load the updated config or `false` if the current config is already up to date
* or does not need to be updated
*/
export async function prepareEmulatorForImageInjection(
this: AndroidDriver,
sdkRoot: string,
): Promise<boolean> {
const { injectedImageProperties: props } = this.opts;
if (!props) {
return false;
}
const size = `size ${props.size?.scaleX ?? 1} ${props.size?.scaleY ?? 1}`;
const position = `position ${props.position?.x ?? 0} ${props.position?.y ?? 0} ${props.position?.z ?? -1.5}`;
const rotation = `rotation ${props.rotation?.x ?? 0} ${props.rotation?.y ?? 0} ${props.rotation?.z ?? 0}`;
const strProps = `${size}\n${position}\n${rotation}`;
const configPath = path.resolve(sdkRoot, ...EMULATOR_RESOURCES_ROOT, CONFIG_NAME);
if (await fs.exists(configPath)) {
const configPayload = await fs.readFile(configPath, 'utf8');
if (configPayload.includes(strProps)) {
this.log.info(`The image injection config at '${configPath}' is already up to date. Doing nothing`);
return false;
}
const updatedPayload = `${DEFAULT_CONFIG_PAYLOAD_PREFIX}
${size}
${position}
${rotation}
`;
await fs.writeFile(configPath, updatedPayload, 'utf-8');
} else {
await fs.writeFile(configPath, DEFAULT_CONFIG_PAYLOAD, 'utf-8');
}
this.log.info(
`The image injection config at '${configPath}' has been successfully updated with ` +
`${size}, ${position}, ${rotation}. ` +
`Expecting further emulator restart to reload the changed config.`
);
return true;
}
/**
* Updates the emulator configuration to show the given
* image on the foreground when one opens the Camera app.
* It is expected that the rear camera of the emulator is
* configured to show VirtualScene for this feature to work.
* It is expected that the Virtual Scene posters config is
* already prepared and loaded either manually or using the
* `injectedImageProperties` capability.
*
* @param payload Base64-encoded payload of a .png image to be injected
* @returns Promise that resolves when the image is injected.
* @throws {Error} If called on a non-emulator device.
* @throws {errors.InvalidArgumentError} If the payload is not a valid base64-encoded PNG.
*/
export async function mobileInjectEmulatorCameraImage(
this: AndroidDriver,
payload: string,
): Promise<void> {
if (!this.isEmulator()) {
throw new Error('The image injection feature is only available on emulators');
}
if (!_.isString(payload) || _.size(payload) <= PNG_MAGIC_LENGTH) {
throw new errors.InvalidArgumentError(
`You must provide a valid base64-encoded .PNG data as the 'payload' argument`
);
}
const pngBuffer = Buffer.from(payload, 'base64');
const magic = pngBuffer.toString('hex', 0, PNG_MAGIC_LENGTH);
if (magic !== PNG_MAGIC) {
throw new errors.InvalidArgumentError(
'The provided image payload must contain a valid base64-encoded .PNG data'
);
}
const tmpImagePath = await tempDir.path({
// this is needed to avoid creation of multiple temp files for the same image payload
prefix: calculateImageHash(pngBuffer),
suffix: '.png',
});
await fs.writeFile(tmpImagePath, pngBuffer);
await this.adb.execEmuConsoleCommand(`virtualscene-image table ${tmpImagePath}`);
this.log.info(
`The provided image has been successully injected to the ${this.adb.curDeviceId} emulator`
);
}
// #region Internal helpers
function calculateImageHash(buffer: Buffer): string {
const hasher = crypto.createHash('sha1');
hasher.update(buffer);
return hasher.digest('hex');
}
// #endregion