io.appium.settings
Version:
App for dealing with Android settings
193 lines • 8.54 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SettingsApp = void 0;
const logger_1 = require("./logger");
const lodash_1 = __importDefault(require("lodash"));
const asyncbox_1 = require("asyncbox");
const constants_js_1 = require("./constants.js");
const animation_1 = require("./commands/animation");
const bluetooth_1 = require("./commands/bluetooth");
const clipboard_1 = require("./commands/clipboard");
const geolocation_1 = require("./commands/geolocation");
const locale_1 = require("./commands/locale");
const media_1 = require("./commands/media");
const network_1 = require("./commands/network");
const notifications_1 = require("./commands/notifications");
const sms_1 = require("./commands/sms");
const typing_1 = require("./commands/typing");
const media_projection_1 = require("./commands/media-projection");
/**
* @typedef {Object} SettingsAppOpts
* @property {import('appium-adb').ADB} adb
*/
class SettingsApp {
/**
* @param {SettingsAppOpts} opts
*/
constructor(opts) {
this.setAnimationState = animation_1.setAnimationState;
this.setBluetoothState = bluetooth_1.setBluetoothState;
this.unpairAllBluetoothDevices = bluetooth_1.unpairAllBluetoothDevices;
this.getClipboard = clipboard_1.getClipboard;
this.setGeoLocation = geolocation_1.setGeoLocation;
this.getGeoLocation = geolocation_1.getGeoLocation;
this.refreshGeoLocationCache = geolocation_1.refreshGeoLocationCache;
this.listSupportedLocales = locale_1.listSupportedLocales;
this.setDeviceLocale = locale_1.setDeviceLocale;
this.scanMedia = media_1.scanMedia;
this.setDataState = network_1.setDataState;
this.setWifiState = network_1.setWifiState;
this.getNotifications = notifications_1.getNotifications;
this.adjustNotificationsPermissions = notifications_1.adjustNotificationsPermissions;
this.getSmsList = sms_1.getSmsList;
this.performEditorAction = typing_1.performEditorAction;
this.typeUnicode = typing_1.typeUnicode;
this.makeMediaProjectionRecorder = media_projection_1.makeMediaProjectionRecorder;
this.adjustMediaProjectionServicePermissions = media_projection_1.adjustMediaProjectionServicePermissions;
this.adb = opts.adb;
this.log = logger_1.log;
}
/**
* @typedef {Object} SettingsAppStartupOptions
* @property {number} [timeout=5000] The maximum number of milliseconds
* to wait until the app has started
* @property {boolean} [shouldRestoreCurrentApp=false] Whether to restore
* the activity which was the current one before Settings startup
* @property {boolean} [forceRestart=false] Whether to forcefully restart
* the Settings app if it is already running
*/
/**
* Ensures that Appium Settings helper application is running
* and starts it if necessary
*
* @param {SettingsAppStartupOptions} [opts={}]
* @throws {Error} If Appium Settings has failed to start
* @returns {Promise<SettingsApp>} self instance for chaining
*/
async requireRunning(opts = {}) {
const { timeout = 5000, shouldRestoreCurrentApp = false, forceRestart = false, } = opts;
if (forceRestart) {
await this.adb.forceStop(constants_js_1.SETTINGS_HELPER_ID);
}
else if (await this.isRunningInForeground()) {
return this;
}
this.log.debug(logger_1.LOG_PREFIX, 'Starting Appium Settings app');
let appPackage;
if (shouldRestoreCurrentApp) {
try {
({ appPackage } = await this.adb.getFocusedPackageAndActivity());
}
catch (e) {
this.log.warn(logger_1.LOG_PREFIX, `The current application can not be restored: ${e.message}`);
}
}
await this.adb.startApp({
pkg: constants_js_1.SETTINGS_HELPER_ID,
activity: constants_js_1.SETTINGS_HELPER_MAIN_ACTIVITY,
action: 'android.intent.action.MAIN',
category: 'android.intent.category.LAUNCHER',
stopApp: false,
waitForLaunch: false,
});
try {
await (0, asyncbox_1.waitForCondition)(async () => await this.isRunningInForeground(), {
waitMs: timeout,
intervalMs: 300,
});
if (shouldRestoreCurrentApp && appPackage) {
try {
await this.adb.activateApp(appPackage);
}
catch (e) {
logger_1.log.warn(`The current application can not be restored: ${e.message}`);
}
}
return this;
}
catch {
throw new Error(`Appium Settings app is not running after ${timeout}ms`);
}
}
/**
* If the io.appium.settings package has running foreground service.
* It returns the io.appium.settings's process existence for api level 25 and lower
* becase the concept of foreground services has only been introduced since API 26
*
* @throws {Error} If the method gets an error in the adb shell execution.
* @returns {Promise<boolean>} Return true if the device Settings app has a servicve running in foreground.
*/
async isRunningInForeground() {
if (await this.adb.getApiLevel() < 26) {
// The foreground service check is available since api level 26
return await this.adb.processExists(constants_js_1.SETTINGS_HELPER_ID);
}
// 'dumpsys activity services <package>' had slightly better performance
// than 'dumpsys activity services' and parsing the foreground apps.
const output = await this.adb.shell(['dumpsys', 'activity', 'services', constants_js_1.SETTINGS_HELPER_ID]);
return output.includes('isForeground=true');
}
/**
* Performs broadcast and verifies the result of it
*
* @param {string[]} args Arguments passed to the `am broadcast` comand
* @param {string} action The exception message in case of broadcast failure
* @param {boolean} [requireRunningApp=true] Whether to run a check for a running Appium Settings app
* @returns {Promise<string>}
*/
async checkBroadcast(args, action, requireRunningApp = true) {
if (requireRunningApp) {
await this.requireRunning({ shouldRestoreCurrentApp: true });
}
const output = await this.adb.shell([
'am', 'broadcast',
...args,
]);
if (!output.includes('result=-1')) {
this.log.debug(logger_1.LOG_PREFIX, output);
const error = new Error(`Cannot execute the '${action}' action. Check the logcat output for more details.`);
// @ts-ignore This is fine
error.output = output;
throw error;
}
return output;
}
/**
* Parses the output in JSON format retrieved from
* the corresponding Appium Settings broadcast calls
*
* @param {string} output The actual command output
* @param {string} entityName The name of the entity which is
* going to be parsed
* @returns {Object} The parsed JSON object
* @throws {Error} If the output cannot be parsed
* as a valid JSON
*/
_parseJsonData(output, entityName) {
if (!/\bresult=-1\b/.test(output) || !/\bdata="/.test(output)) {
this.log.debug(logger_1.LOG_PREFIX, output);
throw new Error(`Cannot retrieve ${entityName} from the device. ` +
'Check the server log for more details');
}
const match = /\bdata="(.+)",?/.exec(output);
if (!match) {
this.log.debug(logger_1.LOG_PREFIX, output);
throw new Error(`Cannot parse ${entityName} from the command output. ` +
'Check the server log for more details');
}
const jsonStr = lodash_1.default.trim(match[1]);
try {
return JSON.parse(jsonStr);
}
catch {
logger_1.log.debug(jsonStr);
throw new Error(`Cannot parse ${entityName} from the resulting data string. ` +
'Check the server log for more details');
}
}
}
exports.SettingsApp = SettingsApp;
//# sourceMappingURL=client.js.map