Skip to content

Commit

Permalink
feat: Use APIs imported from the io.appium.settings package (#897)
Browse files Browse the repository at this point in the history
  • Loading branch information
mykola-mokhnach authored Jan 11, 2024
1 parent 1c8536c commit 31c32f9
Show file tree
Hide file tree
Showing 12 changed files with 105 additions and 320 deletions.
17 changes: 8 additions & 9 deletions lib/commands/file-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,17 @@ function parseContainerPath(remotePath) {
* and adds matching items to the device's media library.
* Exceptions are ignored and written into the log.
*
* @param {ADB} adb ADB instance
* @this {import('../driver').AndroidDriver}
* @param {string} remotePath The file/folder path on the remote device
* @param {import('@appium/types').AppiumLogger} [log] Logger instance
*/
async function scanMedia(adb, remotePath, log) {
log?.debug(`Performing media scan of '${remotePath}'`);
async function scanMedia(remotePath) {
this.log.debug(`Performing media scan of '${remotePath}'`);
try {
// https://github.com/appium/appium/issues/16184
if ((await adb.getApiLevel()) >= 29) {
await adb.scanMedia(remotePath);
if ((await this.adb.getApiLevel()) >= 29) {
await this.settingsApp.scanMedia(remotePath);
} else {
await adb.shell([
await this.adb.shell([
'am',
'broadcast',
'-a',
Expand All @@ -60,7 +59,7 @@ async function scanMedia(adb, remotePath, log) {
} catch (e) {
const err = /** @type {any} */ (e);
// FIXME: what has a `stderr` prop?
log?.warn(
this.log.warn(
`Ignoring an unexpected error upon media scanning of '${remotePath}': ${
err.stderr ?? err.message
}`
Expand Down Expand Up @@ -183,7 +182,7 @@ const FileActionsMixin = {

// if we have pushed a file, it might be a media file, so ensure that
// apps know about it
await scanMedia(this.adb, remotePath, this.log);
await scanMedia.bind(this)(remotePath);
}
} finally {
if (await fs.exists(localFile)) {
Expand Down
9 changes: 4 additions & 5 deletions lib/commands/general.js
Original file line number Diff line number Diff line change
Expand Up @@ -301,21 +301,20 @@ const GeneralMixin = {
this.log.debug(`Parsed density value was NaN: "${out}"`);
}
// couldn't get anything, so error out
this.log.errorAndThrow('Failed to get display density property.');
throw new Error(); // unreachable
throw this.log.errorAndThrow('Failed to get display density property.');
},

async mobilePerformEditorAction(opts) {
const {action} = requireArgs('action', opts);
await this.adb.performEditorAction(action);
await this.settingsApp.performEditorAction(action);
},

async mobileGetNotifications() {
return await this.adb.getNotifications();
return await this.settingsApp.getNotifications();
},

async mobileListSms(opts) {
return await this.adb.getSmsList(opts);
return await this.settingsApp.getSmsList(opts);
},

async mobileUnlock(opts = {}) {
Expand Down
123 changes: 4 additions & 119 deletions lib/commands/media-projection.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,12 @@
// @ts-check

import {fs, net, tempDir, util} from '@appium/support';
import {waitForCondition} from 'asyncbox';
import B from 'bluebird';
import {fs, net, util} from '@appium/support';
import _ from 'lodash';
import moment from 'moment';
import path from 'node:path';
import {SETTINGS_HELPER_PKG_ID} from '../helpers';
import {mixin} from './mixins';

// https://github.com/appium/io.appium.settings#internal-audio--video-recording
const DEFAULT_EXT = '.mp4';
const RECORDING_STARTUP_TIMEOUT_MS = 3 * 1000;
const RECORDING_STOP_TIMEOUT_MS = 3 * 1000;
const MIN_API_LEVEL = 29;
const RECORDING_SERVICE_NAME = `${SETTINGS_HELPER_PKG_ID}/.recorder.RecorderService`;
const RECORDING_ACTIVITY_NAME = `${SETTINGS_HELPER_PKG_ID}/io.appium.settings.Settings`;
const RECORDING_ACTION_START = `${SETTINGS_HELPER_PKG_ID}.recording.ACTION_START`;
const RECORDING_ACTION_STOP = `${SETTINGS_HELPER_PKG_ID}.recording.ACTION_STOP`;
const RECORDINGS_ROOT = `/storage/emulated/0/Android/data/${SETTINGS_HELPER_PKG_ID}/files`;
const DEFAULT_FILENAME_FORMAT = 'YYYY-MM-DDTHH-mm-ss';

/**
Expand Down Expand Up @@ -82,109 +70,6 @@ async function verifyMediaProjectionRecordingIsSupported(adb) {
}
}

class MediaProjectionRecorder {
/**
* @param {ADB} adb
*/
constructor(adb) {
this.adb = adb;
}

async isRunning() {
const stdout = await this.adb.shell([
'dumpsys',
'activity',
'services',
RECORDING_SERVICE_NAME,
]);
return stdout.includes(RECORDING_SERVICE_NAME);
}

/**
*
* @param {import('./types').StartMediaProjectionRecordingOpts} opts
* @returns {Promise<boolean>}
*/
async start(opts = {}) {
if (await this.isRunning()) {
return false;
}

await this.cleanup();
const {filename, maxDurationSec, priority, resolution} = opts;
const args = ['am', 'start', '-n', RECORDING_ACTIVITY_NAME, '-a', RECORDING_ACTION_START];
if (filename) {
args.push('--es', 'filename', filename);
}
if (maxDurationSec) {
args.push('--es', 'max_duration_sec', `${maxDurationSec}`);
}
if (priority) {
args.push('--es', 'priority', priority);
}
if (resolution) {
args.push('--es', 'resolution', resolution);
}
await this.adb.shell(args);
await new B((resolve, reject) => {
setTimeout(async () => {
if (!(await this.isRunning())) {
return reject(
new Error(
`The media projection recording is not running after ${RECORDING_STARTUP_TIMEOUT_MS}ms. ` +
`Please check the logcat output for more details.`
)
);
}
resolve();
}, RECORDING_STARTUP_TIMEOUT_MS);
});
return true;
}

async cleanup() {
await this.adb.shell([`rm -f ${RECORDINGS_ROOT}/*`]);
}

async pullRecent() {
const recordings = await this.adb.ls(RECORDINGS_ROOT, ['-tr']);
if (_.isEmpty(recordings)) {
return null;
}

const dstPath = path.join(await tempDir.openDir(), recordings[0]);
// increase timeout to 5 minutes because it might take a while to pull a large video file
await this.adb.pull(`${RECORDINGS_ROOT}/${recordings[0]}`, dstPath, {timeout: 300000});
return dstPath;
}

async stop() {
if (!(await this.isRunning())) {
return false;
}

await this.adb.shell([
'am',
'start',
'-n',
RECORDING_ACTIVITY_NAME,
'-a',
RECORDING_ACTION_STOP,
]);
try {
await waitForCondition(async () => !(await this.isRunning()), {
waitMs: RECORDING_STOP_TIMEOUT_MS,
intervalMs: 500,
});
} catch (e) {
throw new Error(
`The attempt to stop the current media projection recording timed out after ` +
`${RECORDING_STOP_TIMEOUT_MS}ms`
);
}
return true;
}
}
/**
* @type {import('./mixins').MediaProjectionMixin & ThisType<import('../driver').AndroidDriver>}
* @satisfies {import('@appium/types').ExternalDriver}
Expand All @@ -194,7 +79,7 @@ const MediaProjectionMixin = {
await verifyMediaProjectionRecordingIsSupported(this.adb);

const {resolution, priority, maxDurationSec, filename} = options;
const recorder = new MediaProjectionRecorder(this.adb);
const recorder = this.settingsApp.makeMediaProjectionRecorder();
const fname = adjustMediaExtension(filename || moment().format(DEFAULT_FILENAME_FORMAT));
const didStart = await recorder.start({
resolution,
Expand All @@ -215,14 +100,14 @@ const MediaProjectionMixin = {
async mobileIsMediaProjectionRecordingRunning() {
await verifyMediaProjectionRecordingIsSupported(this.adb);

const recorder = new MediaProjectionRecorder(this.adb);
const recorder = this.settingsApp.makeMediaProjectionRecorder();
return await recorder.isRunning();
},

async mobileStopMediaProjectionRecording(options = {}) {
await verifyMediaProjectionRecordingIsSupported(this.adb);

const recorder = new MediaProjectionRecorder(this.adb);
const recorder = this.settingsApp.makeMediaProjectionRecorder();
if (await recorder.stop()) {
this.log.info(
'Successfully stopped a media projection recording. Pulling the recorded media'
Expand Down
1 change: 1 addition & 0 deletions lib/commands/mixins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,7 @@ export interface NetworkMixin {
* decoupling to override behaviour in other drivers like UiAutomator2.
*/
setWifiState(state: boolean): Promise<void>;
setDataState(state: boolean): Promise<void>;
toggleData(): Promise<void>;
toggleWiFi(): Promise<void>;
toggleFlightMode(): Promise<void>;
Expand Down
34 changes: 18 additions & 16 deletions lib/commands/network.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// @ts-check

import {mixin} from './mixins';
import _ from 'lodash';
import {errors} from 'appium/driver';
Expand Down Expand Up @@ -67,10 +65,10 @@ const NetworkMixin = {
/** @type {(Promise<any>|(() => Promise<any>))[]} */
const setters = [];
if (!_.isUndefined(wifi) && currentState.wifi !== Boolean(wifi)) {
setters.push(this.adb.setWifiState(wifi, this.isEmulator()));
setters.push(this.setWifiState(wifi));
}
if (!_.isUndefined(data) && currentState.data !== Boolean(data)) {
setters.push(this.adb.setDataState(data, this.isEmulator()));
setters.push(this.setDataState(data));
}
if (!_.isUndefined(airplaneMode) && currentState.airplaneMode !== Boolean(airplaneMode)) {
setters.push(async () => {
Expand Down Expand Up @@ -170,26 +168,30 @@ const NetworkMixin = {
`${shouldEnableDataConnection ? 'enabled' : 'disabled'}`
);
} else {
await this.adb.setDataState(shouldEnableDataConnection, this.isEmulator());
await this.setDataState(shouldEnableDataConnection);
}

return await this.getNetworkConnection();
},

async setWifiState(wifi) {
await this.adb.setWifiState(wifi, this.isEmulator());
async setWifiState(isOn) {
await this.settingsApp.setWifiState(isOn, this.isEmulator());
},

async setDataState(isOn) {
await this.settingsApp.setDataState(isOn, this.isEmulator());
},

async toggleData() {
let data = !(await this.adb.isDataOn());
this.log.info(`Turning network data ${data ? 'on' : 'off'}`);
await this.adb.setWifiAndData({data}, this.isEmulator());
const isOn = await this.adb.isDataOn();
this.log.info(`Turning network data ${!isOn ? 'on' : 'off'}`);
await this.setDataState(!isOn);
},

async toggleWiFi() {
let wifi = !(await this.adb.isWifiOn());
this.log.info(`Turning WiFi ${wifi ? 'on' : 'off'}`);
await this.adb.setWifiAndData({wifi}, this.isEmulator());
const isOn = await this.adb.isWifiOn();
this.log.info(`Turning WiFi ${!isOn ? 'on' : 'off'}`);
await this.setWifiState(!isOn);
},

async toggleFlightMode() {
Expand All @@ -206,7 +208,7 @@ const NetworkMixin = {
},

async setGeoLocation(location) {
await this.adb.setGeoLocation(location, this.isEmulator());
await this.settingsApp.setGeoLocation(location, this.isEmulator());
try {
return await this.getGeoLocation();
} catch (e) {
Expand All @@ -224,11 +226,11 @@ const NetworkMixin = {

async mobileRefreshGpsCache(opts = {}) {
const {timeoutMs} = opts;
await this.adb.refreshGeoLocationCache(timeoutMs);
await this.settingsApp.refreshGeoLocationCache(timeoutMs);
},

async getGeoLocation() {
const {latitude, longitude, altitude} = await this.adb.getGeoLocation();
const {latitude, longitude, altitude} = await this.settingsApp.getGeoLocation();
return {
latitude: parseFloat(String(latitude)) || GEO_EPSILON,
longitude: parseFloat(String(longitude)) || GEO_EPSILON,
Expand Down
10 changes: 10 additions & 0 deletions lib/driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {BaseDriver} from 'appium/driver';
import ANDROID_DRIVER_CONSTRAINTS, {AndroidDriverConstraints} from './constraints';
import {helpers} from './helpers';
import {newMethodMap} from './method-map';
import { SettingsApp } from 'io.appium.settings';

export type AndroidDriverCaps = DriverCaps<AndroidDriverConstraints>;
export type W3CAndroidDriverCaps = W3CDriverCaps<AndroidDriverConstraints>;
Expand All @@ -31,6 +32,8 @@ class AndroidDriver

adb: ADB;

_settingsApp: SettingsApp;

unlocker: typeof helpers.unlocker;

apkStrings: StringRecord<StringRecord<string>>;
Expand Down Expand Up @@ -75,6 +78,13 @@ class AndroidDriver
this.opts = opts as AndroidDriverOpts;
}

get settingsApp() {
if (!this._settingsApp) {
this._settingsApp = new SettingsApp({adb: this.adb});
}
return this._settingsApp;
}

isEmulator() {
return helpers.isEmulator(this.adb, this.opts);
}
Expand Down
Loading

0 comments on commit 31c32f9

Please sign in to comment.