diff --git a/apps/demo/components/demo-mode.tsx b/apps/demo/components/demo-mode-panel.tsx similarity index 74% rename from apps/demo/components/demo-mode.tsx rename to apps/demo/components/demo-mode-panel.tsx index 0aec875ea..5378813b4 100644 --- a/apps/demo/components/demo-mode.tsx +++ b/apps/demo/components/demo-mode-panel.tsx @@ -1,26 +1,26 @@ import { Dropdown, IDropdownOption, Position, Separator, SpinButton, Toggle } from '@fluentui/react'; -import { AdbDemoModeMobileDataType, AdbDemoModeMobileDataTypes, AdbDemoModeSignalStrength, AdbDemoModeStatusBarMode, AdbDemoModeStatusBarModes } from '@yume-chan/adb'; +import { DemoMode, DemoModeMobileDataType, DemoModeMobileDataTypes, DemoModeSignalStrength, DemoModeStatusBarMode, DemoModeStatusBarModes } from '@yume-chan/android-bin'; import { autorun, makeAutoObservable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react-lite"; import { CSSProperties, useCallback } from 'react'; import { globalState } from "../state"; const SignalStrengthOptions = - Object.values(AdbDemoModeSignalStrength) + Object.values(DemoModeSignalStrength) .map((key) => ({ key, text: { - [AdbDemoModeSignalStrength.Hidden]: 'Hidden', - [AdbDemoModeSignalStrength.Level0]: 'Level 0', - [AdbDemoModeSignalStrength.Level1]: 'Level 1', - [AdbDemoModeSignalStrength.Level2]: 'Level 2', - [AdbDemoModeSignalStrength.Level3]: 'Level 3', - [AdbDemoModeSignalStrength.Level4]: 'Level 4', + [DemoModeSignalStrength.Hidden]: 'Hidden', + [DemoModeSignalStrength.Level0]: 'Level 0', + [DemoModeSignalStrength.Level1]: 'Level 1', + [DemoModeSignalStrength.Level2]: 'Level 2', + [DemoModeSignalStrength.Level3]: 'Level 3', + [DemoModeSignalStrength.Level4]: 'Level 4', }[key], })); const MobileDataTypeOptions = - AdbDemoModeMobileDataTypes + DemoModeMobileDataTypes .map((key) => ({ key, text: { @@ -47,7 +47,7 @@ const MobileDataTypeOptions = })); const StatusBarModeOptions = - AdbDemoModeStatusBarModes + DemoModeStatusBarModes .map((key) => ({ key, text: { @@ -59,7 +59,9 @@ const StatusBarModeOptions = }[key], })); -class DemoModeState { +class DemoModePanelState { + demoMode: DemoMode | undefined; + allowed = false; enabled = false; features: Map = new Map(); @@ -71,13 +73,15 @@ class DemoModeState { () => globalState.device, async (device) => { if (device) { - const allowed = await device.demoMode.getAllowed(); + runInAction(() => this.demoMode = new DemoMode(device)); + const allowed = await this.demoMode!.getAllowed(); runInAction(() => this.allowed = allowed); if (allowed) { - const enabled = await device.demoMode.getEnabled(); + const enabled = await this.demoMode!.getEnabled(); runInAction(() => this.enabled = enabled); } } else { + this.demoMode = undefined; this.allowed = false; this.enabled = false; this.features.clear(); @@ -99,7 +103,7 @@ class DemoModeState { } } -const state = new DemoModeState(); +const state = new DemoModePanelState(); interface FeatureDefinition { key: string; @@ -123,21 +127,21 @@ const FEATURES: FeatureDefinition[][] = [ max: 100, step: 1, initial: 100, - onChange: (value) => globalState.device!.demoMode.setBatteryLevel(value as number), + onChange: (value) => state.demoMode!.setBatteryLevel(value as number), }, { key: 'batteryCharging', label: 'Battery Charging', type: 'boolean', initial: false, - onChange: (value) => globalState.device!.demoMode.setBatteryCharging(value as boolean), + onChange: (value) => state.demoMode!.setBatteryCharging(value as boolean), }, { key: 'powerSaveMode', label: 'Power Save Mode', type: 'boolean', initial: false, - onChange: (value) => globalState.device!.demoMode.setPowerSaveMode(value as boolean), + onChange: (value) => state.demoMode!.setPowerSaveMode(value as boolean), }, ], [ @@ -146,15 +150,15 @@ const FEATURES: FeatureDefinition[][] = [ label: 'Wifi Signal Strength', type: 'select', options: SignalStrengthOptions, - initial: AdbDemoModeSignalStrength.Level4, - onChange: (value) => globalState.device!.demoMode.setWifiSignalStrength(value as AdbDemoModeSignalStrength), + initial: DemoModeSignalStrength.Level4, + onChange: (value) => state.demoMode!.setWifiSignalStrength(value as DemoModeSignalStrength), }, { key: 'airplaneMode', label: 'Airplane Mode', type: 'boolean', initial: false, - onChange: (value) => globalState.device!.demoMode.setAirplaneMode(value as boolean), + onChange: (value) => state.demoMode!.setAirplaneMode(value as boolean), }, { key: 'mobileDataType', @@ -162,15 +166,15 @@ const FEATURES: FeatureDefinition[][] = [ type: 'select', options: MobileDataTypeOptions, initial: 'lte', - onChange: (value) => globalState.device!.demoMode.setMobileDataType(value as AdbDemoModeMobileDataType), + onChange: (value) => state.demoMode!.setMobileDataType(value as DemoModeMobileDataType), }, { key: 'mobileSignalStrength', label: 'Mobile Signal Strength', type: 'select', options: SignalStrengthOptions, - initial: AdbDemoModeSignalStrength.Level4, - onChange: (value) => globalState.device!.demoMode.setMobileSignalStrength(value as AdbDemoModeSignalStrength), + initial: DemoModeSignalStrength.Level4, + onChange: (value) => state.demoMode!.setMobileSignalStrength(value as DemoModeSignalStrength), }, ], [ @@ -180,42 +184,42 @@ const FEATURES: FeatureDefinition[][] = [ type: 'select', options: StatusBarModeOptions, initial: 'transparent', - onChange: (value) => globalState.device!.demoMode.setStatusBarMode(value as AdbDemoModeStatusBarMode), + onChange: (value) => state.demoMode!.setStatusBarMode(value as DemoModeStatusBarMode), }, { key: 'vibrateMode', label: 'Vibrate Mode Indicator', type: 'boolean', initial: false, - onChange: (value) => globalState.device!.demoMode.setVibrateModeEnabled(value as boolean), + onChange: (value) => state.demoMode!.setVibrateModeEnabled(value as boolean), }, { key: 'bluetoothConnected', label: 'Bluetooth Indicator', type: 'boolean', initial: false, - onChange: (value) => globalState.device!.demoMode.setBluetoothConnected(value as boolean), + onChange: (value) => state.demoMode!.setBluetoothConnected(value as boolean), }, { key: 'locatingIcon', label: 'Locating Icon', type: 'boolean', initial: false, - onChange: (value) => globalState.device!.demoMode.setLocatingIcon(value as boolean), + onChange: (value) => state.demoMode!.setLocatingIcon(value as boolean), }, { key: 'alarmIcon', label: 'Alarm Icon', type: 'boolean', initial: false, - onChange: (value) => globalState.device!.demoMode.setAlarmIcon(value as boolean), + onChange: (value) => state.demoMode!.setAlarmIcon(value as boolean), }, { key: 'notificationsVisibility', label: 'Notifications Visibility', type: 'boolean', initial: true, - onChange: (value) => globalState.device!.demoMode.setNotificationsVisibility(value as boolean), + onChange: (value) => state.demoMode!.setNotificationsVisibility(value as boolean), }, { key: 'hour', @@ -225,7 +229,7 @@ const FEATURES: FeatureDefinition[][] = [ max: 23, step: 1, initial: 12, - onChange: (value) => globalState.device!.demoMode.setTime(value as number, state.features.get('minute') as number | undefined ?? 34) + onChange: (value) => state.demoMode!.setTime(value as number, state.features.get('minute') as number | undefined ?? 34) }, { key: 'minute', @@ -235,7 +239,7 @@ const FEATURES: FeatureDefinition[][] = [ max: 59, step: 1, initial: 34, - onChange: (value) => globalState.device!.demoMode.setTime(state.features.get('hour') as number | undefined ?? 34, value as number) + onChange: (value) => state.demoMode!.setTime(state.features.get('hour') as number | undefined ?? 34, value as number) }, ], ]; @@ -301,15 +305,15 @@ const FeatureBase = ({ feature }: { feature: FeatureDefinition; }) => { const Feature = observer(FeatureBase); -export interface DemoModeProps { +export interface DemoModePanelProps { style?: CSSProperties; } -const DemoModeBase = ({ +export const DemoModePanel = observer(({ style, -}: DemoModeProps) => { +}: DemoModePanelProps) => { const handleAllowedChange = useCallback(async (e, value?: boolean) => { - await globalState.device!.demoMode.setAllowed(value!); + await state.demoMode!.setAllowed(value!); runInAction(() => { state.allowed = value!; state.enabled = false; @@ -317,7 +321,7 @@ const DemoModeBase = ({ }, []); const handleEnabledChange = useCallback(async (e, value?: boolean) => { - await globalState.device!.demoMode.setEnabled(value!); + await state.demoMode!.setEnabled(value!); runInAction(() => state.enabled = value!); }, []); @@ -351,6 +355,4 @@ const DemoModeBase = ({ ))} ); -}; - -export const DemoMode = observer(DemoModeBase); +}); diff --git a/apps/demo/components/index.ts b/apps/demo/components/index.ts index d55bd9497..ac6a584f0 100644 --- a/apps/demo/components/index.ts +++ b/apps/demo/components/index.ts @@ -1,6 +1,6 @@ export * from './command-bar'; export * from './connect'; -export * from './demo-mode'; +export * from './demo-mode-panel'; export * from './device-view'; export * from './error-dialog'; export * from './external-link'; diff --git a/apps/demo/package.json b/apps/demo/package.json index 19af9ba4d..3e7d515d0 100644 --- a/apps/demo/package.json +++ b/apps/demo/package.json @@ -20,6 +20,7 @@ "@yume-chan/adb-backend-webusb": "^0.0.10", "@yume-chan/adb-backend-ws": "^0.0.9", "@yume-chan/adb-credential-web": "^0.0.10", + "@yume-chan/android-bin": "^0.0.10", "@yume-chan/async": "^2.1.4", "@yume-chan/event": "^0.0.10", "@yume-chan/scrcpy": "^0.0.10", diff --git a/apps/demo/pages/framebuffer.tsx b/apps/demo/pages/framebuffer.tsx index 4cdda5dd8..bbc97d3a5 100644 --- a/apps/demo/pages/framebuffer.tsx +++ b/apps/demo/pages/framebuffer.tsx @@ -5,7 +5,7 @@ import { observer } from "mobx-react-lite"; import { NextPage } from "next"; import Head from "next/head"; import React, { useCallback, useEffect, useRef } from 'react'; -import { CommandBar, DemoMode, DeviceView } from '../components'; +import { CommandBar, DemoModePanel, DeviceView } from '../components'; import { globalState } from "../state"; import { Icons, RouteStackProps } from "../utils"; @@ -123,7 +123,7 @@ const FrameBuffer: NextPage = (): JSX.Element | null => { - + ); diff --git a/apps/demo/pages/scrcpy.tsx b/apps/demo/pages/scrcpy.tsx index 5fdf312b4..fb1c19d0a 100644 --- a/apps/demo/pages/scrcpy.tsx +++ b/apps/demo/pages/scrcpy.tsx @@ -8,7 +8,7 @@ import { observer } from "mobx-react-lite"; import { NextPage } from "next"; import Head from "next/head"; import React, { useEffect, useMemo, useState } from "react"; -import { DemoMode, DeviceView, DeviceViewRef, ExternalLink } from "../components"; +import { DemoModePanel, DeviceView, DeviceViewRef, ExternalLink } from "../components"; import { globalState } from "../state"; import { CommonStackTokens, formatSpeed, Icons, RouteStackProps } from "../utils"; @@ -894,7 +894,7 @@ const Scrcpy: NextPage = () => { /> - diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 6179dc1b2..7fe422d40 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -16,6 +16,7 @@ specifiers: '@rush-temp/adb-backend-webusb': file:./projects/adb-backend-webusb.tgz '@rush-temp/adb-backend-ws': file:./projects/adb-backend-ws.tgz '@rush-temp/adb-credential-web': file:./projects/adb-credential-web.tgz + '@rush-temp/android-bin': file:./projects/android-bin.tgz '@rush-temp/dataview-bigint-polyfill': file:./projects/dataview-bigint-polyfill.tgz '@rush-temp/demo': file:./projects/demo.tgz '@rush-temp/event': file:./projects/event.tgz @@ -74,6 +75,7 @@ dependencies: '@rush-temp/adb-backend-webusb': file:projects/adb-backend-webusb.tgz '@rush-temp/adb-backend-ws': file:projects/adb-backend-ws.tgz '@rush-temp/adb-credential-web': file:projects/adb-credential-web.tgz + '@rush-temp/android-bin': file:projects/android-bin.tgz '@rush-temp/dataview-bigint-polyfill': file:projects/dataview-bigint-polyfill.tgz '@rush-temp/demo': file:projects/demo.tgz_@mdx-js+react@1.6.22 '@rush-temp/event': file:projects/event.tgz @@ -13108,6 +13110,15 @@ packages: - utf-8-validate dev: false + file:projects/android-bin.tgz: + resolution: {integrity: sha512-+POm1mf4P1s+NhgwA6IHN+GBBmc00lDi7k+geCc04ts836duRlnJfkIEeIIJmjQHVwDz36BONp8rCXAaX4U/fA==, tarball: file:projects/android-bin.tgz} + name: '@rush-temp/android-bin' + version: 0.0.0 + dependencies: + tslib: 2.3.1 + typescript: 4.5.5 + dev: false + file:projects/dataview-bigint-polyfill.tgz: resolution: {integrity: sha512-tlwAp44MyiGGyikI+6kFaCkVQjkpKmlMPm0hm6ts97uydxfdNgMnoGp0JW4J4WDvFOjforF3Z1+kfvBTVDHv1w==, tarball: file:projects/dataview-bigint-polyfill.tgz} name: '@rush-temp/dataview-bigint-polyfill' diff --git a/libraries/adb/src/adb.ts b/libraries/adb/src/adb.ts index 75185265e..15a7cd285 100644 --- a/libraries/adb/src/adb.ts +++ b/libraries/adb/src/adb.ts @@ -2,7 +2,7 @@ import { PromiseResolver } from '@yume-chan/async'; import { DisposableList } from '@yume-chan/event'; import { AdbAuthenticationHandler, AdbCredentialStore, AdbDefaultAuthenticators } from './auth'; import { AdbBackend } from './backend'; -import { AdbChildProcess, AdbDemoMode, AdbFrameBuffer, AdbPower, AdbReverseCommand, AdbSync, AdbTcpIpCommand, escapeArg, framebuffer, install } from './commands'; +import { AdbChildProcess, AdbFrameBuffer, AdbPower, AdbReverseCommand, AdbSync, AdbTcpIpCommand, escapeArg, framebuffer, install } from './commands'; import { AdbFeatures } from './features'; import { AdbCommand } from './packet'; import { AdbLogger, AdbPacketDispatcher, AdbSocket } from './socket'; @@ -45,7 +45,6 @@ export class Adb { public get features() { return this._features; } public readonly childProcess: AdbChildProcess; - public readonly demoMode: AdbDemoMode; public readonly power: AdbPower; public readonly reverse: AdbReverseCommand; public readonly tcpip: AdbTcpIpCommand; @@ -55,7 +54,6 @@ export class Adb { this.packetDispatcher = new AdbPacketDispatcher(backend, logger); this.childProcess = new AdbChildProcess(this); - this.demoMode = new AdbDemoMode(this); this.power = new AdbPower(this); this.reverse = new AdbReverseCommand(this.packetDispatcher); this.tcpip = new AdbTcpIpCommand(this); @@ -196,12 +194,17 @@ export class Adb { } public async getProp(key: string): Promise { - const output = await this.childProcess.exec('getprop', key); - return output.trim(); + const stdout = await this.childProcess.spawnAndWaitLegacy( + ['getprop', key] + ); + return stdout.trim(); } public async rm(...filenames: string[]): Promise { - return await this.childProcess.exec('rm', '-rf', ...filenames.map(arg => escapeArg(arg))); + const stdout = await this.childProcess.spawnAndWaitLegacy( + ['rm', '-rf', ...filenames.map(arg => escapeArg(arg))], + ); + return stdout; } public async install( diff --git a/libraries/adb/src/commands/index.ts b/libraries/adb/src/commands/index.ts index bab664448..2d61861fb 100644 --- a/libraries/adb/src/commands/index.ts +++ b/libraries/adb/src/commands/index.ts @@ -1,5 +1,4 @@ export * from './base'; -export * from './demo-mode'; export * from './framebuffer'; export * from './install'; export * from './power'; diff --git a/libraries/adb/src/commands/install.ts b/libraries/adb/src/commands/install.ts index 0a500f8e8..af6112a39 100644 --- a/libraries/adb/src/commands/install.ts +++ b/libraries/adb/src/commands/install.ts @@ -14,7 +14,7 @@ export async function install( sync.dispose(); // Invoke `pm install` to install it - await adb.childProcess.exec('pm', 'install', escapeArg(filename)); + await adb.childProcess.spawnAndWaitLegacy(['pm', 'install', escapeArg(filename)]); // Remove the temp file await adb.rm(filename); diff --git a/libraries/adb/src/commands/power.ts b/libraries/adb/src/commands/power.ts index 7a53c244b..b354f5c95 100644 --- a/libraries/adb/src/commands/power.ts +++ b/libraries/adb/src/commands/power.ts @@ -36,11 +36,11 @@ export class AdbPower extends AdbCommandBase { } public powerOff() { - return this.adb.childProcess.exec('reboot', '-p'); + return this.adb.childProcess.spawnAndWaitLegacy(['reboot', '-p']); } public powerButton(longPress: boolean = false) { - return this.adb.childProcess.exec('input', 'keyevent', longPress ? '--longpress POWER' : 'POWER'); + return this.adb.childProcess.spawnAndWaitLegacy(['input', 'keyevent', longPress ? '--longpress POWER' : 'POWER']); } /** diff --git a/libraries/adb/src/commands/shell/index.ts b/libraries/adb/src/commands/shell/index.ts index b3ccfa49a..18082456e 100644 --- a/libraries/adb/src/commands/shell/index.ts +++ b/libraries/adb/src/commands/shell/index.ts @@ -1,4 +1,6 @@ +import { once } from "@yume-chan/event"; import type { Adb } from '../../adb'; +import { decodeUtf8 } from "../../utils"; import { AdbLegacyShell } from './legacy'; import { AdbShellProtocol } from './protocol'; import type { AdbShell, AdbShellConstructor } from './types'; @@ -27,6 +29,12 @@ const DefaultOptions: AdbChildProcessOptions = { shells: [AdbShellProtocol, AdbLegacyShell], }; +export interface ChildProcessResult { + stdout: string; + stderr: string; + exitCode: number; +} + export class AdbChildProcess { public readonly adb: Adb; @@ -80,8 +88,23 @@ export class AdbChildProcess { * @param args List of command arguments * @returns The entire output of the command */ - public exec(command: string, ...args: string[]): Promise { - // `exec` only needs the entire output, use Legacy Shell is simpler. - return this.adb.createSocketAndReadAll(`shell:${command} ${args.join(' ')}`); + public async spawnAndWait(command: string | string[], options?: Partial): Promise { + const shell = await this.spawn(command, options); + // Optimization: rope (concat strings) is faster than `[].join('')` + let stdout = ''; + let stderr = ''; + shell.onStdout(buffer => stdout += decodeUtf8(buffer)); + shell.onStderr(buffer => stderr += decodeUtf8(buffer)); + const exitCode = await once(shell.onExit); + return { + stdout, + stderr, + exitCode, + }; + } + + public async spawnAndWaitLegacy(command: string | string[]): Promise { + const { stdout } = await this.spawnAndWait(command, { shells: [AdbLegacyShell] }); + return stdout; } } diff --git a/libraries/android-bin/README.md b/libraries/android-bin/README.md new file mode 100644 index 000000000..96d1e739d --- /dev/null +++ b/libraries/android-bin/README.md @@ -0,0 +1,5 @@ +# `@yume-chan/android-bin` + +Wrappers for Android built-in executables. + +Currently it's bound to `@yume-chan/adb`, maybe one day it can be used with other executors. diff --git a/libraries/android-bin/package.json b/libraries/android-bin/package.json new file mode 100644 index 000000000..368868c45 --- /dev/null +++ b/libraries/android-bin/package.json @@ -0,0 +1,41 @@ +{ + "name": "@yume-chan/android-bin", + "version": "0.0.10", + "description": "Wrappers for Android built-in executables.", + "keywords": [ + "adb", + "Android" + ], + "license": "MIT", + "author": { + "name": "Simon Chan", + "email": "cnsimonchan@live.com", + "url": "https://chensi.moe/blog" + }, + "homepage": "https://github.com/yume-chan/ya-webadb/tree/master/libraries/android-bin#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/yume-chan/ya-webadb.git", + "directory": "libraries/android-bin" + }, + "bugs": { + "url": "https://github.com/yume-chan/ya-webadb/issues" + }, + "type": "module", + "module": "esm/index.js", + "types": "esm/index.d.ts", + "scripts": { + "build": "build-ts-package", + "build:watch": "build-ts-package --incremental", + "prepublishOnly": "npm run build" + }, + "devDependencies": { + "typescript": "^4.5.5", + "@yume-chan/ts-package-builder": "^1.0.0" + }, + "dependencies": { + "@yume-chan/adb": "^0.0.10", + "@yume-chan/event": "^0.0.10", + "tslib": "^2.3.1" + } +} diff --git a/libraries/android-bin/src/bug-report.ts b/libraries/android-bin/src/bug-report.ts new file mode 100644 index 000000000..3bec1567b --- /dev/null +++ b/libraries/android-bin/src/bug-report.ts @@ -0,0 +1,35 @@ +// cspell: ignore bugreport +// cspell: ignore bugreportz + +import { AdbCommandBase, AdbShellProtocol } from "@yume-chan/adb"; + +export interface BugReportVersion { + major: number; + minor: number; +} + +const BUG_REPORT_VERSION_REGEX = /(\d+)\.(\d+)/; + +export class BugReport extends AdbCommandBase { + public async bugReportZVersion(): Promise { + // bugreportz requires shell protocol + if (!AdbShellProtocol.isSupported(this.adb)) { + return undefined; + } + + const { stderr, exitCode } = await this.adb.childProcess.spawnAndWait(['bugreportz', '-v']); + if (exitCode !== 0 || stderr === '') { + return undefined; + } + + const match = stderr.match(BUG_REPORT_VERSION_REGEX); + if (!match) { + return undefined; + } + + return { + major: parseInt(match[1]!, 10), + minor: parseInt(match[2]!, 10), + }; + } +} diff --git a/libraries/adb/src/commands/demo-mode.ts b/libraries/android-bin/src/demo-mode.ts similarity index 74% rename from libraries/adb/src/commands/demo-mode.ts rename to libraries/android-bin/src/demo-mode.ts index 6c0dcdcda..3335869ba 100644 --- a/libraries/adb/src/commands/demo-mode.ts +++ b/libraries/android-bin/src/demo-mode.ts @@ -4,9 +4,9 @@ // cspell: ignore systemui // cspell: ignore sysui -import { AdbCommandBase } from './base'; +import { AdbCommandBase, AdbLegacyShell } from '@yume-chan/adb'; -export enum AdbDemoModeSignalStrength { +export enum DemoModeSignalStrength { Hidden = 'null', Level0 = '0', Level1 = '1', @@ -16,17 +16,23 @@ export enum AdbDemoModeSignalStrength { } // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java;l=1073 -export const AdbDemoModeMobileDataTypes = ['1x', '3g', '4g', '4g+', '5g', '5ge', '5g+', +export const DemoModeMobileDataTypes = ['1x', '3g', '4g', '4g+', '5g', '5ge', '5g+', 'e', 'g', 'h', 'h+', 'lte', 'lte+', 'dis', 'not', 'null'] as const; -export type AdbDemoModeMobileDataType = (typeof AdbDemoModeMobileDataTypes)[number]; +export type DemoModeMobileDataType = (typeof DemoModeMobileDataTypes)[number]; // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java;l=3136 -export const AdbDemoModeStatusBarModes = ['opaque', 'translucent', 'semi-transparent', 'transparent', 'warning'] as const; +export const DemoModeStatusBarModes = ['opaque', 'translucent', 'semi-transparent', 'transparent', 'warning'] as const; -export type AdbDemoModeStatusBarMode = (typeof AdbDemoModeStatusBarModes)[number]; +export type DemoModeStatusBarMode = (typeof DemoModeStatusBarModes)[number]; -export class AdbDemoMode extends AdbCommandBase { +export class Settings extends AdbCommandBase { + public get(key: string, global: boolean = false) { + + } +} + +export class DemoMode extends AdbCommandBase { public static readonly AllowedSettingKey = 'sysui_demo_allowed'; // Demo Mode actually doesn't have a setting indicates its enablement @@ -34,36 +40,41 @@ export class AdbDemoMode extends AdbCommandBase { // So we can only try our best to guess if it's enabled public static readonly EnabledSettingKey = 'sysui_tuner_demo_on'; + private async settings(...command: string[]): Promise { + const { stdout } = await this.adb.childProcess.spawnAndWait(command, { shells: [AdbLegacyShell] }); + return stdout.trim(); + } + public async getAllowed(): Promise { - const result = await this.adb.childProcess.exec('settings', 'get', 'global', AdbDemoMode.AllowedSettingKey); - return result.trim() === '1'; + const output = await this.settings('settings', 'get', 'global', DemoMode.AllowedSettingKey); + return output.trim() === '1'; } public async setAllowed(value: boolean): Promise { if (value) { - await this.adb.childProcess.exec('settings', 'put', 'global', AdbDemoMode.AllowedSettingKey, '1'); + await this.settings('settings', 'put', 'global', DemoMode.AllowedSettingKey, '1'); } else { await this.setEnabled(false); - await this.adb.childProcess.exec('settings', 'delete', 'global', AdbDemoMode.AllowedSettingKey); + await this.settings('settings', 'delete', 'global', DemoMode.AllowedSettingKey); } } public async getEnabled(): Promise { - const result = await this.adb.childProcess.exec('settings', 'get', 'global', AdbDemoMode.EnabledSettingKey); + const result = await this.settings('settings', 'get', 'global', DemoMode.EnabledSettingKey); return result.trim() === '1'; } public async setEnabled(value: boolean): Promise { if (value) { - await this.adb.childProcess.exec('settings', 'put', 'global', AdbDemoMode.EnabledSettingKey, '1'); + await this.settings('settings', 'put', 'global', DemoMode.EnabledSettingKey, '1'); } else { - await this.adb.childProcess.exec('settings', 'delete', 'global', AdbDemoMode.EnabledSettingKey); + await this.settings('settings', 'delete', 'global', DemoMode.EnabledSettingKey); await this.broadcast('exit'); } } public async broadcast(command: string, extra?: Record): Promise { - await this.adb.childProcess.exec( + await this.adb.childProcess.spawnAndWaitLegacy([ 'am', 'broadcast', '-a', @@ -72,7 +83,7 @@ export class AdbDemoMode extends AdbCommandBase { 'command', command, ...(extra ? Object.entries(extra).flatMap(([key, value]) => ['-e', key, value]) : []), - ); + ]); } public async setBatteryLevel(level: number): Promise { @@ -91,11 +102,11 @@ export class AdbDemoMode extends AdbCommandBase { await this.broadcast('network', { airplane: show ? 'show' : 'hide' }); } - public async setWifiSignalStrength(value: AdbDemoModeSignalStrength): Promise { + public async setWifiSignalStrength(value: DemoModeSignalStrength): Promise { await this.broadcast('network', { wifi: 'show', level: value }); } - public async setMobileDataType(value: AdbDemoModeMobileDataType): Promise { + public async setMobileDataType(value: DemoModeMobileDataType): Promise { for (let i = 0; i < 2; i += 1) { await this.broadcast('network', { mobile: 'show', @@ -113,7 +124,7 @@ export class AdbDemoMode extends AdbCommandBase { } } - public async setMobileSignalStrength(value: AdbDemoModeSignalStrength): Promise { + public async setMobileSignalStrength(value: DemoModeSignalStrength): Promise { await this.broadcast('network', { mobile: 'show', level: value }); } @@ -121,7 +132,7 @@ export class AdbDemoMode extends AdbCommandBase { await this.broadcast('network', { nosim: show ? 'show' : 'hide' }); } - public async setStatusBarMode(mode: AdbDemoModeStatusBarMode): Promise { + public async setStatusBarMode(mode: DemoModeStatusBarMode): Promise { await this.broadcast('bars', { mode }); } diff --git a/libraries/android-bin/src/index.ts b/libraries/android-bin/src/index.ts new file mode 100644 index 000000000..51939f853 --- /dev/null +++ b/libraries/android-bin/src/index.ts @@ -0,0 +1 @@ +export * from './demo-mode'; diff --git a/libraries/android-bin/tsconfig.json b/libraries/android-bin/tsconfig.json new file mode 100644 index 000000000..07f9242b1 --- /dev/null +++ b/libraries/android-bin/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "./node_modules/@yume-chan/ts-package-builder/tsconfig.base.json", + "compilerOptions": { + "lib": [ + "ESNext", + "DOM" + ], + }, + "references": [ + { + "path": "../adb/tsconfig.json" + } + ] +} diff --git a/rush.json b/rush.json index acf0619a7..871688e96 100644 --- a/rush.json +++ b/rush.json @@ -474,6 +474,12 @@ "shouldPublish": true, "versionPolicyName": "adb" }, + { + "packageName": "@yume-chan/android-bin", + "projectFolder": "libraries/android-bin", + "shouldPublish": true, + "versionPolicyName": "adb" + }, { "packageName": "@yume-chan/scrcpy", "projectFolder": "libraries/scrcpy",