appium-android-driver
Version:
Android UiAutomator and Chrome support for Appium
253 lines (230 loc) • 8.39 kB
JavaScript
import _ from 'lodash';
import {errors} from 'appium/driver';
import {util} from 'appium/support';
import B from 'bluebird';
const AIRPLANE_MODE_MASK = 0b001;
const WIFI_MASK = 0b010;
const DATA_MASK = 0b100;
const WIFI_KEY_NAME = 'wifi';
const DATA_KEY_NAME = 'data';
const AIRPLANE_MODE_KEY_NAME = 'airplaneMode';
const SUPPORTED_SERVICE_NAMES = /** @type {import('./types').ServiceType[]} */ ([
WIFI_KEY_NAME,
DATA_KEY_NAME,
AIRPLANE_MODE_KEY_NAME,
]);
/**
* @this {import('../driver').AndroidDriver}
* @returns {Promise<number>}
*/
export async function getNetworkConnection() {
this.log.info('Getting network connection');
let airplaneModeOn = await this.adb.isAirplaneModeOn();
let connection = airplaneModeOn ? AIRPLANE_MODE_MASK : 0;
// no need to check anything else if we are in airplane mode
if (!airplaneModeOn) {
let wifiOn = await this.isWifiOn();
connection |= wifiOn ? WIFI_MASK : 0;
let dataOn = await this.adb.isDataOn();
connection |= dataOn ? DATA_MASK : 0;
}
return connection;
}
/**
* @this {import('../driver').AndroidDriver}
* @returns {Promise<boolean>}
*/
export async function isWifiOn() {
return await this.adb.isWifiOn();
}
/**
* @since Android 12 (only real devices, emulators work in all APIs)
* @this {import('../driver').AndroidDriver}
* @param {boolean} [wifi] Either to enable or disable Wi-Fi.
* An unset value means to not change the state for the given service.
* @param {boolean} [data] Either to enable or disable mobile data connection.
* An unset value means to not change the state for the given service.
* @param {boolean} [airplaneMode] Either to enable to disable the Airplane Mode
* An unset value means to not change the state for the given service.
* @returns {Promise<void>}
*/
export async function mobileSetConnectivity(wifi, data, airplaneMode) {
if (_.every([wifi, data, airplaneMode], _.isUndefined)) {
throw new errors.InvalidArgumentError(
`Either one of ${JSON.stringify(SUPPORTED_SERVICE_NAMES)} options must be provided`,
);
}
const currentState = await this.mobileGetConnectivity(
/** @type {import('./types').ServiceType[]} */ ([
...(_.isUndefined(wifi) ? [] : [WIFI_KEY_NAME]),
...(_.isUndefined(data) ? [] : [DATA_KEY_NAME]),
...(_.isUndefined(airplaneMode) ? [] : [AIRPLANE_MODE_KEY_NAME]),
]),
);
/** @type {(Promise<any>|(() => Promise<any>))[]} */
const setters = [];
if (!_.isUndefined(wifi) && currentState.wifi !== Boolean(wifi)) {
setters.push(this.setWifiState(wifi));
}
if (!_.isUndefined(data) && currentState.data !== Boolean(data)) {
setters.push(this.setDataState(data));
}
if (!_.isUndefined(airplaneMode) && currentState.airplaneMode !== Boolean(airplaneMode)) {
setters.push(async () => {
await this.adb.setAirplaneMode(airplaneMode);
if ((await this.adb.getApiLevel()) < 30) {
await this.adb.broadcastAirplaneMode(airplaneMode);
}
});
}
if (!_.isEmpty(setters)) {
await B.all(setters);
}
}
/**
* @this {import('../driver').AndroidDriver}
* @param {import('./types').ServiceType[] | import('./types').ServiceType} [services] one or more
* services to get the connectivity for.
* @returns {Promise<import('./types').GetConnectivityResult>}
*/
export async function mobileGetConnectivity(services = SUPPORTED_SERVICE_NAMES) {
const svcs = _.castArray(services);
const unsupportedServices = _.difference(svcs, SUPPORTED_SERVICE_NAMES);
if (!_.isEmpty(unsupportedServices)) {
throw new errors.InvalidArgumentError(
`${util.pluralize(
'Service name',
unsupportedServices.length,
false,
)} ${unsupportedServices} ` +
`${
unsupportedServices.length === 1 ? 'is' : 'are'
} not known. Only the following services are ` +
`suported: ${SUPPORTED_SERVICE_NAMES}`,
);
}
const statePromises = {
wifi: B.resolve(svcs.includes(WIFI_KEY_NAME) ? this.adb.isWifiOn() : undefined),
data: B.resolve(svcs.includes(DATA_KEY_NAME) ? this.adb.isDataOn() : undefined),
airplaneMode: B.resolve(
svcs.includes(AIRPLANE_MODE_KEY_NAME) ? this.adb.isAirplaneModeOn() : undefined,
),
};
await B.all(_.values(statePromises));
return _.reduce(
statePromises,
(state, v, k) => _.isUndefined(v.value()) ? state : {...state, [k]: Boolean(v.value())},
{}
);
}
/**
* @since Android 12 (only real devices, emulators work in all APIs)
* @this {import('../driver').AndroidDriver}
* @param {number} type
* @returns {Promise<number>}
*/
export async function setNetworkConnection(type) {
this.log.info('Setting network connection');
// decode the input
const shouldEnableAirplaneMode = (type & AIRPLANE_MODE_MASK) !== 0;
const shouldEnableWifi = (type & WIFI_MASK) !== 0;
const shouldEnableDataConnection = (type & DATA_MASK) !== 0;
const currentState = await this.getNetworkConnection();
const isAirplaneModeEnabled = (currentState & AIRPLANE_MODE_MASK) !== 0;
const isWiFiEnabled = (currentState & WIFI_MASK) !== 0;
const isDataEnabled = (currentState & DATA_MASK) !== 0;
if (shouldEnableAirplaneMode !== isAirplaneModeEnabled) {
await this.adb.setAirplaneMode(shouldEnableAirplaneMode);
if ((await this.adb.getApiLevel()) < 30) {
await this.adb.broadcastAirplaneMode(shouldEnableAirplaneMode);
}
} else {
this.log.info(
`Not changing airplane mode, since it is already ${
shouldEnableAirplaneMode ? 'enabled' : 'disabled'
}`,
);
}
if (shouldEnableWifi === isWiFiEnabled && shouldEnableDataConnection === isDataEnabled) {
this.log.info(
'Not changing data connection/Wi-Fi states, since they are already set to expected values',
);
if (await this.adb.isAirplaneModeOn()) {
return AIRPLANE_MODE_MASK | currentState;
}
return ~AIRPLANE_MODE_MASK & currentState;
}
if (shouldEnableWifi !== isWiFiEnabled) {
await this.setWifiState(shouldEnableWifi);
} else {
this.log.info(
`Not changing Wi-Fi state, since it is already ` +
`${shouldEnableWifi ? 'enabled' : 'disabled'}`,
);
}
if (shouldEnableAirplaneMode) {
this.log.info('Not changing data connection state, because airplane mode is enabled');
} else if (shouldEnableDataConnection === isDataEnabled) {
this.log.info(
`Not changing data connection state, since it is already ` +
`${shouldEnableDataConnection ? 'enabled' : 'disabled'}`,
);
} else {
await this.setDataState(shouldEnableDataConnection);
}
return await this.getNetworkConnection();
}
/**
* @since Android 12 (only real devices, emulators work in all APIs)
* @this {import('../driver').AndroidDriver}
* @param {boolean} isOn
* @returns {Promise<void>}
*/
export async function setWifiState(isOn) {
await this.settingsApp.setWifiState(isOn, this.isEmulator());
}
/**
* @since Android 12 (only real devices, emulators work in all APIs)
* @this {import('../driver').AndroidDriver}
* @param {boolean} isOn
* @returns {Promise<void>}
*/
export async function setDataState(isOn) {
await this.settingsApp.setDataState(isOn, this.isEmulator());
}
/**
* @since Android 12 (only real devices, emulators work in all APIs)
* @this {import('../driver').AndroidDriver}
* @returns {Promise<void>}
*/
export async function toggleData() {
const isOn = await this.adb.isDataOn();
this.log.info(`Turning network data ${!isOn ? 'on' : 'off'}`);
await this.setDataState(!isOn);
}
/**
* @since Android 12 (only real devices, emulators work in all APIs)
* @this {import('../driver').AndroidDriver}
* @returns {Promise<void>}
*/
export async function toggleWiFi() {
const isOn = await this.adb.isWifiOn();
this.log.info(`Turning WiFi ${!isOn ? 'on' : 'off'}`);
await this.setWifiState(!isOn);
}
/**
* @since Android 12 (only real devices, emulators work in all APIs)
* @this {import('../driver').AndroidDriver}
* @returns {Promise<void>}
*/
export async function toggleFlightMode() {
let flightMode = !(await this.adb.isAirplaneModeOn());
this.log.info(`Turning flight mode ${flightMode ? 'on' : 'off'}`);
await this.adb.setAirplaneMode(flightMode);
if ((await this.adb.getApiLevel()) < 30) {
await this.adb.broadcastAirplaneMode(flightMode);
}
}
/**
* @typedef {import('appium-adb').ADB} ADB
*/