@yume-chan/android-bin 0.0.22 → 0.0.24
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.
- package/CHANGELOG.json +27 -0
- package/CHANGELOG.md +16 -1
- package/LICENSE +1 -1
- package/esm/am.js +2 -2
- package/esm/am.js.map +1 -1
- package/esm/bu.d.ts +2 -2
- package/esm/bu.d.ts.map +1 -1
- package/esm/bu.js +2 -2
- package/esm/bu.js.map +1 -1
- package/esm/bug-report.js +3 -3
- package/esm/bug-report.js.map +1 -1
- package/esm/cmd.d.ts +12 -0
- package/esm/cmd.d.ts.map +1 -1
- package/esm/cmd.js +19 -7
- package/esm/cmd.js.map +1 -1
- package/esm/dumpsys.d.ts +19 -11
- package/esm/dumpsys.d.ts.map +1 -1
- package/esm/dumpsys.js +45 -46
- package/esm/dumpsys.js.map +1 -1
- package/esm/logcat.d.ts.map +1 -1
- package/esm/logcat.js +3 -2
- package/esm/logcat.js.map +1 -1
- package/esm/pm.d.ts +38 -3
- package/esm/pm.d.ts.map +1 -1
- package/esm/pm.js +170 -31
- package/esm/pm.js.map +1 -1
- package/esm/string-format.d.ts.map +1 -1
- package/esm/string-format.js +0 -1
- package/esm/string-format.js.map +1 -1
- package/package.json +11 -13
- package/src/am.ts +2 -2
- package/src/bu.ts +4 -4
- package/src/bug-report.ts +3 -3
- package/src/cmd.ts +19 -7
- package/src/dumpsys.ts +74 -50
- package/src/logcat.ts +4 -3
- package/src/pm.ts +214 -40
- package/src/string-format.ts +0 -1
- package/tsconfig.build.tsbuildinfo +1 -1
package/src/dumpsys.ts
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
import { AdbCommandBase } from "@yume-chan/adb";
|
|
2
2
|
|
|
3
|
+
const BatteryDumpFields: Record<
|
|
4
|
+
string,
|
|
5
|
+
{
|
|
6
|
+
type: "number" | "boolean" | "string";
|
|
7
|
+
field: keyof DumpSys.Battery.Info;
|
|
8
|
+
}
|
|
9
|
+
> = {
|
|
10
|
+
"AC powered": { type: "boolean", field: "acPowered" },
|
|
11
|
+
"USB powered": { type: "boolean", field: "usbPowered" },
|
|
12
|
+
"Wireless powered": { type: "boolean", field: "wirelessPowered" },
|
|
13
|
+
"Dock powered": { type: "boolean", field: "dockPowered" },
|
|
14
|
+
"Max charging current": {
|
|
15
|
+
type: "number",
|
|
16
|
+
field: "maxChargingCurrent",
|
|
17
|
+
},
|
|
18
|
+
"Max charging voltage": {
|
|
19
|
+
type: "number",
|
|
20
|
+
field: "maxChargingVoltage",
|
|
21
|
+
},
|
|
22
|
+
"Charge counter": { type: "number", field: "chargeCounter" },
|
|
23
|
+
status: { type: "number", field: "status" },
|
|
24
|
+
health: { type: "number", field: "health" },
|
|
25
|
+
present: { type: "boolean", field: "present" },
|
|
26
|
+
level: { type: "number", field: "level" },
|
|
27
|
+
scale: { type: "number", field: "scale" },
|
|
28
|
+
voltage: { type: "number", field: "voltage" },
|
|
29
|
+
temperature: { type: "number", field: "temperature" },
|
|
30
|
+
technology: { type: "string", field: "technology" },
|
|
31
|
+
current: { type: "number", field: "current" },
|
|
32
|
+
};
|
|
33
|
+
|
|
3
34
|
export class DumpSys extends AdbCommandBase {
|
|
4
35
|
async diskStats() {
|
|
5
36
|
const output = await this.adb.subprocess.spawnAndWaitLegacy([
|
|
@@ -40,74 +71,67 @@ export class DumpSys extends AdbCommandBase {
|
|
|
40
71
|
"battery",
|
|
41
72
|
]);
|
|
42
73
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
74
|
+
const info: DumpSys.Battery.Info = {
|
|
75
|
+
acPowered: false,
|
|
76
|
+
usbPowered: false,
|
|
77
|
+
wirelessPowered: false,
|
|
78
|
+
dockPowered: false,
|
|
79
|
+
status: DumpSys.Battery.Status.Unknown,
|
|
80
|
+
health: DumpSys.Battery.Health.Unknown,
|
|
81
|
+
};
|
|
82
|
+
|
|
52
83
|
for (const line of output.split("\n")) {
|
|
53
|
-
const parts = line.split(":");
|
|
84
|
+
const parts = line.split(":").map((part) => part.trim());
|
|
54
85
|
if (parts.length !== 2) {
|
|
55
86
|
continue;
|
|
56
87
|
}
|
|
57
88
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
wirelessPowered = parts[1]!.trim() === "true";
|
|
67
|
-
break;
|
|
68
|
-
case "level":
|
|
69
|
-
level = Number.parseInt(parts[1]!.trim(), 10);
|
|
70
|
-
break;
|
|
71
|
-
case "scale":
|
|
72
|
-
scale = Number.parseInt(parts[1]!.trim(), 10);
|
|
73
|
-
break;
|
|
74
|
-
case "voltage":
|
|
75
|
-
voltage = Number.parseInt(parts[1]!.trim(), 10);
|
|
76
|
-
break;
|
|
77
|
-
case "current now":
|
|
78
|
-
current = Number.parseInt(parts[1]!.trim(), 10);
|
|
89
|
+
const field = BatteryDumpFields[parts[0]!];
|
|
90
|
+
if (!field) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
switch (field.type) {
|
|
95
|
+
case "boolean":
|
|
96
|
+
info[field.field] = (parts[1]!.trim() === "true") as never;
|
|
79
97
|
break;
|
|
80
|
-
case "
|
|
81
|
-
|
|
98
|
+
case "number":
|
|
99
|
+
info[field.field] = Number.parseInt(
|
|
82
100
|
parts[1]!.trim(),
|
|
83
101
|
10,
|
|
84
|
-
) as
|
|
102
|
+
) as never;
|
|
85
103
|
break;
|
|
86
|
-
case "
|
|
87
|
-
|
|
88
|
-
parts[1]!.trim(),
|
|
89
|
-
10,
|
|
90
|
-
) as DumpSys.Battery.Health;
|
|
104
|
+
case "string":
|
|
105
|
+
info[field.field] = parts[1]! as never;
|
|
91
106
|
break;
|
|
92
107
|
}
|
|
93
108
|
}
|
|
94
109
|
|
|
95
|
-
return
|
|
96
|
-
acPowered,
|
|
97
|
-
usbPowered,
|
|
98
|
-
wirelessPowered,
|
|
99
|
-
level,
|
|
100
|
-
scale,
|
|
101
|
-
voltage,
|
|
102
|
-
current,
|
|
103
|
-
status,
|
|
104
|
-
health,
|
|
105
|
-
};
|
|
110
|
+
return info;
|
|
106
111
|
}
|
|
107
112
|
}
|
|
108
113
|
|
|
109
114
|
export namespace DumpSys {
|
|
110
115
|
export namespace Battery {
|
|
116
|
+
export interface Info {
|
|
117
|
+
acPowered: boolean;
|
|
118
|
+
usbPowered: boolean;
|
|
119
|
+
wirelessPowered: boolean;
|
|
120
|
+
dockPowered: boolean;
|
|
121
|
+
maxChargingCurrent?: number;
|
|
122
|
+
maxChargingVoltage?: number;
|
|
123
|
+
chargeCounter?: number;
|
|
124
|
+
status: Status;
|
|
125
|
+
health: Health;
|
|
126
|
+
present?: boolean;
|
|
127
|
+
level?: number;
|
|
128
|
+
scale?: number;
|
|
129
|
+
voltage?: number;
|
|
130
|
+
temperature?: number;
|
|
131
|
+
technology?: string;
|
|
132
|
+
current?: number;
|
|
133
|
+
}
|
|
134
|
+
|
|
111
135
|
export enum Status {
|
|
112
136
|
Unknown = 1,
|
|
113
137
|
Charging,
|
package/src/logcat.ts
CHANGED
|
@@ -5,8 +5,8 @@ import { AdbCommandBase, AdbSubprocessNoneProtocol } from "@yume-chan/adb";
|
|
|
5
5
|
import type { ReadableStream } from "@yume-chan/stream-extra";
|
|
6
6
|
import {
|
|
7
7
|
BufferedTransformStream,
|
|
8
|
-
DecodeUtf8Stream,
|
|
9
8
|
SplitStringStream,
|
|
9
|
+
TextDecoderStream,
|
|
10
10
|
WrapReadableStream,
|
|
11
11
|
WritableStream,
|
|
12
12
|
} from "@yume-chan/stream-extra";
|
|
@@ -353,6 +353,7 @@ export async function deserializeAndroidLogEntry(
|
|
|
353
353
|
): Promise<AndroidLogEntry> {
|
|
354
354
|
const entry = (await LoggerEntry.deserialize(stream)) as AndroidLogEntry;
|
|
355
355
|
if (entry.headerSize !== LoggerEntry.size) {
|
|
356
|
+
// Skip unknown fields
|
|
356
357
|
await stream.readExactly(entry.headerSize - LoggerEntry.size);
|
|
357
358
|
}
|
|
358
359
|
|
|
@@ -384,7 +385,7 @@ export interface LogSize {
|
|
|
384
385
|
|
|
385
386
|
export class Logcat extends AdbCommandBase {
|
|
386
387
|
static logIdToName(id: LogId): string {
|
|
387
|
-
return LogId[id]
|
|
388
|
+
return LogId[id];
|
|
388
389
|
}
|
|
389
390
|
|
|
390
391
|
static logNameToId(name: string): LogId {
|
|
@@ -420,7 +421,7 @@ export class Logcat extends AdbCommandBase {
|
|
|
420
421
|
|
|
421
422
|
const result: LogSize[] = [];
|
|
422
423
|
await stdout
|
|
423
|
-
.pipeThrough(new
|
|
424
|
+
.pipeThrough(new TextDecoderStream())
|
|
424
425
|
.pipeThrough(new SplitStringStream("\n"))
|
|
425
426
|
.pipeTo(
|
|
426
427
|
new WritableStream({
|
package/src/pm.ts
CHANGED
|
@@ -4,16 +4,12 @@
|
|
|
4
4
|
// cspell:ignore versioncode
|
|
5
5
|
|
|
6
6
|
import type { Adb } from "@yume-chan/adb";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
AdbSubprocessNoneProtocol,
|
|
10
|
-
escapeArg,
|
|
11
|
-
} from "@yume-chan/adb";
|
|
12
|
-
import type { Consumable, ReadableStream } from "@yume-chan/stream-extra";
|
|
7
|
+
import { AdbCommandBase, escapeArg } from "@yume-chan/adb";
|
|
8
|
+
import type { MaybeConsumable, ReadableStream } from "@yume-chan/stream-extra";
|
|
13
9
|
import {
|
|
14
10
|
ConcatStringStream,
|
|
15
|
-
DecodeUtf8Stream,
|
|
16
11
|
SplitStringStream,
|
|
12
|
+
TextDecoderStream,
|
|
17
13
|
} from "@yume-chan/stream-extra";
|
|
18
14
|
|
|
19
15
|
import { Cmd } from "./cmd.js";
|
|
@@ -230,6 +226,11 @@ export interface PackageManagerUninstallOptions {
|
|
|
230
226
|
keepData: boolean;
|
|
231
227
|
user: SingleUserOrAll;
|
|
232
228
|
versionCode: number;
|
|
229
|
+
/**
|
|
230
|
+
* Only remove the specified splits, not the entire app
|
|
231
|
+
*
|
|
232
|
+
* On Android 10 and lower, only one split name can be specified.
|
|
233
|
+
*/
|
|
233
234
|
splitNames: string[];
|
|
234
235
|
}
|
|
235
236
|
|
|
@@ -263,27 +264,48 @@ export class PackageManager extends AdbCommandBase {
|
|
|
263
264
|
}
|
|
264
265
|
|
|
265
266
|
#buildInstallArguments(
|
|
267
|
+
command: string,
|
|
266
268
|
options: Partial<PackageManagerInstallOptions> | undefined,
|
|
267
269
|
): string[] {
|
|
268
|
-
|
|
269
|
-
["pm",
|
|
270
|
+
const args = buildArguments(
|
|
271
|
+
["pm", command],
|
|
270
272
|
options,
|
|
271
273
|
PACKAGE_MANAGER_INSTALL_OPTIONS_MAP,
|
|
272
274
|
);
|
|
275
|
+
if (!options?.skipExisting) {
|
|
276
|
+
/*
|
|
277
|
+
* | behavior | previous version | modern version |
|
|
278
|
+
* | -------------------- | -------------------- | -------------------- |
|
|
279
|
+
* | replace existing app | requires `-r` | default behavior [1] |
|
|
280
|
+
* | skip existing app | default behavior [2] | requires `-R` |
|
|
281
|
+
*
|
|
282
|
+
* [1]: `-r` recognized but ignored)
|
|
283
|
+
* [2]: `-R` not recognized but ignored
|
|
284
|
+
*
|
|
285
|
+
* So add `-r` when `skipExisting` is `false` for compatibility.
|
|
286
|
+
*/
|
|
287
|
+
args.push("-r");
|
|
288
|
+
}
|
|
289
|
+
return args;
|
|
273
290
|
}
|
|
274
291
|
|
|
292
|
+
/**
|
|
293
|
+
* Install the apk file.
|
|
294
|
+
*
|
|
295
|
+
* @param apks Path to the apk file. It must exist on the device. On Android 10 and lower, only one apk can be specified.
|
|
296
|
+
*/
|
|
275
297
|
async install(
|
|
276
298
|
apks: string[],
|
|
277
299
|
options?: Partial<PackageManagerInstallOptions>,
|
|
278
300
|
): Promise<string> {
|
|
279
|
-
const args = this.#buildInstallArguments(options);
|
|
301
|
+
const args = this.#buildInstallArguments("install", options);
|
|
280
302
|
// WIP: old version of pm doesn't support multiple apks
|
|
281
303
|
args.push(...apks);
|
|
282
304
|
return await this.adb.subprocess.spawnAndWaitLegacy(args);
|
|
283
305
|
}
|
|
284
306
|
|
|
285
307
|
async pushAndInstallStream(
|
|
286
|
-
stream: ReadableStream<
|
|
308
|
+
stream: ReadableStream<MaybeConsumable<Uint8Array>>,
|
|
287
309
|
options?: Partial<PackageManagerInstallOptions>,
|
|
288
310
|
): Promise<void> {
|
|
289
311
|
const sync = await this.adb.sync();
|
|
@@ -300,49 +322,55 @@ export class PackageManager extends AdbCommandBase {
|
|
|
300
322
|
await sync.dispose();
|
|
301
323
|
}
|
|
302
324
|
|
|
303
|
-
// Starting from Android 7, `pm`
|
|
304
|
-
//
|
|
305
|
-
//
|
|
306
|
-
//
|
|
307
|
-
|
|
325
|
+
// Starting from Android 7, `pm` becomes a wrapper to `cmd package`.
|
|
326
|
+
// The benefit of `cmd package` is it starts faster than the old `pm`,
|
|
327
|
+
// because it connects to the already running `system` process,
|
|
328
|
+
// instead of initializing all system components from scratch.
|
|
329
|
+
//
|
|
330
|
+
// But launching `cmd package` directly causes it to not be able to
|
|
331
|
+
// read files in `/data/local/tmp` (and many other places) due to SELinux policies,
|
|
332
|
+
// so installing files must still use `pm`.
|
|
333
|
+
// (the starting executable file decides which SELinux policies to apply)
|
|
334
|
+
const args = this.#buildInstallArguments("install", options);
|
|
308
335
|
args.push(filePath);
|
|
309
|
-
const process = await this.adb.subprocess.spawn(args.map(escapeArg), {
|
|
310
|
-
protocols: [AdbSubprocessNoneProtocol],
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
const output = await process.stdout
|
|
314
|
-
.pipeThrough(new DecodeUtf8Stream())
|
|
315
|
-
.pipeThrough(new ConcatStringStream())
|
|
316
|
-
.then((output) => output.trim());
|
|
317
336
|
|
|
318
|
-
|
|
337
|
+
try {
|
|
338
|
+
const output = await this.adb.subprocess
|
|
339
|
+
.spawnAndWaitLegacy(args.map(escapeArg))
|
|
340
|
+
.then((output) => output.trim());
|
|
319
341
|
|
|
320
|
-
|
|
321
|
-
|
|
342
|
+
if (output !== "Success") {
|
|
343
|
+
throw new Error(output);
|
|
344
|
+
}
|
|
345
|
+
} finally {
|
|
346
|
+
await this.adb.rm(filePath);
|
|
322
347
|
}
|
|
323
348
|
}
|
|
324
349
|
|
|
325
350
|
async installStream(
|
|
326
351
|
size: number,
|
|
327
|
-
stream: ReadableStream<
|
|
352
|
+
stream: ReadableStream<MaybeConsumable<Uint8Array>>,
|
|
328
353
|
options?: Partial<PackageManagerInstallOptions>,
|
|
329
354
|
): Promise<void> {
|
|
330
355
|
// Android 7 added both `cmd` command and streaming install support,
|
|
331
|
-
//
|
|
332
|
-
// so
|
|
356
|
+
// It's hard to detect whether `pm` supports streaming install (unless actually trying),
|
|
357
|
+
// so check for whether `cmd` is supported,
|
|
358
|
+
// and assume `pm` streaming install support status is same as that.
|
|
333
359
|
if (!this.#cmd.supportsCmd) {
|
|
360
|
+
// Fall back to push file then install
|
|
334
361
|
await this.pushAndInstallStream(stream, options);
|
|
335
362
|
return;
|
|
336
363
|
}
|
|
337
364
|
|
|
338
|
-
const args = this.#buildInstallArguments(options);
|
|
339
|
-
// Remove `pm` from args,
|
|
365
|
+
const args = this.#buildInstallArguments("install", options);
|
|
366
|
+
// Remove `pm` from args, `Cmd#spawn` will prepend `cmd <command>` so the final args
|
|
367
|
+
// will be `cmd package install <args>`
|
|
340
368
|
args.shift();
|
|
341
369
|
args.push("-S", size.toString());
|
|
342
370
|
const process = await this.#cmd.spawn(false, "package", ...args);
|
|
343
371
|
|
|
344
372
|
const output = process.stdout
|
|
345
|
-
.pipeThrough(new
|
|
373
|
+
.pipeThrough(new TextDecoderStream())
|
|
346
374
|
.pipeThrough(new ConcatStringStream())
|
|
347
375
|
.then((output) => output.trim());
|
|
348
376
|
|
|
@@ -356,8 +384,6 @@ export class PackageManager extends AdbCommandBase {
|
|
|
356
384
|
]);
|
|
357
385
|
}
|
|
358
386
|
|
|
359
|
-
// TODO: install: support split apk formats (`adb install-multiple`)
|
|
360
|
-
|
|
361
387
|
static parsePackageListItem(
|
|
362
388
|
line: string,
|
|
363
389
|
): PackageManagerListPackagesResult {
|
|
@@ -369,7 +395,7 @@ export class PackageManager extends AdbCommandBase {
|
|
|
369
395
|
let installer: string | undefined;
|
|
370
396
|
let uid: number | undefined;
|
|
371
397
|
|
|
372
|
-
//
|
|
398
|
+
// The output format is easier to parse in backwards
|
|
373
399
|
let index = line.indexOf(" uid:");
|
|
374
400
|
if (index !== -1) {
|
|
375
401
|
uid = Number.parseInt(line.substring(index + " uid:".length), 10);
|
|
@@ -391,7 +417,9 @@ export class PackageManager extends AdbCommandBase {
|
|
|
391
417
|
line = line.substring(0, index);
|
|
392
418
|
}
|
|
393
419
|
|
|
394
|
-
// `sourceDir` may contain `=`
|
|
420
|
+
// `sourceDir` may contain `=` characters
|
|
421
|
+
// (because in newer Android versions it's a base64 string of encrypted package name),
|
|
422
|
+
// so use `lastIndexOf`
|
|
395
423
|
index = line.lastIndexOf("=");
|
|
396
424
|
if (index !== -1) {
|
|
397
425
|
sourceDir = line.substring(0, index);
|
|
@@ -432,7 +460,10 @@ export class PackageManager extends AdbCommandBase {
|
|
|
432
460
|
|
|
433
461
|
const process = await this.#cmdOrSubprocess(args);
|
|
434
462
|
const reader = process.stdout
|
|
435
|
-
.pipeThrough(new
|
|
463
|
+
.pipeThrough(new TextDecoderStream())
|
|
464
|
+
// FIXME: `SplitStringStream` will throw away some data
|
|
465
|
+
// if it doesn't end with a separator. So each chunk of data
|
|
466
|
+
// must contain several complete lines.
|
|
436
467
|
.pipeThrough(new SplitStringStream("\n"))
|
|
437
468
|
.getReader();
|
|
438
469
|
while (true) {
|
|
@@ -460,7 +491,7 @@ export class PackageManager extends AdbCommandBase {
|
|
|
460
491
|
|
|
461
492
|
const process = await this.#cmdOrSubprocess(args);
|
|
462
493
|
const output = await process.stdout
|
|
463
|
-
.pipeThrough(new
|
|
494
|
+
.pipeThrough(new TextDecoderStream())
|
|
464
495
|
.pipeThrough(new ConcatStringStream())
|
|
465
496
|
.then((output) => output.trim());
|
|
466
497
|
if (output !== "Success") {
|
|
@@ -481,7 +512,7 @@ export class PackageManager extends AdbCommandBase {
|
|
|
481
512
|
|
|
482
513
|
const process = await this.#cmdOrSubprocess(args);
|
|
483
514
|
const output = await process.stdout
|
|
484
|
-
.pipeThrough(new
|
|
515
|
+
.pipeThrough(new TextDecoderStream())
|
|
485
516
|
.pipeThrough(new ConcatStringStream())
|
|
486
517
|
.then((output) => output.trim());
|
|
487
518
|
|
|
@@ -491,4 +522,147 @@ export class PackageManager extends AdbCommandBase {
|
|
|
491
522
|
|
|
492
523
|
return output;
|
|
493
524
|
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Creates a new install session.
|
|
528
|
+
*
|
|
529
|
+
* Install sessions are used to install apps with multiple splits, but it can also be used to install a single apk.
|
|
530
|
+
*
|
|
531
|
+
* Install sessions was added in Android 5.0 (API level 21).
|
|
532
|
+
*
|
|
533
|
+
* @param options Options for the install session
|
|
534
|
+
* @returns ID of the new install session
|
|
535
|
+
*/
|
|
536
|
+
async sessionCreate(options?: Partial<PackageManagerInstallOptions>) {
|
|
537
|
+
const args = this.#buildInstallArguments("install-create", options);
|
|
538
|
+
|
|
539
|
+
const process = await this.#cmdOrSubprocess(args);
|
|
540
|
+
const output = await process.stdout
|
|
541
|
+
.pipeThrough(new TextDecoderStream())
|
|
542
|
+
.pipeThrough(new ConcatStringStream())
|
|
543
|
+
.then((output) => output.trim());
|
|
544
|
+
|
|
545
|
+
const sessionIdString = output.match(/.*\[(\d+)\].*/);
|
|
546
|
+
if (!sessionIdString) {
|
|
547
|
+
throw new Error("Failed to create install session");
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
return Number.parseInt(sessionIdString[1]!, 10);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
async sessionAddSplit(sessionId: number, splitName: string, path: string) {
|
|
554
|
+
const args: string[] = [
|
|
555
|
+
"pm",
|
|
556
|
+
"install-write",
|
|
557
|
+
sessionId.toString(),
|
|
558
|
+
splitName,
|
|
559
|
+
path,
|
|
560
|
+
];
|
|
561
|
+
|
|
562
|
+
const output = await this.adb.subprocess
|
|
563
|
+
.spawnAndWaitLegacy(args)
|
|
564
|
+
.then((output) => output.trim());
|
|
565
|
+
if (!output.startsWith("Success")) {
|
|
566
|
+
throw new Error(output);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
async sessionAddSplitStream(
|
|
571
|
+
sessionId: number,
|
|
572
|
+
splitName: string,
|
|
573
|
+
size: number,
|
|
574
|
+
stream: ReadableStream<MaybeConsumable<Uint8Array>>,
|
|
575
|
+
) {
|
|
576
|
+
const args: string[] = [
|
|
577
|
+
"pm",
|
|
578
|
+
"install-write",
|
|
579
|
+
"-S",
|
|
580
|
+
size.toString(),
|
|
581
|
+
sessionId.toString(),
|
|
582
|
+
splitName,
|
|
583
|
+
"-",
|
|
584
|
+
];
|
|
585
|
+
|
|
586
|
+
const process = await this.#cmdOrSubprocess(args);
|
|
587
|
+
const output = process.stdout
|
|
588
|
+
.pipeThrough(new TextDecoderStream())
|
|
589
|
+
.pipeThrough(new ConcatStringStream())
|
|
590
|
+
.then((output) => output.trim());
|
|
591
|
+
|
|
592
|
+
await Promise.all([
|
|
593
|
+
stream.pipeTo(process.stdin),
|
|
594
|
+
output.then((output) => {
|
|
595
|
+
if (!output.startsWith("Success")) {
|
|
596
|
+
throw new Error(output);
|
|
597
|
+
}
|
|
598
|
+
}),
|
|
599
|
+
]);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
async sessionCommit(sessionId: number) {
|
|
603
|
+
const args: string[] = ["pm", "install-commit", sessionId.toString()];
|
|
604
|
+
const output = await this.adb.subprocess
|
|
605
|
+
.spawnAndWaitLegacy(args)
|
|
606
|
+
.then((output) => output.trim());
|
|
607
|
+
if (output !== "Success") {
|
|
608
|
+
throw new Error(output);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
async sessionAbandon(sessionId: number) {
|
|
613
|
+
const args: string[] = ["pm", "install-abandon", sessionId.toString()];
|
|
614
|
+
const output = await this.adb.subprocess
|
|
615
|
+
.spawnAndWaitLegacy(args)
|
|
616
|
+
.then((output) => output.trim());
|
|
617
|
+
if (output !== "Success") {
|
|
618
|
+
throw new Error(output);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
export class PackageManagerInstallSession {
|
|
624
|
+
static async create(
|
|
625
|
+
packageManager: PackageManager,
|
|
626
|
+
options?: Partial<PackageManagerInstallOptions>,
|
|
627
|
+
) {
|
|
628
|
+
const id = await packageManager.sessionCreate(options);
|
|
629
|
+
return new PackageManagerInstallSession(packageManager, id);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
#packageManager: PackageManager;
|
|
633
|
+
|
|
634
|
+
#id: number;
|
|
635
|
+
get id() {
|
|
636
|
+
return this.#id;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
constructor(packageManager: PackageManager, id: number) {
|
|
640
|
+
this.#packageManager = packageManager;
|
|
641
|
+
this.#id = id;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
addSplit(splitName: string, path: string) {
|
|
645
|
+
return this.#packageManager.sessionAddSplit(this.#id, splitName, path);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
addSplitStream(
|
|
649
|
+
splitName: string,
|
|
650
|
+
size: number,
|
|
651
|
+
stream: ReadableStream<MaybeConsumable<Uint8Array>>,
|
|
652
|
+
) {
|
|
653
|
+
return this.#packageManager.sessionAddSplitStream(
|
|
654
|
+
this.#id,
|
|
655
|
+
splitName,
|
|
656
|
+
size,
|
|
657
|
+
stream,
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
commit() {
|
|
662
|
+
return this.#packageManager.sessionCommit(this.#id);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
abandon() {
|
|
666
|
+
return this.#packageManager.sessionAbandon(this.#id);
|
|
667
|
+
}
|
|
494
668
|
}
|
package/src/string-format.ts
CHANGED