@u4/adbkit
Version:
A Typescript client for the Android Debug Bridge.
557 lines • 22.9 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs_1 = __importDefault(require("fs"));
const promise_duplex_1 = __importDefault(require("promise-duplex"));
const stream_1 = require("stream");
const ThirdUtils_1 = __importDefault(require("../ThirdUtils"));
const STF = __importStar(require("./STFServiceModel"));
// import * as STFAg from "./STFAgentModel";
const protobufjs_1 = require("protobufjs");
const STFServiceBuf_1 = __importDefault(require("./STFServiceBuf"));
const utils_1 = __importDefault(require("../../utils"));
// const debug = Debug('STFService');
const PKG = 'jp.co.cyberagent.stf';
class STFService extends stream_1.EventEmitter {
constructor(client, options = {}) {
super();
this.client = client;
this.on = (event, listener) => super.on(event, listener);
this.off = (event, listener) => super.off(event, listener);
this.once = (event, listener) => super.once(event, listener);
this.emit = (event, ...args) => super.emit(event, ...args);
this._cachedApkPath = '';
/**
* esponce callback hooks
*/
this.responseHook = {};
/**
* request id counter [1..0xFFFFFF]
*/
this.reqCnt = 1;
this.config = {
timeout: 15000,
noInstall: false,
...options,
};
this._maxContact = new Promise((resolve) => this.setMaxContact = resolve);
this._width = new Promise((resolve) => this.setWidth = resolve);
this._height = new Promise((resolve) => this.setHeight = resolve);
this._maxPressure = new Promise((resolve) => this.setMaxPressure = resolve);
}
get maxContact() { return this._maxContact; }
get width() { return this._width; }
get height() { return this._height; }
get maxPressure() { return this._maxPressure; }
/**
* find the APK and install it
*/
async installApk(version) {
const apk = ThirdUtils_1.default.getResourcePath(`STFService_${version}.apk`);
try {
await fs_1.default.promises.stat(apk);
}
catch (e) {
throw Error(`can not find APK bin/STFService_${version}.apk Err: ${JSON.stringify(e)}`);
}
this._cachedApkPath = '';
return this.client.install(apk);
}
/**
*
* @returns get agent setup path
*/
async getApkPath() {
if (this._cachedApkPath)
return this._cachedApkPath;
/**
* locate the installed apk file
*/
let setupPath = (await this.client.execOut(`pm path ${PKG}`, 'utf8')).trim();
if (!setupPath.startsWith('package:')) {
return '';
// throw new Error(`Failed to find ${PKG} package path`);
}
setupPath = setupPath.substring(8);
this._cachedApkPath = setupPath;
return setupPath;
}
/**
* get the current installed Agent version number
* @returns 'MISSING' if not installed, 'OK' if expected, 'MISMATCH' if version differ
*/
async checkVersion(version) {
const setupPath = await this.getApkPath();
const getVersion = `export CLASSPATH='${setupPath}'; exec app_process /system/bin '${PKG}.Agent' --version 2>/dev/null`;
const currentVersion = await this.client.execOut(getVersion, 'utf8');
if (!currentVersion)
return 'MISSING';
if (currentVersion.trim() !== version) {
return 'MISMATCH';
}
return 'OK';
}
/**
* start agent
*/
async startAgent() {
const setupPath = await this.getApkPath();
const startAgent = `export CLASSPATH='${setupPath}'; exec app_process /system/bin '${PKG}.Agent' 2>&1`;
const agentProcess = new promise_duplex_1.default(await this.client.exec(startAgent));
const result = await utils_1.default.waitforText(agentProcess, /|Address already in use/, 10000);
if (result.includes("@stfagent"))
return; // started
// console.log(`${this.client.serial} stfagent already running`);
// debug only
// ThirdUtils.dumpReadable(agentProcess, 'STFagent');
}
/**
* start long running service and keep the duplex opened
*/
async startService() {
const props = await this.client.getProperties();
const action = `${PKG}.ACTION_START`;
const component = `${PKG}/.Service`;
const sdkLevel = parseInt(props['ro.build.version.sdk']);
const startServiceCmd = (sdkLevel < 26) ? 'startservice' : 'start-foreground-service';
const duplex = new promise_duplex_1.default(await this.client.shell(`am ${startServiceCmd} --user 0 -a '${action}' -n '${component}'`));
await utils_1.default.waitforReadable(duplex);
const msg = await duplex.setEncoding('utf8').readAll();
if (msg.includes('Error')) {
throw Error(msg.trim());
}
}
/**
* uninstall the service
*/
async uninstall() {
await this.client.uninstall(PKG);
await this.client.execOut('rm -f /data/local/tmp/minicap /data/local/tmp/minicap.so /data/local/tmp/minitouch /data/local/tmp/minirev');
return true;
}
async start() {
this.protoSrv = await STFServiceBuf_1.default.get();
if (!this.config.noInstall) {
const versionStatus = await this.checkVersion('2.4.9');
if (versionStatus === 'MISMATCH') {
await this.uninstall();
}
if (versionStatus !== 'OK') {
await this.installApk('2.4.9');
}
}
await this.startService();
await this.startAgent();
this.servicesSocket = await this.client.openLocal2('localabstract:stfservice');
this.servicesSocket.once('close').then(() => this.stop());
void this.startServiceStream().catch((e) => { console.log('Service failed', e); this.stop(); });
return this;
}
/**
* get minitouch duplex, if not connected open connexion
*/
async getMinitouchSocket() {
if (this._minitouchagent)
return this._minitouchagent;
this._minitouchagent = this.client.openLocal2('localabstract:minitouchagent');
const socket = await this._minitouchagent;
socket.once('close').then(() => {
this._minitouchagent = undefined;
// console.log('getMinitouchSocket just closed');
});
void this.startMinitouchStream(socket).catch(() => { socket.destroy(); });
return socket;
}
async getAgentSocket() {
if (this._agentSocket)
return this._agentSocket;
this._agentSocket = this.client.openLocal2('localabstract:stfagent');
const socket = await this._agentSocket;
socket.once('close').then(() => {
this._agentSocket = undefined;
// console.log('agentSocket just closed');
});
void this.startAgentStream(socket).catch(() => { socket.destroy(); });
return this._agentSocket;
}
async startServiceStream() {
let buffer = null;
for (;;) {
await utils_1.default.waitforReadable(this.servicesSocket);
if (!this.servicesSocket)
return;
const next = await this.servicesSocket.read();
if (!next)
continue;
if (buffer) {
buffer = Buffer.concat([buffer, next]);
}
else {
buffer = next;
}
while (buffer) {
const reader = protobufjs_1.Reader.create(buffer);
const envelopLen = reader.uint32();
const bufLen = envelopLen + reader.pos;
// need mode data to complet envelop
if (buffer.length < envelopLen)
break;
let chunk;
if (bufLen === buffer.length) {
// chunk len match Envelop len should speedup parsing, depending on nodeJS internal Buffer implementation, need to check Buffer.subarray implementation
chunk = buffer.subarray(reader.pos);
buffer = null;
}
else {
chunk = buffer.subarray(reader.pos, bufLen);
buffer = buffer.subarray(bufLen);
}
try {
const eventObj = this.protoSrv.readEnvelope(chunk);
const { id, message } = eventObj;
if (id) {
const resolv = this.responseHook[id];
if (resolv) {
delete this.responseHook[id];
resolv(message);
}
else {
console.error(`STFService RCV response to unknown QueryId:${id} Type:${eventObj.type}`);
}
continue;
}
switch (eventObj.type) {
case STF.MessageType.EVENT_AIRPLANE_MODE:
this.emit("airplaneMode", this.protoSrv.read.AirplaneModeEvent(message));
break;
case STF.MessageType.EVENT_BATTERY:
this.emit("battery", this.protoSrv.read.BatteryEvent(message));
break;
case STF.MessageType.EVENT_CONNECTIVITY:
this.emit("connectivity", this.protoSrv.read.ConnectivityEvent(message));
break;
case STF.MessageType.EVENT_ROTATION:
this.emit("rotation", this.protoSrv.read.RotationEvent(message));
break;
case STF.MessageType.EVENT_PHONE_STATE:
this.emit("phoneState", this.protoSrv.read.PhoneStateEvent(message));
break;
case STF.MessageType.EVENT_BROWSER_PACKAGE:
this.emit("browerPackage", this.protoSrv.read.BrowserPackageEvent(message));
break;
default: console.error(`STFService Response Type (${eventObj.type}) is not implemented`);
}
}
catch (e) {
if (chunk)
console.error(chunk.toString('hex'));
console.error(e);
}
}
await utils_1.default.delay(0);
}
}
/**
* RCV banne:
* v 1
* ^ %d %d %d %d DEFAULT_MAX_CONTACTS, width, height, DEFAULT_MAX_PRESSURE;
* @param socket
*/
async startMinitouchStream(socket) {
socket.setEncoding('ascii');
let data = '';
for (;;) {
await utils_1.default.waitforReadable(socket);
const chunk = await socket.read();
if (!chunk)
return;
data = data + chunk;
for (;;) {
const p = data.indexOf('\n');
if (p >= 0)
break;
const line = data.substring(0, p);
data = data.substring(p + 1);
if (line.startsWith('v 1'))
continue;
if (line.startsWith('^')) {
const [, mc, w, h, mp] = line.split(/ /);
this.setMaxContact(Number(mc));
this.setWidth(Number(w));
this.setHeight(Number(h));
this.setMaxPressure(Number(mp));
continue;
}
console.error('minitouchSocket RCV chunk len:', line);
}
await utils_1.default.delay(0);
}
}
async startAgentStream(socket) {
for (;;) {
await utils_1.default.waitforReadable(socket);
const chunk = await socket.read();
if (chunk) {
console.log('agentSocket RCV chunk len:', chunk.length, chunk.toString('hex').substring(0, 80));
}
else {
return;
}
await utils_1.default.delay(0);
}
}
pushService(type, message, requestReader) {
const id = (this.reqCnt + 1) | 0xFFFFFF;
this.reqCnt = id;
const envelope = { type, message, id };
let pReject;
const promise = new Promise((resolve, reject) => {
pReject = reject;
this.responseHook[id] = (message) => {
if (requestReader) {
const conv = requestReader(message);
resolve(conv);
}
else {
resolve();
}
};
});
const buf = this.protoSrv.write.Envelope(envelope);
if (!this.servicesSocket)
throw Error('servicesSocket is not open');
this.servicesSocket.write(buf);
const timeout = utils_1.default.delay(this.config.timeout).then(() => {
if (this.responseHook[id]) {
delete this.responseHook[id];
pReject(Error('timeout'));
}
});
Promise.race([promise, timeout]);
return promise;
}
/**
* Generic method to push message to agent
*/
async pushAgent(type, message) {
const envelope = { type, message };
// const buf = this.protoAgent.write.Envelope(envelope)
const buf = this.protoSrv.write.Envelope(envelope);
const socket = await this.getAgentSocket();
return socket.write(buf);
}
////////////////////////////
// public methods
async getAccounts(type) {
const message = this.protoSrv.write.GetAccountsRequest({ type });
return this.pushService(STF.MessageType.GET_ACCOUNTS, message, this.protoSrv.read.GetAccountsResponse);
}
async getBrowsers(req = {}) {
const message = this.protoSrv.write.GetBrowsersRequest(req);
return this.pushService(STF.MessageType.GET_BROWSERS, message, this.protoSrv.read.GetBrowsersResponse);
}
async getClipboard(type = STF.ClipboardType.TEXT) {
const message = this.protoSrv.write.GetClipboardRequest({ type });
return this.pushService(STF.MessageType.GET_CLIPBOARD, message, this.protoSrv.read.GetClipboardResponse);
}
async getDisplay(id = 0) {
const message = this.protoSrv.write.GetDisplayRequest({ id });
return this.pushService(STF.MessageType.GET_DISPLAY, message, this.protoSrv.read.GetDisplayResponse);
}
async getProperties(properties) {
const message = this.protoSrv.write.GetPropertiesRequest({ properties });
return this.pushService(STF.MessageType.GET_PROPERTIES, message, this.protoSrv.read.GetPropertiesResponse);
}
async getRingerMode(req = {}) {
const message = this.protoSrv.write.GetRingerModeRequest(req);
return this.pushService(STF.MessageType.GET_RINGER_MODE, message, this.protoSrv.read.GetRingerModeResponse);
}
async getSdStatus(req = {}) {
const message = this.protoSrv.write.GetSdStatusRequest(req);
return this.pushService(STF.MessageType.GET_SD_STATUS, message, this.protoSrv.read.GetSdStatusResponse);
}
// invalid response send by the service
// public async getVersion(): Promise<STF.GetVersionResponse> {
// const message = this.proto.write.GetVersionRequest();
// return this.pushEnvelop<STF.GetVersionResponse>(STF.MessageType.GET_VERSION, message })
// }
async getWifiStatus(req = {}) {
const message = this.protoSrv.write.GetWifiStatusRequest(req);
return this.pushService(STF.MessageType.GET_WIFI_STATUS, message, this.protoSrv.read.GetWifiStatusResponse);
}
async getBluetoothStatus(req = {}) {
const message = this.protoSrv.write.GetBluetoothStatusRequest(req);
return this.pushService(STF.MessageType.GET_BLUETOOTH_STATUS, message, this.protoSrv.read.GetBluetoothStatusResponse);
}
async getRootStatus(req = {}) {
const message = this.protoSrv.write.GetRootStatusRequest(req);
return this.pushService(STF.MessageType.GET_ROOT_STATUS, message, this.protoSrv.read.GetRootStatusResponse);
}
async setClipboard(req) {
const message = this.protoSrv.write.SetClipboardRequest(req);
return this.pushService(STF.MessageType.SET_CLIPBOARD, message, this.protoSrv.read.SetClipboardResponse);
}
async setKeyguardState(req) {
const message = this.protoSrv.write.SetKeyguardStateRequest(req);
return this.pushService(STF.MessageType.SET_KEYGUARD_STATE, message, this.protoSrv.read.SetKeyguardStateResponse);
}
async setRingerMode(req) {
const message = this.protoSrv.write.SetRingerModeRequest(req);
return this.pushService(STF.MessageType.SET_RINGER_MODE, message, this.protoSrv.read.SetRingerModeResponse);
}
async setRotationRequest(req) {
const message = this.protoSrv.write.SetRotationRequest(req);
return this.pushService(STF.MessageType.SET_ROTATION, message);
}
async setWakeLock(req) {
const message = this.protoSrv.write.SetWakeLockRequest(req);
return this.pushService(STF.MessageType.SET_WAKE_LOCK, message, this.protoSrv.read.GetWifiStatusResponse);
}
async setWifiEnabledRequest(req) {
const message = this.protoSrv.write.SetWifiEnabledRequest(req);
return this.pushService(STF.MessageType.SET_WIFI_ENABLED, message, this.protoSrv.read.SetWifiEnabledResponse);
}
async setBluetoothEnabledRequest(req) {
const message = this.protoSrv.write.SetBluetoothEnabledRequest(req);
return this.pushService(STF.MessageType.SET_BLUETOOTH_ENABLED, message, this.protoSrv.read.SetBluetoothEnabledResponse);
}
async setMasterMute(req) {
const message = this.protoSrv.write.SetMasterMuteRequest(req);
return this.pushService(STF.MessageType.SET_MASTER_MUTE, message, this.protoSrv.read.SetMasterMuteResponse);
}
// Agents
async doKeyEvent(req) {
const message = this.protoSrv.write.KeyEventRequest(req);
return this.pushAgent(STF.MessageType.DO_KEYEVENT, message);
}
async doType(req) {
const message = this.protoSrv.write.DoTypeRequest(req);
return this.pushAgent(STF.MessageType.DO_TYPE, message);
}
async doWake(req) {
const message = this.protoSrv.write.DoWakeRequest(req);
return this.pushAgent(STF.MessageType.DO_WAKE, message);
}
async setRotation(req) {
const message = this.protoSrv.write.SetRotationRequest(req);
return this.pushAgent(STF.MessageType.SET_ROTATION, message);
}
/**
* Send commit minitouch events
*/
async commit() {
const cmd = `c\n`;
const s = await this.getMinitouchSocket();
return s.write(cmd, 'ascii');
}
/**
* Send move minitouch events
*/
async move(x, y, contact = 0, pressure = 0) {
const cmd = `m ${contact | 0} ${x | 0} ${y | 0} ${pressure | 0}\n`;
const s = await this.getMinitouchSocket();
return s.write(cmd, 'ascii');
}
/**
* Send press down minitouch events
*/
async down(x, y, contact = 0, pressure = 0) {
const cmd = `d ${contact} ${x | 0} ${y | 0} ${pressure | 0}\n`;
const s = await this.getMinitouchSocket();
return s.write(cmd, 'ascii');
}
/**
* Send press up minitouch events
*/
async up(contact = 0) {
const cmd = `u ${contact | 0}\n`;
const s = await this.getMinitouchSocket();
return s.write(cmd, 'ascii');
}
/**
* Send move + commit minitouch events
*/
async moveCommit(x, y, contact = 0, pressure = 0) {
const cmd = `m ${contact | 0} ${x | 0} ${y | 0} ${pressure | 0}\nc\n`;
const s = await this.getMinitouchSocket();
return s.write(cmd, 'ascii');
}
/**
* Send press down + commit minitouch events
*/
async downCommit(x, y, contact = 0, pressure = 0) {
const cmd = `d ${contact} ${x | 0} ${y | 0} ${pressure | 0}\nc\n`;
const s = await this.getMinitouchSocket();
return s.write(cmd, 'ascii');
}
/**
* Send press up + commit minitouch events
*/
async upCommit(contact = 0) {
const cmd = `u ${contact | 0}\nc\n`;
const s = await this.getMinitouchSocket();
return s.write(cmd, 'ascii');
}
/**
* Send wait instruction minitouch events
*/
async wait(time) {
const cmd = `w ${time}\n`;
const s = await this.getMinitouchSocket();
return s.write(cmd, 'ascii');
}
/**
* stop the service
*/
stop() {
let close = false;
if (this.servicesSocket) {
this.servicesSocket.destroy();
this.servicesSocket = undefined;
close = true;
}
if (this._agentSocket) {
this._agentSocket.then(a => a.destroy());
this._agentSocket = undefined;
close = true;
}
if (this._minitouchagent) {
this._minitouchagent.then(a => a.destroy());
this._minitouchagent = undefined;
close = true;
}
if (close)
this.emit('disconnect');
return close;
}
isRunning() {
return this.servicesSocket !== null;
}
}
exports.default = STFService;
//# sourceMappingURL=STFService.js.map