UNPKG

@u4/adbkit

Version:

A Typescript client for the Android Debug Bridge.

135 lines 5.51 kB
import xpath from 'xpath'; import { DOMParser } from '@xmldom/xmldom'; import { KeyCodesMap } from "./keycode.js"; import { Utils } from "../index.js"; export default class DeviceClientExtra { constructor(deviceClient) { this.deviceClient = deviceClient; } /** * rootless version of enable usb tethering * Depends of phone language will fail with non latin language. * @param enable */ async usbTethering(enable) { await this.keyCode(KeyCodesMap.KEYCODE_WAKEUP); await this.deviceClient.startActivity({ component: 'com.android.settings/.TetherSettings', wait: true }); const xml = await this.deviceClient.execOut('uiautomator dump /dev/tty', 'utf8'); const doc = new DOMParser().parseFromString(xml, "text/xml"); // https://gist.github.com/LeCoupa/8c305ec8c713aad07b14 const nodesRaw = xpath.select('//*[contains(@text,"USB")]/../..', doc); const nodes = Array.isArray(nodesRaw) ? nodesRaw.filter((n) => n instanceof Element) : []; if (!nodes.length) throw Error('can not find USB labeled node'); const switch_widget = xpath.select('./*/node[@class="android.widget.Switch"]', nodes[0]); if (!Array.isArray(switch_widget)) { throw Error('no switch on screen.'); } if (!switch_widget.length) throw Error('can not find android.widget.Switch linked to USB label'); const [checkBox] = switch_widget; // console.log(checkBox.toString()); const checked = checkBox.getAttribute('checked') === 'true'; const bounds = checkBox.getAttribute('bounds'); if (!bounds) throw Error('missing bounds attribut to checkbox'); if (checked === enable) { return false; } const m = bounds.match(/\[(\d+),(\d+)\]\[(\d+),(\d+)\]/); if (!m) throw Error('failed to parse Switch bounds'); const [, x1, y1] = m; // , x2, y2 await this.deviceClient.exec('input tap ' + x1 + ' ' + y1); return true; } /** * rootless version of enable air plain mode * Depends of phone language will fail with non latin language. * @param enable expected final stat for airplain mode * @param twiceMs if > 0 will switch airplan mode 2 time to match expected state */ async airPlainMode(enable, twiceMs) { // wake screen await this.keyCode(KeyCodesMap.KEYCODE_WAKEUP); await this.deviceClient.startActivity({ action: 'android.settings.AIRPLANE_MODE_SETTINGS', wait: true }); // await Utils.delay(100); let xml = await this.deviceClient.execOut('uiautomator dump /dev/tty', 'utf8'); xml = xml.replace('UI hierchary dumped to: /dev/tty', ''); // xml = xml.replace(/ ([a-z-]+)=""/g, ''); // xml = xml.replace(/ (checkable|clickable|content-desc|enabled|focused|focusable|index|long-clickable|package|password|resource-id|scrollable|selected)="[^"]*"/g, ''); const textFilter = (text) => text.toLowerCase(); const doc = new DOMParser().parseFromString(textFilter(xml), 'text/xml'); const all_switch_widget = xpath.select(textFilter('//*/node[@class="android.widget.Switch"]'), doc); if (!Array.isArray(all_switch_widget)) { throw Error('no switch on screen.'); } let theSwitch = null; if (!all_switch_widget.length) { throw Error('no switch on screen.'); } for (let i = 0; i < all_switch_widget.length; i++) { const nodes = xpath.select('../../*/node[contains(@text,"mode")]', all_switch_widget[i]); if (nodes.length) { theSwitch = all_switch_widget[i]; } } if (!theSwitch) { throw Error('can not find mode labeled node "mode avion" airPlainMode switch failed'); } // https://gist.github.com/LeCoupa/8c305ec8c713aad07b14 // "Airplane mode" const checked = theSwitch.getAttribute('checked') === 'true'; const bounds = theSwitch.getAttribute('bounds'); if (!bounds) throw Error('missing bounds attribut in switch'); if (!twiceMs && checked === enable) { return false; } const m = bounds.match(/\[(\d+),(\d+)\]\[(\d+),(\d+)\]/); if (!m) throw Error('failed to parse Switch bounds airPlainMode switch failed'); const [, x1, y1] = m; // , x2, y2 await this.tap(x1, y1); if (twiceMs && checked === enable) { await Utils.delay(twiceMs); await this.tap(x1, y1); } return true; } /** * enable / disable * type: bluetooth / data/ wifi */ async setSvc(type, enable) { const action = enable ? 'enable' : 'disable'; return this.deviceClient.execOut(`svc ${type} ${action}`, 'utf8'); } /** * Tap on screen * @param x1 * @param y1 * @returns */ async tap(x1, y1) { return this.deviceClient.execOut(`input tap ${x1} ${y1}`, 'utf8'); } /** * Tap a keyCode * @param key * @returns */ async keyCode(key) { return this.deviceClient.execOut(`input keyevent ${key}`, 'utf8'); } /** * press the back button * @returns */ async back() { return this.keyCode(KeyCodesMap.KEYCODE_BACK); } } //# sourceMappingURL=DeviceClientExtra.js.map