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": {
"@yume-chan/adb": "workspace:^0.0.20",
"@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",
"commander": "^10.0.1",
"source-map-support": "^0.5.21",
"tslib": "^2.5.2"
},
"devDependencies": {
"@types/node": "^20.2.1",
"@yume-chan/eslint-config": "workspace:^1.0.0",
"@yume-chan/tsconfig": "workspace:^1.0.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.
{
"pnpmShrinkwrapHash": "44a16b54cd6e6ab0bca0baf63df4de9d614e3857",
"pnpmShrinkwrapHash": "749c0b292fb7bef0d2e3e177d6dc093da3453d0c",
"preferredVersionsHash": "bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f"
}

View file

@ -108,15 +108,15 @@ export class AdbSubprocess extends AdbCommandBase {
command: string | string[],
options?: Partial<AdbSubprocessOptions>
): Promise<AdbSubprocessWaitResult> {
const shell = await this.spawn(command, options);
const process = await this.spawn(command, options);
const stdout = new GatherStringStream();
const stderr = new GatherStringStream();
const [, , exitCode] = await Promise.all([
shell.stdout.pipeThrough(new DecodeUtf8Stream()).pipeTo(stdout),
shell.stderr.pipeThrough(new DecodeUtf8Stream()).pipeTo(stderr),
shell.exit,
process.stdout.pipeThrough(new DecodeUtf8Stream()).pipeTo(stdout),
process.stderr.pipeThrough(new DecodeUtf8Stream()).pipeTo(stderr),
process.exit,
]);
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 {
AdbCommandBase,
AdbFeature,
AdbSubprocessNoneProtocol,
AdbSubprocessShellProtocol,
} from "@yume-chan/adb";
import { DecodeUtf8Stream, GatherStringStream } from "@yume-chan/stream-extra";
export class Cmd extends AdbCommandBase {
private _supportsShellV2: boolean;
private _supportsCmd: boolean;
private _supportsAbb: boolean;
private _supportsAbbExec: boolean;
#supportsShellV2: boolean;
public get supportsShellV2() {
return this._supportsShellV2;
return this.#supportsShellV2;
}
#supportsCmd: boolean;
public get supportsCmd() {
return this._supportsCmd;
return this.#supportsCmd;
}
#supportsAbb: boolean;
public get supportsAbb() {
return this._supportsAbb;
return this.#supportsAbb;
}
#supportsAbbExec: boolean;
public get supportsAbbExec() {
return this._supportsAbbExec;
return this.#supportsAbbExec;
}
public constructor(adb: Adb) {
super(adb);
this._supportsShellV2 = adb.supportsFeature(AdbFeature.ShellV2);
this._supportsCmd = adb.supportsFeature(AdbFeature.Cmd);
this._supportsAbb = adb.supportsFeature(AdbFeature.Abb);
this._supportsAbbExec = adb.supportsFeature(AdbFeature.AbbExec);
this.#supportsShellV2 = adb.supportsFeature(AdbFeature.ShellV2);
this.#supportsCmd = adb.supportsFeature(AdbFeature.Cmd);
this.#supportsAbb = adb.supportsFeature(AdbFeature.Abb);
this.#supportsAbbExec = adb.supportsFeature(AdbFeature.AbbExec);
}
public async spawn(
shellProtocol: boolean,
command: string,
...args: string[]
) {
): Promise<AdbSubprocessProtocol> {
let supportsAbb: boolean;
let supportsCmd: boolean = this.supportsCmd;
let supportsCmd: boolean = this.#supportsCmd;
let service: string;
let Protocol: AdbSubprocessProtocolConstructor;
if (shellProtocol) {
supportsAbb = this._supportsAbb;
supportsAbb = this.#supportsAbb;
supportsCmd &&= this.supportsShellV2;
service = "abb";
Protocol = AdbSubprocessShellProtocol;
} else {
supportsAbb = this._supportsAbbExec;
supportsAbb = this.#supportsAbbExec;
service = "abb_exec";
Protocol = AdbSubprocessNoneProtocol;
}
if (supportsAbb) {
const socket = await this.adb.createSocket(
return new Protocol(
await this.adb.createSocket(
`${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(" ")}`);
} else {
}
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",
DemoMode.AllowedSettingKey
);
return output.trim() === "1";
return output === "1";
}
public async setAllowed(value: boolean): Promise<void> {
@ -88,7 +88,7 @@ export class DemoMode extends AdbCommandBase {
"global",
DemoMode.EnabledSettingKey
);
return result.trim() === "1";
return result === "1";
}
public async setEnabled(value: boolean): Promise<void> {

View file

@ -4,5 +4,6 @@ export * from "./bug-report.js";
export * from "./cmd.js";
export * from "./demo-mode.js";
export * from "./logcat.js";
export * from "./overlay-display.js";
export * from "./pm.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 { Cmd } from "./cmd.js";
export type SettingsNamespace = "system" | "secure" | "global";
export type SettingsResetMode =
| "untrusted_defaults"
| "untrusted_clear"
| "trusted_defaults";
export enum SettingsResetMode {
UntrustedDefaults = "untrusted_defaults",
UntrustedClear = "untrusted_clear",
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
export class Settings extends AdbCommandBase {
// TODO: `--user <user>` argument
#cmd: Cmd;
public base(
command: string,
public constructor(adb: Adb) {
super(adb);
this.#cmd = new Cmd(adb);
}
public async base(
verb: string,
namespace: SettingsNamespace,
options: SettingsOptions | undefined,
...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([
"settings",
command,
namespace,
...args,
]);
const output = await this.base("get", namespace, options, key);
// Remove last \n
return output.substring(0, output.length - 1);
}
public get(namespace: SettingsNamespace, key: string) {
return this.base("get", namespace, key);
public async delete(
namespace: SettingsNamespace,
key: string,
options?: SettingsOptions
): Promise<void> {
await this.base("delete", namespace, options, key);
}
public delete(namespace: SettingsNamespace, key: string) {
return this.base("delete", namespace, key);
}
public put(
public async put(
namespace: SettingsNamespace,
key: string,
value: string,
tag?: string,
makeDefault?: boolean
) {
options?: SettingsPutOptions
): Promise<void> {
const args = [key, value];
if (tag) {
args.push(tag);
if (options?.tag) {
args.push(options.tag);
}
if (makeDefault) {
if (options?.makeDefault) {
args.push("default");
}
return this.base("put", namespace, ...args);
await this.base("put", namespace, options, ...args);
}
public reset(
namespace: SettingsNamespace,
mode: SettingsResetMode
): Promise<string>;
mode: SettingsResetMode,
options?: SettingsOptions
): Promise<void>;
public reset(
namespace: SettingsNamespace,
packageName: string,
tag?: string
): Promise<string>;
public reset(
tag?: string,
options?: SettingsOptions
): Promise<void>;
public async reset(
namespace: SettingsNamespace,
modeOrPackageName: string,
tag?: string
): Promise<string> {
tagOrOptions?: string | SettingsOptions,
options?: SettingsOptions
): Promise<void> {
const args = [modeOrPackageName];
if (tag) {
args.push(tag);
if (
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);
}
public async list(namespace: SettingsNamespace): Promise<string[]> {
const output = await this.base("list", namespace);
return output.split("\n");
await this.base("reset", namespace, options, ...args);
}
}