@yume-chan/android-bin 1.1.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/LICENSE +1 -1
  3. package/esm/am.d.ts +4 -2
  4. package/esm/am.d.ts.map +1 -1
  5. package/esm/am.js +9 -17
  6. package/esm/am.js.map +1 -1
  7. package/esm/bu.d.ts +2 -2
  8. package/esm/bu.d.ts.map +1 -1
  9. package/esm/bu.js +6 -14
  10. package/esm/bu.js.map +1 -1
  11. package/esm/bug-report.d.ts +2 -2
  12. package/esm/bug-report.d.ts.map +1 -1
  13. package/esm/bug-report.js +15 -13
  14. package/esm/bug-report.js.map +1 -1
  15. package/esm/cmd.d.ts +18 -20
  16. package/esm/cmd.d.ts.map +1 -1
  17. package/esm/cmd.js +84 -63
  18. package/esm/cmd.js.map +1 -1
  19. package/esm/demo-mode.d.ts +2 -2
  20. package/esm/demo-mode.js +3 -3
  21. package/esm/demo-mode.js.map +1 -1
  22. package/esm/dumpsys.d.ts +2 -2
  23. package/esm/dumpsys.js +6 -6
  24. package/esm/dumpsys.js.map +1 -1
  25. package/esm/logcat.d.ts +7 -8
  26. package/esm/logcat.d.ts.map +1 -1
  27. package/esm/logcat.js +18 -10
  28. package/esm/logcat.js.map +1 -1
  29. package/esm/overlay-display.d.ts +2 -2
  30. package/esm/overlay-display.js +2 -2
  31. package/esm/pm.d.ts +7 -3
  32. package/esm/pm.d.ts.map +1 -1
  33. package/esm/pm.js +81 -88
  34. package/esm/pm.js.map +1 -1
  35. package/esm/settings.d.ts +10 -7
  36. package/esm/settings.d.ts.map +1 -1
  37. package/esm/settings.js +14 -23
  38. package/esm/settings.js.map +1 -1
  39. package/package.json +9 -9
  40. package/src/am.ts +14 -21
  41. package/src/bu.ts +6 -14
  42. package/src/bug-report.ts +16 -16
  43. package/src/cmd.ts +136 -85
  44. package/src/demo-mode.ts +3 -3
  45. package/src/dumpsys.ts +6 -6
  46. package/src/logcat.ts +26 -15
  47. package/src/overlay-display.ts +2 -2
  48. package/src/pm.ts +92 -96
  49. package/src/settings.ts +20 -28
  50. package/tsconfig.build.tsbuildinfo +1 -1
package/src/bug-report.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // cspell: ignore bugreportz
3
3
 
4
4
  import type { Adb, AdbSync } from "@yume-chan/adb";
5
- import { AdbCommandBase, AdbSubprocessShellProtocol } from "@yume-chan/adb";
5
+ import { AdbServiceBase } from "@yume-chan/adb";
6
6
  import type { AbortSignal, ReadableStream } from "@yume-chan/stream-extra";
7
7
  import {
8
8
  AbortController,
@@ -31,7 +31,7 @@ export interface BugReportZOptions {
31
31
  onProgress?: ((completed: string, total: string) => void) | undefined;
32
32
  }
33
33
 
34
- export class BugReport extends AdbCommandBase {
34
+ export class BugReport extends AdbServiceBase {
35
35
  static VERSION_REGEX: RegExp = /(\d+)\.(\d+)/;
36
36
 
37
37
  static BEGIN_REGEX: RegExp = /BEGIN:(.*)/;
@@ -47,7 +47,7 @@ export class BugReport extends AdbCommandBase {
47
47
  */
48
48
  static async queryCapabilities(adb: Adb): Promise<BugReport> {
49
49
  // bugreportz requires shell protocol
50
- if (!AdbSubprocessShellProtocol.isSupported(adb)) {
50
+ if (!adb.subprocess.shellProtocol) {
51
51
  return new BugReport(adb, {
52
52
  supportsBugReport: true,
53
53
  bugReportZVersion: undefined,
@@ -57,11 +57,11 @@ export class BugReport extends AdbCommandBase {
57
57
  });
58
58
  }
59
59
 
60
- const { stderr, exitCode } = await adb.subprocess.spawnAndWait([
60
+ const result = await adb.subprocess.shellProtocol.spawnWaitText([
61
61
  "bugreportz",
62
62
  "-v",
63
63
  ]);
64
- if (exitCode !== 0 || stderr === "") {
64
+ if (result.exitCode !== 0 || result.stderr === "") {
65
65
  return new BugReport(adb, {
66
66
  supportsBugReport: true,
67
67
  bugReportZVersion: undefined,
@@ -71,7 +71,7 @@ export class BugReport extends AdbCommandBase {
71
71
  });
72
72
  }
73
73
 
74
- const match = stderr.match(BugReport.VERSION_REGEX);
74
+ const match = result.stderr.match(BugReport.VERSION_REGEX);
75
75
  if (!match) {
76
76
  return new BugReport(adb, {
77
77
  supportsBugReport: true,
@@ -170,8 +170,9 @@ export class BugReport extends AdbCommandBase {
170
170
 
171
171
  return new WrapReadableStream(async () => {
172
172
  // https://cs.android.com/android/platform/superproject/+/master:frameworks/native/cmds/bugreport/bugreport.cpp;drc=9b73bf07d73dbab5b792632e1e233edbad77f5fd;bpv=0;bpt=0
173
- const process = await this.adb.subprocess.spawn(["bugreport"]);
174
- return process.stdout;
173
+ const process =
174
+ await this.adb.subprocess.noneProtocol.spawn("bugreport");
175
+ return process.output;
175
176
  });
176
177
  }
177
178
 
@@ -200,9 +201,8 @@ export class BugReport extends AdbCommandBase {
200
201
  args.push("-p");
201
202
  }
202
203
 
203
- const process = await this.adb.subprocess.spawn(args, {
204
- protocols: [AdbSubprocessShellProtocol],
205
- });
204
+ // `subprocess.shellProtocol` must be defined when `this.#supportsBugReportZ` is `true`
205
+ const process = await this.adb.subprocess.shellProtocol!.spawn(args);
206
206
 
207
207
  options?.signal?.addEventListener("abort", () => {
208
208
  void process.kill();
@@ -258,10 +258,10 @@ export class BugReport extends AdbCommandBase {
258
258
  */
259
259
  bugReportZStream(): ReadableStream<Uint8Array> {
260
260
  return new PushReadableStream(async (controller) => {
261
- const process = await this.adb.subprocess.spawn(
262
- ["bugreportz", "-s"],
263
- { protocols: [AdbSubprocessShellProtocol] },
264
- );
261
+ const process = await this.adb.subprocess.shellProtocol!.spawn([
262
+ "bugreportz",
263
+ "-s",
264
+ ]);
265
265
  process.stdout
266
266
  .pipeTo(
267
267
  new WritableStream({
@@ -285,7 +285,7 @@ export class BugReport extends AdbCommandBase {
285
285
  .catch((e) => {
286
286
  controller.error(e);
287
287
  });
288
- await process.exit;
288
+ await process.exited;
289
289
  });
290
290
  }
291
291
 
package/src/cmd.ts CHANGED
@@ -1,23 +1,70 @@
1
- import type {
2
- Adb,
3
- AdbSubprocessProtocol,
4
- AdbSubprocessProtocolConstructor,
5
- AdbSubprocessWaitResult,
6
- } from "@yume-chan/adb";
1
+ import type { Adb, AdbShellProtocolProcess } from "@yume-chan/adb";
7
2
  import {
8
- AdbCommandBase,
9
3
  AdbFeature,
10
- AdbSubprocessNoneProtocol,
11
- AdbSubprocessShellProtocol,
4
+ AdbNoneProtocolProcessImpl,
5
+ AdbNoneProtocolSpawner,
6
+ AdbServiceBase,
7
+ AdbShellProtocolProcessImpl,
8
+ AdbShellProtocolSpawner,
12
9
  } from "@yume-chan/adb";
13
- import { ConcatStringStream, TextDecoderStream } from "@yume-chan/stream-extra";
14
10
 
15
- export class Cmd extends AdbCommandBase {
16
- #supportsShellV2: boolean;
17
- get supportsShellV2(): boolean {
18
- return this.#supportsShellV2;
11
+ export class CmdNoneProtocolService extends AdbNoneProtocolSpawner {
12
+ #supportsAbbExec: boolean;
13
+ get supportsAbbExec(): boolean {
14
+ return this.#supportsAbbExec;
15
+ }
16
+
17
+ #supportsCmd: boolean;
18
+ get supportsCmd(): boolean {
19
+ return this.#supportsCmd;
20
+ }
21
+
22
+ get isSupported() {
23
+ return this.#supportsAbbExec || this.#supportsCmd;
19
24
  }
20
25
 
26
+ constructor(
27
+ adb: Adb,
28
+ fallback?:
29
+ | string
30
+ | Record<string, string>
31
+ | ((service: string) => string),
32
+ ) {
33
+ super(async (command) => {
34
+ if (this.#supportsAbbExec) {
35
+ return new AdbNoneProtocolProcessImpl(
36
+ await adb.createSocket(`abb_exec:${command.join("\0")}\0`),
37
+ );
38
+ }
39
+
40
+ if (this.#supportsCmd) {
41
+ return adb.subprocess.noneProtocol.spawn(
42
+ `cmd ${command.join(" ")}`,
43
+ );
44
+ }
45
+
46
+ if (typeof fallback === "function") {
47
+ fallback = fallback(command[0]!);
48
+ } else if (typeof fallback === "object") {
49
+ fallback = fallback[command[0]!];
50
+ }
51
+
52
+ if (!fallback) {
53
+ throw new Error("Unsupported");
54
+ }
55
+
56
+ command[0] = fallback;
57
+ return adb.subprocess.noneProtocol.spawn(command);
58
+ });
59
+
60
+ this.#supportsCmd = adb.canUseFeature(AdbFeature.Cmd);
61
+ this.#supportsAbbExec = adb.canUseFeature(AdbFeature.AbbExec);
62
+ }
63
+ }
64
+
65
+ export class CmdShellProtocolService extends AdbShellProtocolSpawner {
66
+ #adb: Adb;
67
+
21
68
  #supportsCmd: boolean;
22
69
  get supportsCmd(): boolean {
23
70
  return this.#supportsCmd;
@@ -28,86 +75,90 @@ export class Cmd extends AdbCommandBase {
28
75
  return this.#supportsAbb;
29
76
  }
30
77
 
31
- #supportsAbbExec: boolean;
32
- get supportsAbbExec(): boolean {
33
- return this.#supportsAbbExec;
78
+ get isSupported() {
79
+ return (
80
+ this.#supportsAbb ||
81
+ (this.#supportsCmd && !!this.#adb.subprocess.shellProtocol)
82
+ );
34
83
  }
35
84
 
36
- constructor(adb: Adb) {
37
- super(adb);
38
- this.#supportsShellV2 = adb.canUseFeature(AdbFeature.ShellV2);
85
+ constructor(
86
+ adb: Adb,
87
+ fallback?:
88
+ | string
89
+ | Record<string, string>
90
+ | ((service: string) => string),
91
+ ) {
92
+ super(async (command): Promise<AdbShellProtocolProcess> => {
93
+ if (this.#supportsAbb) {
94
+ return new AdbShellProtocolProcessImpl(
95
+ await this.#adb.createSocket(`abb:${command.join("\0")}\0`),
96
+ );
97
+ }
98
+
99
+ if (!adb.subprocess.shellProtocol) {
100
+ throw new Error("Unsupported");
101
+ }
102
+
103
+ if (this.#supportsCmd) {
104
+ return adb.subprocess.shellProtocol.spawn(
105
+ `cmd ${command.join(" ")}`,
106
+ );
107
+ }
108
+
109
+ if (typeof fallback === "function") {
110
+ fallback = fallback(command[0]!);
111
+ } else if (typeof fallback === "object") {
112
+ fallback = fallback[command[0]!];
113
+ }
114
+
115
+ if (!fallback) {
116
+ throw new Error("Unsupported");
117
+ }
118
+
119
+ command[0] = fallback;
120
+ return adb.subprocess.shellProtocol.spawn(command);
121
+ });
122
+
123
+ this.#adb = adb;
39
124
  this.#supportsCmd = adb.canUseFeature(AdbFeature.Cmd);
40
125
  this.#supportsAbb = adb.canUseFeature(AdbFeature.Abb);
41
- this.#supportsAbbExec = adb.canUseFeature(AdbFeature.AbbExec);
42
126
  }
127
+ }
43
128
 
44
- /**
45
- * Spawn a new `cmd` command. It will use ADB's `abb` command if available.
46
- *
47
- * @param shellProtocol
48
- * Whether to use shell protocol. If `true`, `stdout` and `stderr` will be separated.
49
- *
50
- * `cmd` doesn't use PTY, so even when shell protocol is used,
51
- * resizing terminal size and closing `stdin` are not supported.
52
- * @param command The command to run.
53
- * @param args The arguments to pass to the command.
54
- * @returns An `AdbSubprocessProtocol` that provides output streams.
55
- */
56
- async spawn(
57
- shellProtocol: boolean,
58
- command: string,
59
- ...args: string[]
60
- ): Promise<AdbSubprocessProtocol> {
61
- let supportsAbb: boolean;
62
- let supportsCmd: boolean = this.#supportsCmd;
63
- let service: string;
64
- let Protocol: AdbSubprocessProtocolConstructor;
65
- if (shellProtocol) {
66
- supportsAbb = this.#supportsAbb;
67
- supportsCmd &&= this.supportsShellV2;
68
- service = "abb";
69
- Protocol = AdbSubprocessShellProtocol;
70
- } else {
71
- supportsAbb = this.#supportsAbbExec;
72
- service = "abb_exec";
73
- Protocol = AdbSubprocessNoneProtocol;
74
- }
129
+ export class Cmd extends AdbServiceBase {
130
+ #noneProtocol: CmdNoneProtocolService | undefined;
131
+ get noneProtocol() {
132
+ return this.#noneProtocol;
133
+ }
75
134
 
76
- if (supportsAbb) {
77
- return new Protocol(
78
- await this.adb.createSocket(
79
- `${service}:${command}\0${args.join("\0")}\0`,
80
- ),
81
- );
82
- }
135
+ #shellProtocol: CmdShellProtocolService | undefined;
136
+ get shellProtocol() {
137
+ return this.#shellProtocol;
138
+ }
83
139
 
84
- if (supportsCmd) {
85
- return Protocol.raw(this.adb, `cmd ${command} ${args.join(" ")}`);
86
- }
140
+ constructor(
141
+ adb: Adb,
142
+ fallback?:
143
+ | string
144
+ | Record<string, string>
145
+ | ((service: string) => string),
146
+ ) {
147
+ super(adb);
87
148
 
88
- throw new Error("Not supported");
89
- }
149
+ if (
150
+ adb.canUseFeature(AdbFeature.AbbExec) ||
151
+ adb.canUseFeature(AdbFeature.Cmd)
152
+ ) {
153
+ this.#noneProtocol = new CmdNoneProtocolService(adb, fallback);
154
+ }
90
155
 
91
- async spawnAndWait(
92
- command: string,
93
- ...args: string[]
94
- ): Promise<AdbSubprocessWaitResult> {
95
- const process = await this.spawn(true, command, ...args);
96
-
97
- const [stdout, stderr, exitCode] = await Promise.all([
98
- process.stdout
99
- .pipeThrough(new TextDecoderStream())
100
- .pipeThrough(new ConcatStringStream()),
101
- process.stderr
102
- .pipeThrough(new TextDecoderStream())
103
- .pipeThrough(new ConcatStringStream()),
104
- process.exit,
105
- ]);
106
-
107
- return {
108
- stdout,
109
- stderr,
110
- exitCode,
111
- };
156
+ if (
157
+ adb.canUseFeature(AdbFeature.Abb) ||
158
+ (adb.canUseFeature(AdbFeature.Cmd) &&
159
+ adb.canUseFeature(AdbFeature.ShellV2))
160
+ ) {
161
+ this.#shellProtocol = new CmdShellProtocolService(adb, fallback);
162
+ }
112
163
  }
113
164
  }
package/src/demo-mode.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  // cspell: ignore sysui
6
6
 
7
7
  import type { Adb } from "@yume-chan/adb";
8
- import { AdbCommandBase } from "@yume-chan/adb";
8
+ import { AdbServiceBase } from "@yume-chan/adb";
9
9
 
10
10
  import { Settings } from "./settings.js";
11
11
 
@@ -51,7 +51,7 @@ export const DemoModeStatusBarModes = [
51
51
 
52
52
  export type DemoModeStatusBarMode = (typeof DemoModeStatusBarModes)[number];
53
53
 
54
- export class DemoMode extends AdbCommandBase {
54
+ export class DemoMode extends AdbServiceBase {
55
55
  #settings: Settings;
56
56
 
57
57
  constructor(adb: Adb) {
@@ -112,7 +112,7 @@ export class DemoMode extends AdbCommandBase {
112
112
  command: string,
113
113
  extra?: Record<string, string>,
114
114
  ): Promise<void> {
115
- await this.adb.subprocess.spawnAndWaitLegacy([
115
+ await this.adb.subprocess.noneProtocol.spawnWaitText([
116
116
  "am",
117
117
  "broadcast",
118
118
  "-a",
package/src/dumpsys.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AdbCommandBase } from "@yume-chan/adb";
1
+ import { AdbServiceBase } from "@yume-chan/adb";
2
2
 
3
3
  const BatteryDumpFields: Record<
4
4
  string,
@@ -54,17 +54,17 @@ const Battery = {
54
54
  Health,
55
55
  };
56
56
 
57
- export class DumpSys extends AdbCommandBase {
57
+ export class DumpSys extends AdbServiceBase {
58
58
  static readonly Battery = Battery;
59
59
 
60
60
  async diskStats() {
61
- const output = await this.adb.subprocess.spawnAndWaitLegacy([
61
+ const result = await this.adb.subprocess.noneProtocol.spawnWaitText([
62
62
  "dumpsys",
63
63
  "diskstats",
64
64
  ]);
65
65
 
66
66
  function getSize(name: string) {
67
- const match = output.match(
67
+ const match = result.match(
68
68
  new RegExp(`${name}-Free: (\\d+)K / (\\d+)K`),
69
69
  );
70
70
  if (!match) {
@@ -91,7 +91,7 @@ export class DumpSys extends AdbCommandBase {
91
91
  }
92
92
 
93
93
  async battery(): Promise<DumpSys.Battery.Info> {
94
- const output = await this.adb.subprocess.spawnAndWaitLegacy([
94
+ const result = await this.adb.subprocess.noneProtocol.spawnWaitText([
95
95
  "dumpsys",
96
96
  "battery",
97
97
  ]);
@@ -105,7 +105,7 @@ export class DumpSys extends AdbCommandBase {
105
105
  health: DumpSys.Battery.Health.Unknown,
106
106
  };
107
107
 
108
- for (const line of output.split("\n")) {
108
+ for (const line of result.split("\n")) {
109
109
  const parts = line.split(":").map((part) => part.trim());
110
110
  if (parts.length !== 2) {
111
111
  continue;
package/src/logcat.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  // cspell: ignore logcat
2
2
  // cspell: ignore usec
3
3
 
4
- import { AdbCommandBase, AdbSubprocessNoneProtocol } from "@yume-chan/adb";
4
+ import { AdbServiceBase } from "@yume-chan/adb";
5
5
  import type { ReadableStream } from "@yume-chan/stream-extra";
6
6
  import {
7
7
  BufferedTransformStream,
@@ -80,19 +80,25 @@ export type LogcatFormat = (typeof LogcatFormat)[keyof typeof LogcatFormat];
80
80
  export interface LogcatFormatModifiers {
81
81
  microseconds?: boolean;
82
82
  nanoseconds?: boolean;
83
- printable?: boolean;
84
83
  year?: boolean;
85
84
  timezone?: boolean;
86
85
  epoch?: boolean;
87
86
  monotonic?: boolean;
88
87
  uid?: boolean;
89
- descriptive?: boolean;
90
88
  }
91
89
 
92
90
  export interface LogcatOptions {
93
- dump?: boolean;
94
- pid?: number;
95
- ids?: LogId[];
91
+ dump?: boolean | undefined;
92
+ pid?: number | undefined;
93
+ ids?: LogId[] | undefined;
94
+ tail?: number | Date | undefined;
95
+ }
96
+
97
+ function formatTailTime(date: Date) {
98
+ // Tail time supports multiple formats,
99
+ // `sssss.mmm` is simplest to implement
100
+ const timestamp = date.getTime();
101
+ return ((timestamp / 1000) | 0) + "." + (timestamp % 1000);
96
102
  }
97
103
 
98
104
  const NANOSECONDS_PER_SECOND = /* #__PURE__ */ BigInt(1e9);
@@ -399,7 +405,7 @@ export interface LogSize {
399
405
  maxPayloadSize: number;
400
406
  }
401
407
 
402
- export class Logcat extends AdbCommandBase {
408
+ export class Logcat extends AdbServiceBase {
403
409
  static logIdToName(id: LogId): string {
404
410
  return LogIdName[id]!;
405
411
  }
@@ -429,14 +435,14 @@ export class Logcat extends AdbCommandBase {
429
435
  /(.*): ring buffer is (.*) (.*)B \((.*) (.*)B consumed, (.*) (.*)B readable\), max entry is (.*) B, max payload is (.*) B/;
430
436
 
431
437
  async getLogSize(ids?: LogId[]): Promise<LogSize[]> {
432
- const { stdout } = await this.adb.subprocess.spawn([
438
+ const process = await this.adb.subprocess.noneProtocol.spawn([
433
439
  "logcat",
434
440
  "-g",
435
441
  ...(ids ? ["-b", Logcat.joinLogId(ids)] : []),
436
442
  ]);
437
443
 
438
444
  const result: LogSize[] = [];
439
- for await (const line of stdout
445
+ for await (const line of process.output
440
446
  .pipeThrough(new TextDecoderStream())
441
447
  .pipeThrough(new SplitStringStream("\n"))) {
442
448
  let match = line.match(Logcat.LOG_SIZE_REGEX_11);
@@ -488,7 +494,7 @@ export class Logcat extends AdbCommandBase {
488
494
  args.push("-b", Logcat.joinLogId(ids));
489
495
  }
490
496
 
491
- await this.adb.subprocess.spawnAndWait(args);
497
+ await this.adb.subprocess.noneProtocol.spawnWaitText(args);
492
498
  }
493
499
 
494
500
  binary(options?: LogcatOptions): ReadableStream<AndroidLogEntry> {
@@ -503,14 +509,19 @@ export class Logcat extends AdbCommandBase {
503
509
  if (options?.ids) {
504
510
  args.push("-b", Logcat.joinLogId(options.ids));
505
511
  }
512
+ if (options?.tail) {
513
+ args.push(
514
+ "-t",
515
+ typeof options.tail === "number"
516
+ ? options.tail.toString()
517
+ : formatTailTime(options.tail),
518
+ );
519
+ }
506
520
 
507
521
  // TODO: make `spawn` return synchronously with streams pending
508
522
  // so it's easier to chain them.
509
- const { stdout } = await this.adb.subprocess.spawn(args, {
510
- // PERF: None protocol is 150% faster then Shell protocol
511
- protocols: [AdbSubprocessNoneProtocol],
512
- });
513
- return stdout;
523
+ const process = await this.adb.subprocess.noneProtocol.spawn(args);
524
+ return process.output;
514
525
  }).pipeThrough(
515
526
  new BufferedTransformStream((stream) => {
516
527
  return deserializeAndroidLogEntry(stream);
@@ -1,5 +1,5 @@
1
1
  import type { Adb } from "@yume-chan/adb";
2
- import { AdbCommandBase } from "@yume-chan/adb";
2
+ import { AdbServiceBase } from "@yume-chan/adb";
3
3
 
4
4
  import { Settings } from "./settings.js";
5
5
  import { p } from "./string-format.js";
@@ -17,7 +17,7 @@ export interface OverlayDisplayDevice {
17
17
  showSystemDecorations: boolean;
18
18
  }
19
19
 
20
- export class OverlayDisplay extends AdbCommandBase {
20
+ export class OverlayDisplay extends AdbServiceBase {
21
21
  #settings: Settings;
22
22
 
23
23
  static readonly SETTING_KEY = "overlay_display_devices";