feat(bin): use cmd in settings

This commit is contained in:
Simon Chan 2023-06-22 19:55:45 +08:00
parent 9b0e06cdfb
commit e3bfd1592f
No known key found for this signature in database
GPG key ID: A8B69F750B9BCEDD
8 changed files with 1490 additions and 1495 deletions

View file

@ -33,12 +33,14 @@
"dependencies": { "dependencies": {
"@yume-chan/adb": "workspace:^0.0.20", "@yume-chan/adb": "workspace:^0.0.20",
"@yume-chan/adb-server-node-tcp": "workspace:^0.0.19", "@yume-chan/adb-server-node-tcp": "workspace:^0.0.19",
"@yume-chan/android-bin": "workspace:^0.0.20",
"@yume-chan/stream-extra": "workspace:^0.0.20", "@yume-chan/stream-extra": "workspace:^0.0.20",
"commander": "^10.0.1", "commander": "^10.0.1",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
"tslib": "^2.5.2" "tslib": "^2.5.2"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20.2.1",
"@yume-chan/eslint-config": "workspace:^1.0.0", "@yume-chan/eslint-config": "workspace:^1.0.0",
"@yume-chan/tsconfig": "workspace:^1.0.0", "@yume-chan/tsconfig": "workspace:^1.0.0",
"eslint": "^8.41.0", "eslint": "^8.41.0",

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
// DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush. // DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush.
{ {
"pnpmShrinkwrapHash": "44a16b54cd6e6ab0bca0baf63df4de9d614e3857", "pnpmShrinkwrapHash": "749c0b292fb7bef0d2e3e177d6dc093da3453d0c",
"preferredVersionsHash": "bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f" "preferredVersionsHash": "bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f"
} }

View file

@ -108,15 +108,15 @@ export class AdbSubprocess extends AdbCommandBase {
command: string | string[], command: string | string[],
options?: Partial<AdbSubprocessOptions> options?: Partial<AdbSubprocessOptions>
): Promise<AdbSubprocessWaitResult> { ): Promise<AdbSubprocessWaitResult> {
const shell = await this.spawn(command, options); const process = await this.spawn(command, options);
const stdout = new GatherStringStream(); const stdout = new GatherStringStream();
const stderr = new GatherStringStream(); const stderr = new GatherStringStream();
const [, , exitCode] = await Promise.all([ const [, , exitCode] = await Promise.all([
shell.stdout.pipeThrough(new DecodeUtf8Stream()).pipeTo(stdout), process.stdout.pipeThrough(new DecodeUtf8Stream()).pipeTo(stdout),
shell.stderr.pipeThrough(new DecodeUtf8Stream()).pipeTo(stderr), process.stderr.pipeThrough(new DecodeUtf8Stream()).pipeTo(stderr),
shell.exit, process.exit,
]); ]);
return { return {

View file

@ -1,67 +1,100 @@
import type { Adb, AdbSubprocessProtocolConstructor } from "@yume-chan/adb"; import type {
Adb,
AdbSubprocessProtocol,
AdbSubprocessProtocolConstructor,
AdbSubprocessWaitResult,
} from "@yume-chan/adb";
import { import {
AdbCommandBase, AdbCommandBase,
AdbFeature, AdbFeature,
AdbSubprocessNoneProtocol, AdbSubprocessNoneProtocol,
AdbSubprocessShellProtocol, AdbSubprocessShellProtocol,
} from "@yume-chan/adb"; } from "@yume-chan/adb";
import { DecodeUtf8Stream, GatherStringStream } from "@yume-chan/stream-extra";
export class Cmd extends AdbCommandBase { export class Cmd extends AdbCommandBase {
private _supportsShellV2: boolean; #supportsShellV2: boolean;
private _supportsCmd: boolean;
private _supportsAbb: boolean;
private _supportsAbbExec: boolean;
public get supportsShellV2() { public get supportsShellV2() {
return this._supportsShellV2; return this.#supportsShellV2;
} }
#supportsCmd: boolean;
public get supportsCmd() { public get supportsCmd() {
return this._supportsCmd; return this.#supportsCmd;
} }
#supportsAbb: boolean;
public get supportsAbb() { public get supportsAbb() {
return this._supportsAbb; return this.#supportsAbb;
} }
#supportsAbbExec: boolean;
public get supportsAbbExec() { public get supportsAbbExec() {
return this._supportsAbbExec; return this.#supportsAbbExec;
} }
public constructor(adb: Adb) { public constructor(adb: Adb) {
super(adb); super(adb);
this._supportsShellV2 = adb.supportsFeature(AdbFeature.ShellV2); this.#supportsShellV2 = adb.supportsFeature(AdbFeature.ShellV2);
this._supportsCmd = adb.supportsFeature(AdbFeature.Cmd); this.#supportsCmd = adb.supportsFeature(AdbFeature.Cmd);
this._supportsAbb = adb.supportsFeature(AdbFeature.Abb); this.#supportsAbb = adb.supportsFeature(AdbFeature.Abb);
this._supportsAbbExec = adb.supportsFeature(AdbFeature.AbbExec); this.#supportsAbbExec = adb.supportsFeature(AdbFeature.AbbExec);
} }
public async spawn( public async spawn(
shellProtocol: boolean, shellProtocol: boolean,
command: string, command: string,
...args: string[] ...args: string[]
) { ): Promise<AdbSubprocessProtocol> {
let supportsAbb: boolean; let supportsAbb: boolean;
let supportsCmd: boolean = this.supportsCmd; let supportsCmd: boolean = this.#supportsCmd;
let service: string; let service: string;
let Protocol: AdbSubprocessProtocolConstructor; let Protocol: AdbSubprocessProtocolConstructor;
if (shellProtocol) { if (shellProtocol) {
supportsAbb = this._supportsAbb; supportsAbb = this.#supportsAbb;
supportsCmd &&= this.supportsShellV2; supportsCmd &&= this.supportsShellV2;
service = "abb"; service = "abb";
Protocol = AdbSubprocessShellProtocol; Protocol = AdbSubprocessShellProtocol;
} else { } else {
supportsAbb = this._supportsAbbExec; supportsAbb = this.#supportsAbbExec;
service = "abb_exec"; service = "abb_exec";
Protocol = AdbSubprocessNoneProtocol; Protocol = AdbSubprocessNoneProtocol;
} }
if (supportsAbb) { if (supportsAbb) {
const socket = await this.adb.createSocket( return new Protocol(
await this.adb.createSocket(
`${service}:${command}\0${args.join("\0")}\0` `${service}:${command}\0${args.join("\0")}\0`
)
); );
return new Protocol(socket); }
} else if (supportsCmd) {
if (supportsCmd) {
return Protocol.raw(this.adb, `cmd ${command} ${args.join(" ")}`); return Protocol.raw(this.adb, `cmd ${command} ${args.join(" ")}`);
} else { }
throw new Error("Not supported"); throw new Error("Not supported");
} }
public async spawnAndWait(
command: string,
...args: string[]
): Promise<AdbSubprocessWaitResult> {
const process = await this.spawn(true, command, ...args);
const stdout = new GatherStringStream();
const stderr = new GatherStringStream();
const [, , exitCode] = await Promise.all([
process.stdout.pipeThrough(new DecodeUtf8Stream()).pipeTo(stdout),
process.stderr.pipeThrough(new DecodeUtf8Stream()).pipeTo(stderr),
process.exit,
]);
return {
stdout: stdout.result,
stderr: stderr.result,
exitCode,
};
} }
} }

View file

@ -71,7 +71,7 @@ export class DemoMode extends AdbCommandBase {
"global", "global",
DemoMode.AllowedSettingKey DemoMode.AllowedSettingKey
); );
return output.trim() === "1"; return output === "1";
} }
public async setAllowed(value: boolean): Promise<void> { public async setAllowed(value: boolean): Promise<void> {
@ -88,7 +88,7 @@ export class DemoMode extends AdbCommandBase {
"global", "global",
DemoMode.EnabledSettingKey DemoMode.EnabledSettingKey
); );
return result.trim() === "1"; return result === "1";
} }
public async setEnabled(value: boolean): Promise<void> { public async setEnabled(value: boolean): Promise<void> {

View file

@ -4,5 +4,6 @@ export * from "./bug-report.js";
export * from "./cmd.js"; export * from "./cmd.js";
export * from "./demo-mode.js"; export * from "./demo-mode.js";
export * from "./logcat.js"; export * from "./logcat.js";
export * from "./overlay-display.js";
export * from "./pm.js"; export * from "./pm.js";
export * from "./settings.js"; export * from "./settings.js";

View file

@ -1,77 +1,127 @@
import type { Adb, AdbSubprocessWaitResult } from "@yume-chan/adb";
import { AdbCommandBase } from "@yume-chan/adb"; import { AdbCommandBase } from "@yume-chan/adb";
import { Cmd } from "./cmd.js";
export type SettingsNamespace = "system" | "secure" | "global"; export type SettingsNamespace = "system" | "secure" | "global";
export type SettingsResetMode = export enum SettingsResetMode {
| "untrusted_defaults" UntrustedDefaults = "untrusted_defaults",
| "untrusted_clear" UntrustedClear = "untrusted_clear",
| "trusted_defaults"; TrustedDefaults = "trusted_defaults",
}
export interface SettingsOptions {
user?: number | "current";
}
export interface SettingsPutOptions extends SettingsOptions {
tag?: string;
makeDefault?: boolean;
}
// frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java // frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java
export class Settings extends AdbCommandBase { export class Settings extends AdbCommandBase {
// TODO: `--user <user>` argument #cmd: Cmd;
public base( public constructor(adb: Adb) {
command: string, super(adb);
this.#cmd = new Cmd(adb);
}
public async base(
verb: string,
namespace: SettingsNamespace, namespace: SettingsNamespace,
options: SettingsOptions | undefined,
...args: string[] ...args: string[]
): Promise<string> {
let command = ["settings"];
if (options?.user !== undefined) {
command.push("--user", options.user.toString());
}
command.push(verb, namespace);
command = command.concat(args);
let output: AdbSubprocessWaitResult;
if (this.#cmd.supportsCmd) {
output = await this.#cmd.spawnAndWait(
command[0]!,
...command.slice(1)
);
} else {
output = await this.adb.subprocess.spawnAndWait(command);
}
if (output.stderr) {
throw new Error(output.stderr);
}
return output.stdout;
}
public async get(
namespace: SettingsNamespace,
key: string,
options?: SettingsOptions
) { ) {
return this.adb.subprocess.spawnAndWaitLegacy([ const output = await this.base("get", namespace, options, key);
"settings", // Remove last \n
command, return output.substring(0, output.length - 1);
namespace,
...args,
]);
} }
public get(namespace: SettingsNamespace, key: string) { public async delete(
return this.base("get", namespace, key); namespace: SettingsNamespace,
key: string,
options?: SettingsOptions
): Promise<void> {
await this.base("delete", namespace, options, key);
} }
public delete(namespace: SettingsNamespace, key: string) { public async put(
return this.base("delete", namespace, key);
}
public put(
namespace: SettingsNamespace, namespace: SettingsNamespace,
key: string, key: string,
value: string, value: string,
tag?: string, options?: SettingsPutOptions
makeDefault?: boolean ): Promise<void> {
) {
const args = [key, value]; const args = [key, value];
if (tag) { if (options?.tag) {
args.push(tag); args.push(options.tag);
} }
if (makeDefault) { if (options?.makeDefault) {
args.push("default"); args.push("default");
} }
return this.base("put", namespace, ...args); await this.base("put", namespace, options, ...args);
} }
public reset( public reset(
namespace: SettingsNamespace, namespace: SettingsNamespace,
mode: SettingsResetMode mode: SettingsResetMode,
): Promise<string>; options?: SettingsOptions
): Promise<void>;
public reset( public reset(
namespace: SettingsNamespace, namespace: SettingsNamespace,
packageName: string, packageName: string,
tag?: string tag?: string,
): Promise<string>; options?: SettingsOptions
public reset( ): Promise<void>;
public async reset(
namespace: SettingsNamespace, namespace: SettingsNamespace,
modeOrPackageName: string, modeOrPackageName: string,
tag?: string tagOrOptions?: string | SettingsOptions,
): Promise<string> { options?: SettingsOptions
): Promise<void> {
const args = [modeOrPackageName]; const args = [modeOrPackageName];
if (tag) { if (
args.push(tag); modeOrPackageName === SettingsResetMode.UntrustedDefaults ||
modeOrPackageName === SettingsResetMode.UntrustedClear ||
modeOrPackageName === SettingsResetMode.TrustedDefaults
) {
options = tagOrOptions as SettingsOptions;
} else if (typeof tagOrOptions === "string") {
args.push(tagOrOptions);
} }
return this.base("reset", namespace, ...args); await this.base("reset", namespace, options, ...args);
}
public async list(namespace: SettingsNamespace): Promise<string[]> {
const output = await this.base("list", namespace);
return output.split("\n");
} }
} }