UNPKG

io.appium.settings

Version:
193 lines 8.54 kB
"use strict"; 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