appium-adb
Version:
Android Debug Bridge interface
351 lines • 13.8 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.isLockManagementSupported = isLockManagementSupported;
exports.verifyLockCredential = verifyLockCredential;
exports.clearLockCredential = clearLockCredential;
exports.isLockEnabled = isLockEnabled;
exports.setLockCredential = setLockCredential;
exports.isScreenLocked = isScreenLocked;
exports.dismissKeyguard = dismissKeyguard;
exports.cycleWakeUp = cycleWakeUp;
exports.lock = lock;
exports.isShowingLockscreen = isShowingLockscreen;
exports.isScreenStateOff = isScreenStateOff;
const logger_1 = require("../logger");
const lodash_1 = __importDefault(require("lodash"));
const bluebird_1 = __importDefault(require("bluebird"));
const asyncbox_1 = require("asyncbox");
const CREDENTIAL_CANNOT_BE_NULL_OR_EMPTY_ERROR = `Credential can't be null or empty`;
const CREDENTIAL_DID_NOT_MATCH_ERROR = `didn't match`;
const SUPPORTED_LOCK_CREDENTIAL_TYPES = ['password', 'pin', 'pattern'];
const KEYCODE_POWER = 26;
const KEYCODE_WAKEUP = 224; // works over API Level 20
const HIDE_KEYBOARD_WAIT_TIME = 100;
/**
* @param verb
* @param oldCredential
* @param args
*/
function buildCommand(verb, oldCredential = null, ...args) {
const cmd = ['locksettings', verb];
if (oldCredential && !lodash_1.default.isEmpty(oldCredential)) {
cmd.push('--old', oldCredential);
}
if (!lodash_1.default.isEmpty(args)) {
cmd.push(...args);
}
return cmd;
}
/**
* Performs swipe up gesture on the screen
*
* @param windowDumpsys The output of `adb shell dumpsys window` command
* @throws {Error} If the display size cannot be retrieved
*/
async function swipeUp(windowDumpsys) {
const dimensionsMatch = /init=(\d+)x(\d+)/.exec(windowDumpsys);
if (!dimensionsMatch) {
throw new Error('Cannot retrieve the display size');
}
const displayWidth = parseInt(dimensionsMatch[1], 10);
const displayHeight = parseInt(dimensionsMatch[2], 10);
const x0 = displayWidth / 2;
const y0 = (displayHeight / 5) * 4;
const x1 = x0;
const y1 = displayHeight / 5;
await this.shell([
'input',
'touchscreen',
'swipe',
...[x0, y0, x1, y1].map((c) => `${Math.trunc(c)}`),
]);
}
/**
* Check whether the device supports lock settings management with `locksettings`
* command line tool. This tool has been added to Android toolset since API 27 Oreo
*
* @return True if the management is supported. The result is cached per ADB instance
*/
async function isLockManagementSupported() {
if (!lodash_1.default.isBoolean(this._isLockManagementSupported)) {
const passFlag = '__PASS__';
let output = '';
try {
output = await this.shell([`locksettings help && echo ${passFlag}`]);
}
catch { }
this._isLockManagementSupported = lodash_1.default.includes(output, passFlag);
logger_1.log.debug(`Extended lock settings management is ` +
`${this._isLockManagementSupported ? '' : 'not '}supported`);
}
return this._isLockManagementSupported;
}
/**
* Check whether the given credential is matches to the currently set one.
*
* @param credential - The credential value. It could be either
* pin, password or a pattern. A pattern is specified by a non-separated list
* of numbers that index the cell on the pattern in a 1-based manner in left
* to right and top to bottom order, i.e. the top-left cell is indexed with 1,
* whereas the bottom-right cell is indexed with 9. Example: 1234.
* null/empty value assumes the device has no lock currently set.
* @return True if the given credential matches to the device's one
* @throws {Error} If the verification faces an unexpected error
*/
async function verifyLockCredential(credential = null) {
try {
const { stdout, stderr } = await this.shell(buildCommand('verify', credential), {
outputFormat: this.EXEC_OUTPUT_FORMAT.FULL,
});
if (lodash_1.default.includes(stdout, 'verified successfully')) {
return true;
}
if ([`didn't match`, CREDENTIAL_CANNOT_BE_NULL_OR_EMPTY_ERROR].some((x) => lodash_1.default.includes(stderr || stdout, x))) {
return false;
}
throw new Error(stderr || stdout);
}
catch (e) {
throw new Error(`Device lock credential verification failed. ` +
`Original error: ${e.stderr ||
e.stdout ||
e.message}`);
}
}
/**
* Clears current lock credentials. Usually it takes several seconds for a device to
* sync the credential state after this method returns.
*
* @param credential - The credential value. It could be either
* pin, password or a pattern. A pattern is specified by a non-separated list
* of numbers that index the cell on the pattern in a 1-based manner in left
* to right and top to bottom order, i.e. the top-left cell is indexed with 1,
* whereas the bottom-right cell is indexed with 9. Example: 1234.
* null/empty value assumes the device has no lock currently set.
* @throws {Error} If operation faces an unexpected error
*/
async function clearLockCredential(credential = null) {
try {
const { stdout, stderr } = await this.shell(buildCommand('clear', credential), {
outputFormat: this.EXEC_OUTPUT_FORMAT.FULL,
});
if (!['user has no password', 'Lock credential cleared'].some((x) => lodash_1.default.includes(stderr || stdout, x))) {
throw new Error(stderr || stdout);
}
}
catch (e) {
throw new Error(`Cannot clear device lock credential. ` +
`Original error: ${e.stderr ||
e.stdout ||
e.message}`);
}
}
/**
* Checks whether the device is locked with a credential (either pin or a password
* or a pattern).
*
* @returns `true` if the device is locked
* @throws {Error} If operation faces an unexpected error
*/
async function isLockEnabled() {
try {
const { stdout, stderr } = await this.shell(buildCommand('get-disabled'), {
outputFormat: this.EXEC_OUTPUT_FORMAT.FULL,
});
if (/\bfalse\b/.test(stdout) ||
[CREDENTIAL_DID_NOT_MATCH_ERROR, CREDENTIAL_CANNOT_BE_NULL_OR_EMPTY_ERROR].some((x) => lodash_1.default.includes(stderr || stdout, x))) {
return true;
}
if (/\btrue\b/.test(stdout)) {
return false;
}
throw new Error(stderr || stdout);
}
catch (e) {
throw new Error(`Cannot check if device lock is enabled. Original error: ${e.message}`);
}
}
/**
* Sets the device lock.
*
* @param credentialType - One of: password, pin, pattern.
* @param credential - A non-empty credential value to be set.
* Make sure your new credential matches to the actual system security requirements,
* e.g. a minimum password length. A pattern is specified by a non-separated list
* of numbers that index the cell on the pattern in a 1-based manner in left
* to right and top to bottom order, i.e. the top-left cell is indexed with 1,
* whereas the bottom-right cell is indexed with 9. Example: 1234.
* @param oldCredential - An old credential string.
* It is only required to be set in case you need to change the current
* credential rather than to set a new one. Setting it to a wrong value will
* make this method to fail and throw an exception.
* @throws {Error} If there was a failure while verifying input arguments or setting
* the credential
*/
async function setLockCredential(credentialType, credential, oldCredential = null) {
if (!SUPPORTED_LOCK_CREDENTIAL_TYPES.includes(credentialType)) {
throw new Error(`Device lock credential type '${credentialType}' is unknown. ` +
`Only the following credential types are supported: ${SUPPORTED_LOCK_CREDENTIAL_TYPES}`);
}
if (lodash_1.default.isEmpty(credential) && !lodash_1.default.isInteger(credential)) {
throw new Error('Device lock credential cannot be empty');
}
const cmd = buildCommand(`set-${credentialType}`, oldCredential, credential);
try {
const { stdout, stderr } = await this.shell(cmd, {
outputFormat: this.EXEC_OUTPUT_FORMAT.FULL,
});
if (!lodash_1.default.includes(stdout, 'set to')) {
throw new Error(stderr || stdout);
}
}
catch (e) {
throw new Error(`Setting of device lock ${credentialType} credential failed. ` +
`Original error: ${e.stderr ||
e.stdout ||
e.message}`);
}
}
/**
* Retrieve the screen lock state of the device under test.
*
* @return True if the device is locked.
*/
async function isScreenLocked() {
const [windowOutput, powerOutput] = await bluebird_1.default.all([
this.shell(['dumpsys', 'window']),
this.shell(['dumpsys', 'power']),
]);
return (isShowingLockscreen(windowOutput) ||
isCurrentFocusOnKeyguard(windowOutput) ||
!isScreenOnFully(windowOutput) ||
isInDozingMode(powerOutput) ||
isScreenStateOff(windowOutput));
}
/**
* Dismisses keyguard overlay.
*/
async function dismissKeyguard() {
logger_1.log.info('Waking up the device to dismiss the keyguard');
// Screen off once to force pre-inputted text field clean after wake-up
// Just screen on if the screen defaults off
await this.cycleWakeUp();
if ((await this.getApiLevel()) > 21) {
await this.shell(['wm', 'dismiss-keyguard']);
return;
}
const stdout = await this.shell(['dumpsys', 'window']);
if (!isCurrentFocusOnKeyguard(stdout)) {
logger_1.log.debug('The keyguard seems to be inactive');
return;
}
logger_1.log.debug('Swiping up to dismiss the keyguard');
if (await this.hideKeyboard()) {
await bluebird_1.default.delay(HIDE_KEYBOARD_WAIT_TIME);
}
logger_1.log.debug('Dismissing notifications from the unlock view');
await this.shell(['service', 'call', 'notification', '1']);
await this.back();
await swipeUp.bind(this)(stdout);
}
/**
* Presses the corresponding key combination to make sure the device's screen
* is not turned off and is locked if the latter is enabled.
*/
async function cycleWakeUp() {
await this.keyevent(KEYCODE_POWER);
await this.keyevent(KEYCODE_WAKEUP);
}
/**
* Send the special keycode to the device under test in order to lock it.
*/
async function lock() {
if (await this.isScreenLocked()) {
logger_1.log.debug('Screen is already locked. Doing nothing.');
return;
}
logger_1.log.debug('Pressing the KEYCODE_POWER button to lock screen');
await this.keyevent(26);
const timeoutMs = 5000;
try {
await (0, asyncbox_1.waitForCondition)(async () => await this.isScreenLocked(), {
waitMs: timeoutMs,
intervalMs: 500,
});
}
catch {
throw new Error(`The device screen is still not locked after ${timeoutMs}ms timeout`);
}
}
// #region Private functions
/**
* Checks mScreenOnFully in dumpsys output to determine if screen is showing
* Default is true.
* Note: this key
*
* @param dumpsys
* @returns
*/
function isScreenOnFully(dumpsys) {
const m = /mScreenOnFully=\w+/gi.exec(dumpsys);
return (!m || // if information is missing we assume screen is fully on
(m && m.length > 0 && m[0].split('=')[1] === 'true') ||
false);
}
/**
* Checks mCurrentFocus in dumpsys output to determine if Keyguard is activated
*
* @param dumpsys
* @returns
*/
function isCurrentFocusOnKeyguard(dumpsys) {
const m = /mCurrentFocus.+Keyguard/gi.exec(dumpsys);
return Boolean(m?.length && m[0]);
}
/**
* Check the current device power state to determine if it is locked
*
* @param dumpsys The `adb shell dumpsys power` output
* @returns True if lock screen is shown
*/
function isInDozingMode(dumpsys) {
// On some phones/tablets we were observing mWakefulness=Dozing
// while on others it was getWakefulnessLocked()=Dozing
return /^[\s\w]+wakefulness[^=]*=Dozing$/im.test(dumpsys);
}
/**
* Checks mShowingLockscreen or mDreamingLockscreen in dumpsys output to determine
* if lock screen is showing
*
* A note: `adb shell dumpsys trust` performs better while detecting the locked screen state
* in comparison to `adb dumpsys window` output parsing.
* But the trust command does not work for `Swipe` unlock pattern.
*
* In some Android devices (Probably around Android 10+), `mShowingLockscreen` and `mDreamingLockscreen`
* do not work to detect lock status. Instead, keyguard preferences helps to detect the lock condition.
* Some devices such as Android TV do not have keyguard, so we should keep
* screen condition as this primary method.
*
* @param dumpsys - The output of dumpsys window command.
* @return True if lock screen is showing.
*/
function isShowingLockscreen(dumpsys) {
return (lodash_1.default.some(['mShowingLockscreen=true', 'mDreamingLockscreen=true'], (x) => dumpsys.includes(x)) ||
// `mIsShowing` and `mInputRestricted` are `true` in lock condition. `false` is unlock condition.
lodash_1.default.every([/KeyguardStateMonitor[\n\s]+mIsShowing=true/, /\s+mInputRestricted=true/], (x) => x.test(dumpsys)));
}
/**
* Checks screenState has SCREEN_STATE_OFF in dumpsys output to determine
* possible lock screen.
*
* @param dumpsys - The output of dumpsys window command.
* @return True if lock screen is showing.
*/
function isScreenStateOff(dumpsys) {
return /\s+screenState=SCREEN_STATE_OFF/i.test(dumpsys);
}
// #endregion
//# sourceMappingURL=lockmgmt.js.map