diff --git a/lib/android/index.js b/lib/android/index.js index 66a00092c..5ce8b9ed9 100644 --- a/lib/android/index.js +++ b/lib/android/index.js @@ -1,16 +1,11 @@ -import { promisify } from 'node:util'; -import { mkdir as _mkdir, createWriteStream, createReadStream } from 'node:fs'; import path from 'node:path'; import { EOL as endOfLine } from 'node:os'; import { execa } from 'execa'; import { getLogger } from '@sitespeed.io/log'; -import pkg from '@devicefarmer/adbkit'; -const { Adb } = pkg; import { pathToFolder } from '../support/pathToFolder.js'; import { loadUsbPowerProfiler } from '../support/usbPower.js'; import { getProperty } from '../support/util.js'; const log = getLogger('browsertime.android'); -const mkdir = promisify(_mkdir); const delay = ms => new Promise(res => setTimeout(res, ms)); export class Android { @@ -23,7 +18,6 @@ export class Android { } Android.instance = this; - this.client = Adb.createClient(); this.id = getProperty( options, @@ -42,44 +36,53 @@ export class Android { async _init() { if (!this.id) { - const devices = await this.client.listDevices(); + const { stdout } = await execa('adb', ['devices']); // just take the first phone online - if (devices.length > 0) { - this.id = devices[0].id; + const device = stdout + .split(/\r?\n/) + .map(line => line.trim().split(/\s+/)) + .find(parts => parts[1] === 'device'); + if (device) { + this.id = device[0]; } else { throw new Error('No Android phone was found'); } } - this.device = this.client.getDevice(this.id); - if (!this.sdcard) { this.sdcard = await this._runCommandAndGet('echo $EXTERNAL_STORAGE'); } } + _adb(arguments_, options = {}) { + return execa('adb', ['-s', this.id, ...arguments_], options); + } + async _runCommand(command) { - return this.device.shell(command); + const result = await this._adb(['shell', command], { + all: true, + reject: false + }); + // A missing exit code means adb itself could not run + if (result.exitCode === undefined) { + throw result; + } + return result.all; } async _runCommandAndGet(command) { - const data = await this.device.shell(command); - const output = await Adb.util.readAll(data); - return output.toString().trim(); + const output = await this._runCommand(command); + return output.trim(); } async _runAsRootAndGet(command) { - const data = await this.device.shell('su - root -c "' + command + '"'); - const output = await Adb.util.readAll(data); - return output.toString().trim(); + return this._runCommandAndGet('su - root -c "' + command + '"'); } async _runAsRoot(command) { - const data = await this.device.shell( + const result = await this._runCommandAndGet( 'su - root -c "' + command + ' && echo SUCCESS || echo FAIL"' ); - const output = await Adb.util.readAll(data); - const result = output.toString().trim(); if (result === 'FAIL') { log.error('Failing running as root:' + command); } @@ -88,37 +91,18 @@ export class Android { async _downloadFile(sourcePath, destinationPath) { log.debug(`Pulling to ${destinationPath} from ${sourcePath}`); - - const transfer = await this.device.pull(sourcePath); - - return new Promise((resolve, reject) => { - transfer.on('end', function () { - resolve(); - }); - transfer.on('error', reject); - transfer.pipe(createWriteStream(destinationPath)); - }); + await this._adb(['pull', sourcePath, destinationPath]); } async _uploadFile(sourcePath, destinationPath) { log.debug(`Pushing to ${destinationPath} from ${sourcePath}`); - return this.device.push(createReadStream(sourcePath), destinationPath); + await this._adb(['push', sourcePath, destinationPath]); } async _downloadDir(sourcePath, destinationPath) { - const files = await this.device.readdir(sourcePath); - - for (const file of files) { - const fullSourcePath = `${sourcePath}/${file.name}`; - const fullDestinationPath = path.join(destinationPath, file.name); - - if (file.isFile()) { - await this._downloadFile(fullSourcePath, fullDestinationPath); - } else if (file.isDirectory()) { - await mkdir(fullDestinationPath, { recursive: true }); - await this._downloadDir(fullSourcePath, fullDestinationPath); - } - } + // The trailing /. makes adb copy the content of the directory + // instead of the directory itself + await this._adb(['pull', `${sourcePath}/.`, destinationPath]); } getFullPathOnSdCard(path) { @@ -147,7 +131,7 @@ export class Android { } async reboot() { - await this.device.reboot(this.id); + await this._adb(['reboot']); return delay(60_000); } @@ -161,25 +145,17 @@ export class Android { } async getMeta() { - const rawModel = await Adb.util.readAll( - await this._runCommand(`getprop ro.product.model`) - ); + const rawModel = await this._runCommand(`getprop ro.product.model`); const model = rawModel.toString().trim(); - const rawName = await Adb.util.readAll( - await this._runCommand(`getprop ro.product.name`) - ); + const rawName = await this._runCommand(`getprop ro.product.name`); const name = rawName.toString().trim(); - const rawDevice = await Adb.util.readAll( - await this._runCommand(`getprop ro.product.device`) - ); + const rawDevice = await this._runCommand(`getprop ro.product.device`); const device = rawDevice.toString().trim(); - const rawId = await Adb.util.readAll( - await this._runCommand(`getprop ro.serialno`) - ); + const rawId = await this._runCommand(`getprop ro.serialno`); const id = rawId.toString().trim(); const wifi = await this.getWifi(); - const rawRelease = await Adb.util.readAll( - await this._runCommand(`getprop ro.build.version.release `) + const rawRelease = await this._runCommand( + `getprop ro.build.version.release ` ); const androidVersion = rawRelease.toString().trim(); return { model, name, device, androidVersion, id, wifi }; @@ -193,10 +169,11 @@ export class Android { } async addDevtoolsFw() { - return this.device.forward( + return this._adb([ + 'forward', 'tcp:' + this.port, 'localabstract:chrome_devtools_remote' - ); + ]); } async removeDevtoolsFw() { @@ -225,8 +202,13 @@ export class Android { } async startVideo() { - return this._runCommand( - `screenrecord --bit-rate 8000000 ${this.sdcard}/browsertime.mp4` + // screenrecord runs until stopVideo kills it, so do not wait for it + this._adb( + [ + 'shell', + `screenrecord --bit-rate 8000000 ${this.sdcard}/browsertime.mp4` + ], + { reject: false } ); } @@ -241,10 +223,8 @@ export class Android { } async getWifi() { - const rawWifiInfo = await Adb.util.readAll( - await this._runCommand( - `dumpsys netstats | grep -E 'iface=wlan.*networkId'` - ) + const rawWifiInfo = await this._runCommand( + `dumpsys netstats | grep -E 'iface=wlan.*networkId'` ); const wifiInfo = rawWifiInfo.toString().trim(); const wifi = wifiInfo.match(/"[^"]*"|^[^"]*$/)[0].replaceAll('"', ''); @@ -303,9 +283,7 @@ export class Android { // This method is expected to work on the Moto G5 and the Pixel 2. const cmd1 = `pidof ${packageName}`; log.debug(`pidof ${cmd1}`); - const proc1 = await this._runCommand(cmd1); - const ps1 = await Adb.util.readAll(proc1); - const ps = ps1.toString(); + const ps = await this._runCommand(cmd1); log.debug(`pidof ${ps}`); if ( @@ -332,9 +310,7 @@ export class Android { // This method was tested on the Galaxy S5. const cmd = `ps`; log.debug(`pidof ${cmd}`); - const proc = await this._runCommand(cmd); - const psTemporary = await Adb.util.readAll(proc); - const ps = psTemporary.toString(); + const ps = await this._runCommand(cmd); log.debug(`pidof ${ps}`); const lines = ps.split(/[\n\r]+/); @@ -387,10 +363,7 @@ export class Android { // `cut` and `awk`, to make this more robust. const cut = `echo $EPOCHREALTIME && cut -d ' ' -f 1 /proc/uptime && cut -d ' ' -f 22 /proc/self/stat /proc/${pid}/stat`; // const awk = `date +%s%3N && awk '{print $1}' /proc/uptime && awk '{print $22}' /proc/self/stat /proc/${pid}/stat`; - const statAndTimestamp = await Adb.util.readAll( - await this._runCommand(cut) - ); - const output = statAndTimestamp.toString(); + const output = await this._runCommand(cut); const [ dateInS, diff --git a/package-lock.json b/package-lock.json index 250d0d90c..340e3de54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "28.3.0", "license": "Apache-2.0", "dependencies": { - "@devicefarmer/adbkit": "3.3.8", "@sitespeed.io/chromedriver": "149.0.7827", "@sitespeed.io/edgedriver": "149.0.4022", "@sitespeed.io/geckodriver": "0.36.2", @@ -97,58 +96,6 @@ "url": "https://github.com/sponsors/Borewit" } }, - "node_modules/@devicefarmer/adbkit": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/@devicefarmer/adbkit/-/adbkit-3.3.8.tgz", - "integrity": "sha512-7rBLLzWQnBwutH2WZ0EWUkQdihqrnLYCUMaB44hSol9e0/cdIhuNFcqZO0xNheAU6qqHVA8sMiLofkYTgb+lmw==", - "dependencies": { - "@devicefarmer/adbkit-logcat": "^2.1.2", - "@devicefarmer/adbkit-monkey": "~1.2.1", - "bluebird": "~3.7", - "commander": "^9.1.0", - "debug": "~4.3.1", - "node-forge": "^1.3.1", - "split": "~1.0.1" - }, - "bin": { - "adbkit": "bin/adbkit" - }, - "engines": { - "node": ">= 0.10.4" - } - }, - "node_modules/@devicefarmer/adbkit-logcat": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@devicefarmer/adbkit-logcat/-/adbkit-logcat-2.1.3.tgz", - "integrity": "sha512-yeaGFjNBc/6+svbDeul1tNHtNChw6h8pSHAt5D+JsedUrMTN7tla7B15WLDyekxsuS2XlZHRxpuC6m92wiwCNw==", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@devicefarmer/adbkit-monkey": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@devicefarmer/adbkit-monkey/-/adbkit-monkey-1.2.1.tgz", - "integrity": "sha512-ZzZY/b66W2Jd6NHbAhLyDWOEIBWC11VizGFk7Wx7M61JZRz7HR9Cq5P+65RKWUU7u6wgsE8Lmh9nE4Mz+U2eTg==", - "engines": { - "node": ">= 0.10.4" - } - }, - "node_modules/@devicefarmer/adbkit/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -1944,7 +1891,8 @@ "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true }, "node_modules/blueimp-md5": { "version": "2.19.0", @@ -2613,6 +2561,7 @@ "version": "9.5.0", "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, "engines": { "node": "^12.20.0 || >=14" } @@ -4720,7 +4669,8 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true }, "node_modules/natural-compare": { "version": "1.4.0", @@ -4774,15 +4724,6 @@ } } }, - "node_modules/node-forge": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", - "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", - "license": "(BSD-3-Clause OR GPL-2.0)", - "engines": { - "node": ">= 6.13.0" - } - }, "node_modules/node-gyp-build": { "version": "4.8.0", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz", @@ -5861,17 +5802,6 @@ "source-map": "^0.6.0" } }, - "node_modules/split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", - "dependencies": { - "through": "2" - }, - "engines": { - "node": "*" - } - }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -6143,11 +6073,6 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" - }, "node_modules/time-zone": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/time-zone/-/time-zone-1.0.0.tgz", @@ -6803,40 +6728,6 @@ "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", "optional": true }, - "@devicefarmer/adbkit": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/@devicefarmer/adbkit/-/adbkit-3.3.8.tgz", - "integrity": "sha512-7rBLLzWQnBwutH2WZ0EWUkQdihqrnLYCUMaB44hSol9e0/cdIhuNFcqZO0xNheAU6qqHVA8sMiLofkYTgb+lmw==", - "requires": { - "@devicefarmer/adbkit-logcat": "^2.1.2", - "@devicefarmer/adbkit-monkey": "~1.2.1", - "bluebird": "~3.7", - "commander": "^9.1.0", - "debug": "~4.3.1", - "node-forge": "^1.3.1", - "split": "~1.0.1" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - } - } - } - }, - "@devicefarmer/adbkit-logcat": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@devicefarmer/adbkit-logcat/-/adbkit-logcat-2.1.3.tgz", - "integrity": "sha512-yeaGFjNBc/6+svbDeul1tNHtNChw6h8pSHAt5D+JsedUrMTN7tla7B15WLDyekxsuS2XlZHRxpuC6m92wiwCNw==" - }, - "@devicefarmer/adbkit-monkey": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@devicefarmer/adbkit-monkey/-/adbkit-monkey-1.2.1.tgz", - "integrity": "sha512-ZzZY/b66W2Jd6NHbAhLyDWOEIBWC11VizGFk7Wx7M61JZRz7HR9Cq5P+65RKWUU7u6wgsE8Lmh9nE4Mz+U2eTg==" - }, "@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -8071,7 +7962,8 @@ "bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true }, "blueimp-md5": { "version": "2.19.0", @@ -8521,7 +8413,8 @@ "commander": { "version": "9.5.0", "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==" + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true }, "common-path-prefix": { "version": "3.0.0", @@ -9980,7 +9873,8 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true }, "natural-compare": { "version": "1.4.0", @@ -10019,11 +9913,6 @@ "whatwg-url": "^5.0.0" } }, - "node-forge": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", - "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==" - }, "node-gyp-build": { "version": "4.8.0", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz", @@ -10733,14 +10622,6 @@ "source-map": "^0.6.0" } }, - "split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", - "requires": { - "through": "2" - } - }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -10930,11 +10811,6 @@ } } }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" - }, "time-zone": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/time-zone/-/time-zone-1.0.0.tgz", diff --git a/package.json b/package.json index fc8c2fc78..8812a228a 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,6 @@ "type": "module", "types": "./scripting.d.ts", "dependencies": { - "@devicefarmer/adbkit": "3.3.8", "@sitespeed.io/chromedriver": "149.0.7827", "@sitespeed.io/edgedriver": "149.0.4022", "@sitespeed.io/geckodriver": "0.36.2",