@yume-chan/android-bin 0.0.0-20240714132542

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 (76) hide show
  1. package/CHANGELOG.md +129 -0
  2. package/LICENSE +21 -0
  3. package/README.md +49 -0
  4. package/esm/am.d.ts +17 -0
  5. package/esm/am.d.ts.map +1 -0
  6. package/esm/am.js +42 -0
  7. package/esm/am.js.map +1 -0
  8. package/esm/bu.d.ts +28 -0
  9. package/esm/bu.d.ts.map +1 -0
  10. package/esm/bu.js +53 -0
  11. package/esm/bu.js.map +1 -0
  12. package/esm/bug-report.d.ts +99 -0
  13. package/esm/bug-report.d.ts.map +1 -0
  14. package/esm/bug-report.js +271 -0
  15. package/esm/bug-report.js.map +1 -0
  16. package/esm/cmd.d.ts +25 -0
  17. package/esm/cmd.d.ts.map +1 -0
  18. package/esm/cmd.js +81 -0
  19. package/esm/cmd.js.map +1 -0
  20. package/esm/demo-mode.d.ts +44 -0
  21. package/esm/demo-mode.d.ts.map +1 -0
  22. package/esm/demo-mode.js +179 -0
  23. package/esm/demo-mode.js.map +1 -0
  24. package/esm/dumpsys.d.ts +51 -0
  25. package/esm/dumpsys.d.ts.map +1 -0
  26. package/esm/dumpsys.js +114 -0
  27. package/esm/dumpsys.js.map +1 -0
  28. package/esm/index.d.ts +13 -0
  29. package/esm/index.d.ts.map +1 -0
  30. package/esm/index.js +14 -0
  31. package/esm/index.js.map +1 -0
  32. package/esm/intent.d.ts +11 -0
  33. package/esm/intent.d.ts.map +1 -0
  34. package/esm/intent.js +59 -0
  35. package/esm/intent.js.map +1 -0
  36. package/esm/logcat.d.ts +93 -0
  37. package/esm/logcat.d.ts.map +1 -0
  38. package/esm/logcat.js +300 -0
  39. package/esm/logcat.js.map +1 -0
  40. package/esm/overlay-display.d.ts +38 -0
  41. package/esm/overlay-display.d.ts.map +1 -0
  42. package/esm/overlay-display.js +51 -0
  43. package/esm/overlay-display.js.map +1 -0
  44. package/esm/pm.d.ts +211 -0
  45. package/esm/pm.d.ts.map +1 -0
  46. package/esm/pm.js +382 -0
  47. package/esm/pm.js.map +1 -0
  48. package/esm/settings.d.ts +27 -0
  49. package/esm/settings.d.ts.map +1 -0
  50. package/esm/settings.js +66 -0
  51. package/esm/settings.js.map +1 -0
  52. package/esm/string-format.d.ts +43 -0
  53. package/esm/string-format.d.ts.map +1 -0
  54. package/esm/string-format.js +152 -0
  55. package/esm/string-format.js.map +1 -0
  56. package/esm/utils.d.ts +4 -0
  57. package/esm/utils.d.ts.map +1 -0
  58. package/esm/utils.js +23 -0
  59. package/esm/utils.js.map +1 -0
  60. package/package.json +49 -0
  61. package/src/am.ts +71 -0
  62. package/src/bu.ts +80 -0
  63. package/src/bug-report.ts +348 -0
  64. package/src/cmd.ts +113 -0
  65. package/src/demo-mode.ts +232 -0
  66. package/src/dumpsys.ts +153 -0
  67. package/src/index.ts +14 -0
  68. package/src/intent.ts +73 -0
  69. package/src/logcat.ts +509 -0
  70. package/src/overlay-display.ts +112 -0
  71. package/src/pm.ts +674 -0
  72. package/src/settings.ts +128 -0
  73. package/src/string-format.ts +197 -0
  74. package/src/utils.ts +29 -0
  75. package/tsconfig.build.json +14 -0
  76. package/tsconfig.build.tsbuildinfo +1 -0
package/src/pm.ts ADDED
@@ -0,0 +1,674 @@
1
+ // cspell:ignore dont
2
+ // cspell:ignore instantapp
3
+ // cspell:ignore apks
4
+ // cspell:ignore versioncode
5
+
6
+ import type { Adb } from "@yume-chan/adb";
7
+ import { AdbCommandBase, escapeArg } from "@yume-chan/adb";
8
+ import type { MaybeConsumable, ReadableStream } from "@yume-chan/stream-extra";
9
+ import {
10
+ ConcatStringStream,
11
+ SplitStringStream,
12
+ TextDecoderStream,
13
+ } from "@yume-chan/stream-extra";
14
+
15
+ import { Cmd } from "./cmd.js";
16
+ import type { IntentBuilder } from "./intent.js";
17
+ import type { SingleUserOrAll } from "./utils.js";
18
+ import { buildArguments } from "./utils.js";
19
+
20
+ export enum PackageManagerInstallLocation {
21
+ Auto,
22
+ InternalOnly,
23
+ PreferExternal,
24
+ }
25
+
26
+ export enum PackageManagerInstallReason {
27
+ Unknown,
28
+ AdminPolicy,
29
+ DeviceRestore,
30
+ DeviceSetup,
31
+ UserRequest,
32
+ }
33
+
34
+ // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java;l=3046;drc=6d14d35d0241f6fee145f8e54ffd77252e8d29fd
35
+ export interface PackageManagerInstallOptions {
36
+ /**
37
+ * `-R`
38
+ */
39
+ skipExisting: boolean;
40
+ /**
41
+ * `-i`
42
+ */
43
+ installerPackageName: string;
44
+ /**
45
+ * `-t`
46
+ */
47
+ allowTest: boolean;
48
+ /**
49
+ * `-f`
50
+ */
51
+ internalStorage: boolean;
52
+ /**
53
+ * `-d`
54
+ */
55
+ requestDowngrade: boolean;
56
+ /**
57
+ * `-g`
58
+ */
59
+ grantRuntimePermissions: boolean;
60
+ /**
61
+ * `--restrict-permissions`
62
+ */
63
+ restrictPermissions: boolean;
64
+ /**
65
+ * `--dont-kill`
66
+ */
67
+ doNotKill: boolean;
68
+ /**
69
+ * `--originating-uri`
70
+ */
71
+ originatingUri: string;
72
+ /**
73
+ * `--referrer`
74
+ */
75
+ refererUri: string;
76
+ /**
77
+ * `-p`
78
+ */
79
+ inheritFrom: string;
80
+ /**
81
+ * `--pkg`
82
+ */
83
+ packageName: string;
84
+ /**
85
+ * `--abi`
86
+ */
87
+ abi: string;
88
+ /**
89
+ * `--ephemeral`/`--instant`/`--instantapp`
90
+ */
91
+ instantApp: boolean;
92
+ /**
93
+ * `--full`
94
+ */
95
+ full: boolean;
96
+ /**
97
+ * `--preload`
98
+ */
99
+ preload: boolean;
100
+ /**
101
+ * `--user`
102
+ */
103
+ user: SingleUserOrAll;
104
+ /**
105
+ * `--install-location`
106
+ */
107
+ installLocation: PackageManagerInstallLocation;
108
+ /**
109
+ * `--install-reason`
110
+ */
111
+ installReason: PackageManagerInstallReason;
112
+ /**
113
+ * `--force-uuid`
114
+ */
115
+ forceUuid: string;
116
+ /**
117
+ * `--apex`
118
+ */
119
+ apex: boolean;
120
+ /**
121
+ * `--force-non-staged`
122
+ */
123
+ forceNonStaged: boolean;
124
+ /**
125
+ * `--staged`
126
+ */
127
+ staged: boolean;
128
+ /**
129
+ * `--force-queryable`
130
+ */
131
+ forceQueryable: boolean;
132
+ /**
133
+ * `--enable-rollback`
134
+ */
135
+ enableRollback: boolean;
136
+ /**
137
+ * `--staged-ready-timeout`
138
+ */
139
+ stagedReadyTimeout: number;
140
+ /**
141
+ * `--skip-verification`
142
+ */
143
+ skipVerification: boolean;
144
+ /**
145
+ * `--bypass-low-target-sdk-block`
146
+ */
147
+ bypassLowTargetSdkBlock: boolean;
148
+ }
149
+
150
+ export const PACKAGE_MANAGER_INSTALL_OPTIONS_MAP: Record<
151
+ keyof PackageManagerInstallOptions,
152
+ string
153
+ > = {
154
+ skipExisting: "-R",
155
+ installerPackageName: "-i",
156
+ allowTest: "-t",
157
+ internalStorage: "-f",
158
+ requestDowngrade: "-d",
159
+ grantRuntimePermissions: "-g",
160
+ restrictPermissions: "--restrict-permissions",
161
+ doNotKill: "--dont-kill",
162
+ originatingUri: "--originating-uri",
163
+ refererUri: "--referrer",
164
+ inheritFrom: "-p",
165
+ packageName: "--pkg",
166
+ abi: "--abi",
167
+ instantApp: "--instant",
168
+ full: "--full",
169
+ preload: "--preload",
170
+ user: "--user",
171
+ installLocation: "--install-location",
172
+ installReason: "--install-reason",
173
+ forceUuid: "--force-uuid",
174
+ apex: "--apex",
175
+ forceNonStaged: "--force-non-staged",
176
+ staged: "--staged",
177
+ forceQueryable: "--force-queryable",
178
+ enableRollback: "--enable-rollback",
179
+ stagedReadyTimeout: "--staged-ready-timeout",
180
+ skipVerification: "--skip-verification",
181
+ bypassLowTargetSdkBlock: "--bypass-low-target-sdk-block",
182
+ };
183
+
184
+ export interface PackageManagerListPackagesOptions {
185
+ listDisabled: boolean;
186
+ listEnabled: boolean;
187
+ showSourceDir: boolean;
188
+ showInstaller: boolean;
189
+ listSystem: boolean;
190
+ showUid: boolean;
191
+ listThirdParty: boolean;
192
+ showVersionCode: boolean;
193
+ listApexOnly: boolean;
194
+ user: SingleUserOrAll;
195
+ uid: number;
196
+ filter: string;
197
+ }
198
+
199
+ export const PACKAGE_MANAGER_LIST_PACKAGES_OPTIONS_MAP: Record<
200
+ keyof PackageManagerListPackagesOptions,
201
+ string
202
+ > = {
203
+ listDisabled: "-d",
204
+ listEnabled: "-e",
205
+ showSourceDir: "-f",
206
+ showInstaller: "-i",
207
+ listSystem: "-s",
208
+ showUid: "-U",
209
+ listThirdParty: "-3",
210
+ showVersionCode: "--show-versioncode",
211
+ listApexOnly: "--apex-only",
212
+ user: "--user",
213
+ uid: "--uid",
214
+ filter: "",
215
+ };
216
+
217
+ export interface PackageManagerListPackagesResult {
218
+ packageName: string;
219
+ sourceDir?: string | undefined;
220
+ versionCode?: number | undefined;
221
+ installer?: string | undefined;
222
+ uid?: number | undefined;
223
+ }
224
+
225
+ export interface PackageManagerUninstallOptions {
226
+ keepData: boolean;
227
+ user: SingleUserOrAll;
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
+ */
234
+ splitNames: string[];
235
+ }
236
+
237
+ const PACKAGE_MANAGER_UNINSTALL_OPTIONS_MAP: Record<
238
+ keyof PackageManagerUninstallOptions,
239
+ string
240
+ > = {
241
+ keepData: "-k",
242
+ user: "--user",
243
+ versionCode: "--versionCode",
244
+ splitNames: "",
245
+ };
246
+
247
+ export interface PackageManagerResolveActivityOptions {
248
+ user?: SingleUserOrAll;
249
+ intent: IntentBuilder;
250
+ }
251
+
252
+ const PACKAGE_MANAGER_RESOLVE_ACTIVITY_OPTIONS_MAP: Partial<
253
+ Record<keyof PackageManagerResolveActivityOptions, string>
254
+ > = {
255
+ user: "--user",
256
+ };
257
+
258
+ function buildInstallArguments(
259
+ command: string,
260
+ options: Partial<PackageManagerInstallOptions> | undefined,
261
+ ): string[] {
262
+ const args = buildArguments(
263
+ ["pm", command],
264
+ options,
265
+ PACKAGE_MANAGER_INSTALL_OPTIONS_MAP,
266
+ );
267
+ if (!options?.skipExisting) {
268
+ /*
269
+ * | behavior | previous version | modern version |
270
+ * | -------------------- | -------------------- | -------------------- |
271
+ * | replace existing app | requires `-r` | default behavior [1] |
272
+ * | skip existing app | default behavior [2] | requires `-R` |
273
+ *
274
+ * [1]: `-r` recognized but ignored
275
+ * [2]: `-R` not recognized but ignored
276
+ *
277
+ * So add `-r` when `skipExisting` is `false` for compatibility.
278
+ */
279
+ args.push("-r");
280
+ }
281
+ return args;
282
+ }
283
+
284
+ export class PackageManager extends AdbCommandBase {
285
+ #cmd: Cmd;
286
+
287
+ constructor(adb: Adb) {
288
+ super(adb);
289
+ this.#cmd = new Cmd(adb);
290
+ }
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
+ */
297
+ async install(
298
+ apks: string[],
299
+ options?: Partial<PackageManagerInstallOptions>,
300
+ ): Promise<string> {
301
+ const args = buildInstallArguments("install", options);
302
+ // WIP: old version of pm doesn't support multiple apks
303
+ args.push(...apks);
304
+ return await this.adb.subprocess.spawnAndWaitLegacy(args);
305
+ }
306
+
307
+ async pushAndInstallStream(
308
+ stream: ReadableStream<MaybeConsumable<Uint8Array>>,
309
+ options?: Partial<PackageManagerInstallOptions>,
310
+ ): Promise<void> {
311
+ const sync = await this.adb.sync();
312
+
313
+ const fileName = Math.random().toString().substring(2);
314
+ const filePath = `/data/local/tmp/${fileName}.apk`;
315
+
316
+ try {
317
+ await sync.write({
318
+ filename: filePath,
319
+ file: stream,
320
+ });
321
+ } finally {
322
+ await sync.dispose();
323
+ }
324
+
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 = buildInstallArguments("install", options);
335
+ args.push(filePath);
336
+
337
+ try {
338
+ const output = await this.adb.subprocess
339
+ .spawnAndWaitLegacy(args.map(escapeArg))
340
+ .then((output) => output.trim());
341
+
342
+ if (output !== "Success") {
343
+ throw new Error(output);
344
+ }
345
+ } finally {
346
+ await this.adb.rm(filePath);
347
+ }
348
+ }
349
+
350
+ async installStream(
351
+ size: number,
352
+ stream: ReadableStream<MaybeConsumable<Uint8Array>>,
353
+ options?: Partial<PackageManagerInstallOptions>,
354
+ ): Promise<void> {
355
+ // Android 7 added both `cmd` command and streaming install support,
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.
359
+ if (!this.#cmd.supportsCmd) {
360
+ // Fall back to push file then install
361
+ await this.pushAndInstallStream(stream, options);
362
+ return;
363
+ }
364
+
365
+ const args = 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>`
368
+ args.shift();
369
+ args.push("-S", size.toString());
370
+ const process = await this.#cmd.spawn(false, "package", ...args);
371
+
372
+ const output = process.stdout
373
+ .pipeThrough(new TextDecoderStream())
374
+ .pipeThrough(new ConcatStringStream())
375
+ .then((output) => output.trim());
376
+
377
+ await Promise.all([
378
+ stream.pipeTo(process.stdin),
379
+ output.then((output) => {
380
+ if (output !== "Success") {
381
+ throw new Error(output);
382
+ }
383
+ }),
384
+ ]);
385
+ }
386
+
387
+ static parsePackageListItem(
388
+ line: string,
389
+ ): PackageManagerListPackagesResult {
390
+ line = line.substring("package:".length);
391
+
392
+ let packageName: string;
393
+ let sourceDir: string | undefined;
394
+ let versionCode: number | undefined;
395
+ let installer: string | undefined;
396
+ let uid: number | undefined;
397
+
398
+ // The output format is easier to parse in backwards
399
+ let index = line.indexOf(" uid:");
400
+ if (index !== -1) {
401
+ uid = Number.parseInt(line.substring(index + " uid:".length), 10);
402
+ line = line.substring(0, index);
403
+ }
404
+
405
+ index = line.indexOf(" installer=");
406
+ if (index !== -1) {
407
+ installer = line.substring(index + " installer=".length);
408
+ line = line.substring(0, index);
409
+ }
410
+
411
+ index = line.indexOf(" versionCode:");
412
+ if (index !== -1) {
413
+ versionCode = Number.parseInt(
414
+ line.substring(index + " versionCode:".length),
415
+ 10,
416
+ );
417
+ line = line.substring(0, index);
418
+ }
419
+
420
+ // `sourceDir` may contain `=` characters
421
+ // (because in newer Android versions it's a base64 string of encrypted package name),
422
+ // so use `lastIndexOf`
423
+ index = line.lastIndexOf("=");
424
+ if (index !== -1) {
425
+ sourceDir = line.substring(0, index);
426
+ packageName = line.substring(index + "=".length);
427
+ } else {
428
+ packageName = line;
429
+ }
430
+
431
+ return {
432
+ packageName,
433
+ sourceDir,
434
+ versionCode,
435
+ installer,
436
+ uid,
437
+ };
438
+ }
439
+
440
+ async #cmdOrSubprocess(args: string[]) {
441
+ if (this.#cmd.supportsCmd) {
442
+ args.shift();
443
+ return await this.#cmd.spawn(false, "package", ...args);
444
+ }
445
+
446
+ return this.adb.subprocess.spawn(args);
447
+ }
448
+
449
+ async *listPackages(
450
+ options?: Partial<PackageManagerListPackagesOptions>,
451
+ ): AsyncGenerator<PackageManagerListPackagesResult, void, void> {
452
+ const args = buildArguments(
453
+ ["pm", "list", "packages"],
454
+ options,
455
+ PACKAGE_MANAGER_LIST_PACKAGES_OPTIONS_MAP,
456
+ );
457
+ if (options?.filter) {
458
+ args.push(options.filter);
459
+ }
460
+
461
+ const process = await this.#cmdOrSubprocess(args);
462
+ const reader = process.stdout
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.
467
+ .pipeThrough(new SplitStringStream("\n"))
468
+ .getReader();
469
+ while (true) {
470
+ const { done, value } = await reader.read();
471
+ if (done) {
472
+ break;
473
+ }
474
+ yield PackageManager.parsePackageListItem(value);
475
+ }
476
+ }
477
+
478
+ async uninstall(
479
+ packageName: string,
480
+ options?: Partial<PackageManagerUninstallOptions>,
481
+ ): Promise<void> {
482
+ const args = buildArguments(
483
+ ["pm", "uninstall"],
484
+ options,
485
+ PACKAGE_MANAGER_UNINSTALL_OPTIONS_MAP,
486
+ );
487
+ args.push(packageName);
488
+ if (options?.splitNames) {
489
+ args.push(...options.splitNames);
490
+ }
491
+
492
+ const process = await this.#cmdOrSubprocess(args);
493
+ const output = await process.stdout
494
+ .pipeThrough(new TextDecoderStream())
495
+ .pipeThrough(new ConcatStringStream())
496
+ .then((output) => output.trim());
497
+ if (output !== "Success") {
498
+ throw new Error(output);
499
+ }
500
+ }
501
+
502
+ async resolveActivity(
503
+ options: PackageManagerResolveActivityOptions,
504
+ ): Promise<string | undefined> {
505
+ let args = buildArguments(
506
+ ["pm", "resolve-activity", "--components"],
507
+ options,
508
+ PACKAGE_MANAGER_RESOLVE_ACTIVITY_OPTIONS_MAP,
509
+ );
510
+
511
+ args = args.concat(options.intent.build());
512
+
513
+ const process = await this.#cmdOrSubprocess(args);
514
+ const output = await process.stdout
515
+ .pipeThrough(new TextDecoderStream())
516
+ .pipeThrough(new ConcatStringStream())
517
+ .then((output) => output.trim());
518
+
519
+ if (output === "No activity found") {
520
+ return undefined;
521
+ }
522
+
523
+ return output;
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(
537
+ options?: Partial<PackageManagerInstallOptions>,
538
+ ): Promise<number> {
539
+ const args = buildInstallArguments("install-create", options);
540
+
541
+ const process = await this.#cmdOrSubprocess(args);
542
+ const output = await process.stdout
543
+ .pipeThrough(new TextDecoderStream())
544
+ .pipeThrough(new ConcatStringStream())
545
+ .then((output) => output.trim());
546
+
547
+ const sessionIdString = output.match(/.*\[(\d+)\].*/);
548
+ if (!sessionIdString) {
549
+ throw new Error("Failed to create install session");
550
+ }
551
+
552
+ return Number.parseInt(sessionIdString[1]!, 10);
553
+ }
554
+
555
+ async sessionAddSplit(
556
+ sessionId: number,
557
+ splitName: string,
558
+ path: string,
559
+ ): Promise<void> {
560
+ const args: string[] = [
561
+ "pm",
562
+ "install-write",
563
+ sessionId.toString(),
564
+ splitName,
565
+ path,
566
+ ];
567
+
568
+ const output = await this.adb.subprocess
569
+ .spawnAndWaitLegacy(args)
570
+ .then((output) => output.trim());
571
+ if (!output.startsWith("Success")) {
572
+ throw new Error(output);
573
+ }
574
+ }
575
+
576
+ async sessionAddSplitStream(
577
+ sessionId: number,
578
+ splitName: string,
579
+ size: number,
580
+ stream: ReadableStream<MaybeConsumable<Uint8Array>>,
581
+ ): Promise<void> {
582
+ const args: string[] = [
583
+ "pm",
584
+ "install-write",
585
+ "-S",
586
+ size.toString(),
587
+ sessionId.toString(),
588
+ splitName,
589
+ "-",
590
+ ];
591
+
592
+ const process = await this.#cmdOrSubprocess(args);
593
+ const output = process.stdout
594
+ .pipeThrough(new TextDecoderStream())
595
+ .pipeThrough(new ConcatStringStream())
596
+ .then((output) => output.trim());
597
+
598
+ await Promise.all([
599
+ stream.pipeTo(process.stdin),
600
+ output.then((output) => {
601
+ if (!output.startsWith("Success")) {
602
+ throw new Error(output);
603
+ }
604
+ }),
605
+ ]);
606
+ }
607
+
608
+ async sessionCommit(sessionId: number): Promise<void> {
609
+ const args: string[] = ["pm", "install-commit", sessionId.toString()];
610
+ const output = await this.adb.subprocess
611
+ .spawnAndWaitLegacy(args)
612
+ .then((output) => output.trim());
613
+ if (output !== "Success") {
614
+ throw new Error(output);
615
+ }
616
+ }
617
+
618
+ async sessionAbandon(sessionId: number): Promise<void> {
619
+ const args: string[] = ["pm", "install-abandon", sessionId.toString()];
620
+ const output = await this.adb.subprocess
621
+ .spawnAndWaitLegacy(args)
622
+ .then((output) => output.trim());
623
+ if (output !== "Success") {
624
+ throw new Error(output);
625
+ }
626
+ }
627
+ }
628
+
629
+ export class PackageManagerInstallSession {
630
+ static async create(
631
+ packageManager: PackageManager,
632
+ options?: Partial<PackageManagerInstallOptions>,
633
+ ): Promise<PackageManagerInstallSession> {
634
+ const id = await packageManager.sessionCreate(options);
635
+ return new PackageManagerInstallSession(packageManager, id);
636
+ }
637
+
638
+ #packageManager: PackageManager;
639
+
640
+ #id: number;
641
+ get id(): number {
642
+ return this.#id;
643
+ }
644
+
645
+ constructor(packageManager: PackageManager, id: number) {
646
+ this.#packageManager = packageManager;
647
+ this.#id = id;
648
+ }
649
+
650
+ addSplit(splitName: string, path: string): Promise<void> {
651
+ return this.#packageManager.sessionAddSplit(this.#id, splitName, path);
652
+ }
653
+
654
+ addSplitStream(
655
+ splitName: string,
656
+ size: number,
657
+ stream: ReadableStream<MaybeConsumable<Uint8Array>>,
658
+ ): Promise<void> {
659
+ return this.#packageManager.sessionAddSplitStream(
660
+ this.#id,
661
+ splitName,
662
+ size,
663
+ stream,
664
+ );
665
+ }
666
+
667
+ commit(): Promise<void> {
668
+ return this.#packageManager.sessionCommit(this.#id);
669
+ }
670
+
671
+ abandon(): Promise<void> {
672
+ return this.#packageManager.sessionAbandon(this.#id);
673
+ }
674
+ }