@yume-chan/android-bin 2.1.0 → 3.0.0-beta.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.
- package/LICENSE +1 -1
- package/README.md +4 -1
- package/esm/am.d.ts +20 -15
- package/esm/am.d.ts.map +1 -1
- package/esm/am.js +60 -13
- package/esm/am.js.map +1 -1
- package/esm/bu.d.ts +22 -18
- package/esm/bu.d.ts.map +1 -1
- package/esm/bu.js +10 -4
- package/esm/bu.js.map +1 -1
- package/esm/bug-report.d.ts +21 -20
- package/esm/bug-report.d.ts.map +1 -1
- package/esm/bug-report.js +15 -21
- package/esm/bug-report.js.map +1 -1
- package/esm/cmd/index.d.ts +2 -0
- package/esm/cmd/index.d.ts.map +1 -0
- package/esm/cmd/index.js +2 -0
- package/esm/cmd/index.js.map +1 -0
- package/esm/cmd/none.d.ts +5 -0
- package/esm/cmd/none.d.ts.map +1 -0
- package/esm/cmd/none.js +39 -0
- package/esm/cmd/none.js.map +1 -0
- package/esm/cmd/service.d.ts +29 -0
- package/esm/cmd/service.d.ts.map +1 -0
- package/esm/cmd/service.js +24 -0
- package/esm/cmd/service.js.map +1 -0
- package/esm/cmd/shell.d.ts +5 -0
- package/esm/cmd/shell.d.ts.map +1 -0
- package/esm/cmd/shell.js +43 -0
- package/esm/cmd/shell.js.map +1 -0
- package/esm/cmd/utils.d.ts +5 -0
- package/esm/cmd/utils.d.ts.map +1 -0
- package/esm/cmd/utils.js +24 -0
- package/esm/cmd/utils.js.map +1 -0
- package/esm/demo-mode.d.ts +3 -4
- package/esm/demo-mode.d.ts.map +1 -1
- package/esm/demo-mode.js +13 -21
- package/esm/demo-mode.js.map +1 -1
- package/esm/dumpsys.d.ts +4 -2
- package/esm/dumpsys.d.ts.map +1 -1
- package/esm/dumpsys.js +13 -10
- package/esm/dumpsys.js.map +1 -1
- package/esm/index.d.ts +1 -1
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +1 -1
- package/esm/index.js.map +1 -1
- package/esm/intent.d.ts +37 -9
- package/esm/intent.d.ts.map +1 -1
- package/esm/intent.js +116 -53
- package/esm/intent.js.map +1 -1
- package/esm/logcat.d.ts +4 -2
- package/esm/logcat.d.ts.map +1 -1
- package/esm/logcat.js +9 -6
- package/esm/logcat.js.map +1 -1
- package/esm/overlay-display.d.ts +17 -14
- package/esm/overlay-display.d.ts.map +1 -1
- package/esm/overlay-display.js +2 -4
- package/esm/overlay-display.js.map +1 -1
- package/esm/pm.d.ts +131 -168
- package/esm/pm.d.ts.map +1 -1
- package/esm/pm.js +229 -112
- package/esm/pm.js.map +1 -1
- package/esm/settings.d.ts +1 -2
- package/esm/settings.d.ts.map +1 -1
- package/esm/settings.js +9 -9
- package/esm/settings.js.map +1 -1
- package/esm/utils.d.ts +4 -1
- package/esm/utils.d.ts.map +1 -1
- package/esm/utils.js +24 -13
- package/esm/utils.js.map +1 -1
- package/package.json +9 -9
- package/src/am.ts +94 -34
- package/src/bu.ts +31 -20
- package/src/bug-report.ts +42 -42
- package/src/cmd/index.ts +1 -0
- package/src/cmd/none.ts +62 -0
- package/src/cmd/service.ts +54 -0
- package/src/cmd/shell.ts +67 -0
- package/src/cmd/utils.ts +35 -0
- package/src/demo-mode.ts +13 -24
- package/src/dumpsys.ts +16 -10
- package/src/index.ts +1 -1
- package/src/intent.ts +187 -49
- package/src/logcat.ts +12 -6
- package/src/overlay-display.ts +21 -19
- package/src/pm.ts +374 -301
- package/src/settings.ts +11 -10
- package/src/utils.ts +30 -13
- package/tsconfig.build.json +0 -11
- package/CHANGELOG.md +0 -181
- package/esm/cmd.d.ts +0 -23
- package/esm/cmd.d.ts.map +0 -1
- package/esm/cmd.js +0 -104
- package/esm/cmd.js.map +0 -1
- package/src/cmd.ts +0 -166
- package/tsconfig.build.tsbuildinfo +0 -1
package/src/pm.ts
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
// cspell:ignore instantapp
|
|
3
3
|
// cspell:ignore apks
|
|
4
4
|
// cspell:ignore versioncode
|
|
5
|
+
// cspell:ignore dexopt
|
|
5
6
|
|
|
6
|
-
import type { Adb } from "@yume-chan/adb";
|
|
7
|
-
import {
|
|
7
|
+
import type { Adb, AdbNoneProtocolProcess } from "@yume-chan/adb";
|
|
8
|
+
import { escapeArg } from "@yume-chan/adb";
|
|
8
9
|
import type { MaybeConsumable, ReadableStream } from "@yume-chan/stream-extra";
|
|
9
10
|
import {
|
|
10
11
|
ConcatStringStream,
|
|
@@ -12,10 +13,11 @@ import {
|
|
|
12
13
|
TextDecoderStream,
|
|
13
14
|
} from "@yume-chan/stream-extra";
|
|
14
15
|
|
|
15
|
-
import {
|
|
16
|
-
import type {
|
|
17
|
-
import
|
|
18
|
-
import {
|
|
16
|
+
import { Cmd } from "./cmd/index.js";
|
|
17
|
+
import type { Intent } from "./intent.js";
|
|
18
|
+
import { serializeIntent } from "./intent.js";
|
|
19
|
+
import type { Optional, SingleUserOrAll } from "./utils.js";
|
|
20
|
+
import { buildCommand } from "./utils.js";
|
|
19
21
|
|
|
20
22
|
export enum PackageManagerInstallLocation {
|
|
21
23
|
Auto,
|
|
@@ -31,173 +33,83 @@ export enum PackageManagerInstallReason {
|
|
|
31
33
|
UserRequest,
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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;
|
|
36
|
+
interface OptionDefinition<T> {
|
|
37
|
+
type: T;
|
|
38
|
+
name: string;
|
|
39
|
+
minApiLevel?: number;
|
|
40
|
+
maxApiLevel?: number;
|
|
148
41
|
}
|
|
149
42
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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;
|
|
43
|
+
function option<T>(
|
|
44
|
+
name: string,
|
|
45
|
+
minApiLevel?: number,
|
|
46
|
+
maxApiLevel?: number,
|
|
47
|
+
): OptionDefinition<T> {
|
|
48
|
+
return {
|
|
49
|
+
name,
|
|
50
|
+
minApiLevel,
|
|
51
|
+
maxApiLevel,
|
|
52
|
+
} as OptionDefinition<T>;
|
|
197
53
|
}
|
|
198
54
|
|
|
199
|
-
|
|
200
|
-
|
|
55
|
+
// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java;l=3046;drc=6d14d35d0241f6fee145f8e54ffd77252e8d29fd
|
|
56
|
+
const InstallOptionsDefinitions = {
|
|
57
|
+
forwardLock: option<boolean>("-l", undefined, 28),
|
|
58
|
+
replaceExisting: option<boolean>("-r", undefined, 27),
|
|
59
|
+
skipExisting: option<boolean>("-R", 28),
|
|
60
|
+
installerPackageName: option<string>("-i"),
|
|
61
|
+
allowTest: option<boolean>("-t"),
|
|
62
|
+
externalStorage: option<boolean>("-s", undefined, 28),
|
|
63
|
+
internalStorage: option<boolean>("-f"),
|
|
64
|
+
requestDowngrade: option<boolean>("-d"),
|
|
65
|
+
grantRuntimePermissions: option<boolean>("-g", 23),
|
|
66
|
+
restrictPermissions: option<boolean>("--restrict-permissions", 29),
|
|
67
|
+
doNotKill: option<boolean>("--dont-kill"),
|
|
68
|
+
originatingUri: option<string>("--originating-uri"),
|
|
69
|
+
referrerUri: option<string>("--referrer"),
|
|
70
|
+
inheritFrom: option<string>("-p", 24),
|
|
71
|
+
packageName: option<string>("--pkg", 28),
|
|
72
|
+
abi: option<string>("--abi", 21),
|
|
73
|
+
instantApp: option<boolean>("--ephemeral", 24),
|
|
74
|
+
full: option<boolean>("--full", 26),
|
|
75
|
+
preload: option<boolean>("--preload", 28),
|
|
76
|
+
user: option<SingleUserOrAll>("--user", 21),
|
|
77
|
+
installLocation: option<PackageManagerInstallLocation>(
|
|
78
|
+
"--install-location",
|
|
79
|
+
24,
|
|
80
|
+
),
|
|
81
|
+
installReason: option<PackageManagerInstallReason>("--install-reason", 29),
|
|
82
|
+
updateOwnership: option<boolean>("--update-ownership", 34),
|
|
83
|
+
forceUuid: option<string>("--force-uuid", 24),
|
|
84
|
+
forceSdk: option<number>("--force-sdk", 24),
|
|
85
|
+
apex: option<boolean>("--apex", 29),
|
|
86
|
+
forceNonStaged: option<boolean>("--force-non-staged", 31),
|
|
87
|
+
multiPackage: option<boolean>("--multi-package", 29),
|
|
88
|
+
staged: option<boolean>("--staged", 29),
|
|
89
|
+
nonStaged: option<boolean>("--non-staged", 35),
|
|
90
|
+
forceQueryable: option<boolean>("--force-queryable", 30),
|
|
91
|
+
enableRollback: option<boolean | number>("--enable-rollback", 29),
|
|
92
|
+
rollbackImpactLevel: option<number>("--rollback-impact-level", 35),
|
|
93
|
+
wait: option<boolean | number>("--wait", 30, 30),
|
|
94
|
+
noWait: option<boolean>("--no-wait", 30, 30),
|
|
95
|
+
stagedReadyTimeout: option<number>("--staged-ready-timeout", 31),
|
|
96
|
+
skipVerification: option<boolean>("--skip-verification", 30),
|
|
97
|
+
skipEnable: option<boolean>("--skip-enable", 34),
|
|
98
|
+
bypassLowTargetSdkBlock: option<boolean>(
|
|
99
|
+
"--bypass-low-target-sdk-block",
|
|
100
|
+
34,
|
|
101
|
+
),
|
|
102
|
+
ignoreDexoptProfile: option<boolean>("--ignore-dexopt-profile", 35),
|
|
103
|
+
packageSource: option<number>("--package-source", 35),
|
|
104
|
+
dexoptCompilerFilter: option<string>("--dexopt-compiler-filter", 35),
|
|
105
|
+
disableAutoInstallDependencies: option<boolean>(
|
|
106
|
+
"--disable-auto-install-dependencies",
|
|
107
|
+
36,
|
|
108
|
+
),
|
|
109
|
+
} as const;
|
|
110
|
+
|
|
111
|
+
const ListPackagesOptionsMap: Record<
|
|
112
|
+
keyof PackageManager.ListPackagesOptions,
|
|
201
113
|
string
|
|
202
114
|
> = {
|
|
203
115
|
listDisabled: "-d",
|
|
@@ -214,28 +126,8 @@ export const PACKAGE_MANAGER_LIST_PACKAGES_OPTIONS_MAP: Record<
|
|
|
214
126
|
filter: "",
|
|
215
127
|
};
|
|
216
128
|
|
|
217
|
-
|
|
218
|
-
|
|
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: readonly string[];
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
const PACKAGE_MANAGER_UNINSTALL_OPTIONS_MAP: Record<
|
|
238
|
-
keyof PackageManagerUninstallOptions,
|
|
129
|
+
const UninstallOptionsMap: Record<
|
|
130
|
+
keyof PackageManager.UninstallOptions,
|
|
239
131
|
string
|
|
240
132
|
> = {
|
|
241
133
|
keepData: "-k",
|
|
@@ -244,26 +136,19 @@ const PACKAGE_MANAGER_UNINSTALL_OPTIONS_MAP: Record<
|
|
|
244
136
|
splitNames: "",
|
|
245
137
|
};
|
|
246
138
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
intent: IntentBuilder;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
const PACKAGE_MANAGER_RESOLVE_ACTIVITY_OPTIONS_MAP: Partial<
|
|
253
|
-
Record<keyof PackageManagerResolveActivityOptions, string>
|
|
139
|
+
const ResolveActivityOptionsMap: Partial<
|
|
140
|
+
Record<keyof PackageManager.ResolveActivityOptions, string>
|
|
254
141
|
> = {
|
|
255
142
|
user: "--user",
|
|
256
143
|
};
|
|
257
144
|
|
|
258
|
-
function
|
|
145
|
+
function buildInstallCommand(
|
|
259
146
|
command: string,
|
|
260
|
-
options:
|
|
147
|
+
options: PackageManager.InstallOptions | undefined,
|
|
148
|
+
apiLevel: number | undefined,
|
|
261
149
|
): string[] {
|
|
262
|
-
const args =
|
|
263
|
-
|
|
264
|
-
options,
|
|
265
|
-
PACKAGE_MANAGER_INSTALL_OPTIONS_MAP,
|
|
266
|
-
);
|
|
150
|
+
const args = [PackageManager.ServiceName, command];
|
|
151
|
+
|
|
267
152
|
if (!options?.skipExisting) {
|
|
268
153
|
/*
|
|
269
154
|
* | behavior | previous version | modern version |
|
|
@@ -278,18 +163,74 @@ function buildInstallArguments(
|
|
|
278
163
|
*/
|
|
279
164
|
args.push("-r");
|
|
280
165
|
}
|
|
166
|
+
|
|
167
|
+
if (!options) {
|
|
168
|
+
return args;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
for (const [key, value] of Object.entries(options)) {
|
|
172
|
+
if (value === undefined || value === null) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const option =
|
|
177
|
+
InstallOptionsDefinitions[
|
|
178
|
+
key as keyof PackageManager.InstallOptions
|
|
179
|
+
];
|
|
180
|
+
|
|
181
|
+
if (option === undefined) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (apiLevel !== undefined) {
|
|
186
|
+
if (
|
|
187
|
+
option.minApiLevel !== undefined &&
|
|
188
|
+
apiLevel < option.minApiLevel
|
|
189
|
+
) {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (
|
|
193
|
+
option.maxApiLevel !== undefined &&
|
|
194
|
+
apiLevel > option.maxApiLevel
|
|
195
|
+
) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
switch (typeof value) {
|
|
201
|
+
case "boolean":
|
|
202
|
+
if (value) {
|
|
203
|
+
args.push(option.name);
|
|
204
|
+
}
|
|
205
|
+
break;
|
|
206
|
+
case "number":
|
|
207
|
+
args.push(option.name, value.toString());
|
|
208
|
+
break;
|
|
209
|
+
case "string":
|
|
210
|
+
args.push(option.name, value);
|
|
211
|
+
break;
|
|
212
|
+
default:
|
|
213
|
+
throw new Error(
|
|
214
|
+
`Unsupported type for option ${key}: ${typeof value}`,
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
281
219
|
return args;
|
|
282
220
|
}
|
|
283
221
|
|
|
284
|
-
export class PackageManager
|
|
285
|
-
static ServiceName = "package";
|
|
286
|
-
static CommandName = "pm";
|
|
222
|
+
export class PackageManager {
|
|
223
|
+
static readonly ServiceName = "package";
|
|
224
|
+
static readonly CommandName = "pm";
|
|
287
225
|
|
|
288
|
-
#
|
|
226
|
+
readonly #adb: Adb;
|
|
227
|
+
readonly #apiLevel: number | undefined;
|
|
228
|
+
readonly #cmd: Cmd.NoneProtocolService;
|
|
289
229
|
|
|
290
|
-
constructor(adb: Adb) {
|
|
291
|
-
|
|
292
|
-
this.#
|
|
230
|
+
constructor(adb: Adb, apiLevel?: number) {
|
|
231
|
+
this.#adb = adb;
|
|
232
|
+
this.#apiLevel = apiLevel;
|
|
233
|
+
this.#cmd = Cmd.createNoneProtocol(adb, PackageManager.CommandName);
|
|
293
234
|
}
|
|
294
235
|
|
|
295
236
|
/**
|
|
@@ -299,76 +240,74 @@ export class PackageManager extends AdbServiceBase {
|
|
|
299
240
|
*/
|
|
300
241
|
async install(
|
|
301
242
|
apks: readonly string[],
|
|
302
|
-
options?:
|
|
303
|
-
): Promise<
|
|
304
|
-
const
|
|
305
|
-
|
|
243
|
+
options?: PackageManager.InstallOptions,
|
|
244
|
+
): Promise<void> {
|
|
245
|
+
const command = buildInstallCommand("install", options, this.#apiLevel);
|
|
246
|
+
|
|
247
|
+
command[0] = PackageManager.CommandName;
|
|
248
|
+
|
|
306
249
|
// WIP: old version of pm doesn't support multiple apks
|
|
307
|
-
|
|
250
|
+
for (const apk of apks) {
|
|
251
|
+
command.push(apk);
|
|
252
|
+
}
|
|
308
253
|
|
|
309
254
|
// Starting from Android 7, `pm` becomes a wrapper to `cmd package`.
|
|
310
255
|
// The benefit of `cmd package` is it starts faster than the old `pm`,
|
|
311
256
|
// because it connects to the already running `system` process,
|
|
312
257
|
// instead of initializing all system components from scratch.
|
|
313
258
|
//
|
|
314
|
-
// But
|
|
315
|
-
//
|
|
316
|
-
// so installing files must still use `pm`.
|
|
259
|
+
// But `cmd` executable can't read files in `/data/local/tmp`
|
|
260
|
+
// (and many other places) due to SELinux policies,
|
|
261
|
+
// so installing from files must still use `pm`.
|
|
317
262
|
// (the starting executable file decides which SELinux policies to apply)
|
|
318
|
-
const output = await this
|
|
319
|
-
.
|
|
263
|
+
const output = await this.#adb.subprocess.noneProtocol
|
|
264
|
+
.spawn(command.map(escapeArg))
|
|
265
|
+
.wait()
|
|
266
|
+
.toString()
|
|
320
267
|
.then((output) => output.trim());
|
|
321
268
|
|
|
322
269
|
if (output !== "Success") {
|
|
323
270
|
throw new Error(output);
|
|
324
271
|
}
|
|
325
|
-
|
|
326
|
-
return output;
|
|
327
272
|
}
|
|
328
273
|
|
|
329
274
|
async pushAndInstallStream(
|
|
330
275
|
stream: ReadableStream<MaybeConsumable<Uint8Array>>,
|
|
331
|
-
options?:
|
|
332
|
-
): Promise<
|
|
276
|
+
options?: PackageManager.InstallOptions,
|
|
277
|
+
): Promise<void> {
|
|
333
278
|
const fileName = Math.random().toString().substring(2);
|
|
334
279
|
const filePath = `/data/local/tmp/${fileName}.apk`;
|
|
335
280
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
filename: filePath,
|
|
341
|
-
file: stream,
|
|
342
|
-
});
|
|
343
|
-
} finally {
|
|
344
|
-
await sync.dispose();
|
|
345
|
-
}
|
|
281
|
+
await this.#adb.sync.write({
|
|
282
|
+
filename: filePath,
|
|
283
|
+
file: stream,
|
|
284
|
+
});
|
|
346
285
|
|
|
347
286
|
try {
|
|
348
|
-
|
|
287
|
+
await this.install([filePath], options);
|
|
349
288
|
} finally {
|
|
350
|
-
await this
|
|
289
|
+
await this.#adb.rm(filePath);
|
|
351
290
|
}
|
|
352
291
|
}
|
|
353
292
|
|
|
354
293
|
async installStream(
|
|
355
294
|
size: number,
|
|
356
295
|
stream: ReadableStream<MaybeConsumable<Uint8Array>>,
|
|
357
|
-
options?:
|
|
296
|
+
options?: PackageManager.InstallOptions,
|
|
358
297
|
): Promise<void> {
|
|
359
|
-
//
|
|
360
|
-
//
|
|
361
|
-
//
|
|
362
|
-
//
|
|
363
|
-
if (
|
|
298
|
+
// Technically, support for `cmd` and streaming install are unrelated,
|
|
299
|
+
// but it's impossible to detect streaming install support without actually trying it.
|
|
300
|
+
// As they are both added in Android 7,
|
|
301
|
+
// assume `cmd` support also means streaming install support (and vice versa).
|
|
302
|
+
if (this.#cmd.mode === Cmd.Mode.Fallback) {
|
|
364
303
|
// Fall back to push file then install
|
|
365
304
|
await this.pushAndInstallStream(stream, options);
|
|
366
305
|
return;
|
|
367
306
|
}
|
|
368
307
|
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
const process = await this.#cmd.spawn(
|
|
308
|
+
const command = buildInstallCommand("install", options, this.#apiLevel);
|
|
309
|
+
command.push("-S", size.toString());
|
|
310
|
+
const process = await this.#cmd.spawn(command);
|
|
372
311
|
|
|
373
312
|
const output = process.output
|
|
374
313
|
.pipeThrough(new TextDecoderStream())
|
|
@@ -385,10 +324,10 @@ export class PackageManager extends AdbServiceBase {
|
|
|
385
324
|
]);
|
|
386
325
|
}
|
|
387
326
|
|
|
388
|
-
static
|
|
389
|
-
|
|
390
|
-
):
|
|
391
|
-
line = line.substring(
|
|
327
|
+
static readonly PackageListItemPrefix = "package:";
|
|
328
|
+
|
|
329
|
+
static parsePackageListItem(line: string): PackageManager.PackageInfo {
|
|
330
|
+
line = line.substring(PackageManager.PackageListItemPrefix.length);
|
|
392
331
|
|
|
393
332
|
let packageName: string;
|
|
394
333
|
let sourceDir: string | undefined;
|
|
@@ -439,41 +378,84 @@ export class PackageManager extends AdbServiceBase {
|
|
|
439
378
|
}
|
|
440
379
|
|
|
441
380
|
async *listPackages(
|
|
442
|
-
options?:
|
|
443
|
-
): AsyncGenerator<
|
|
444
|
-
const
|
|
381
|
+
options?: Optional<PackageManager.ListPackagesOptions>,
|
|
382
|
+
): AsyncGenerator<PackageManager.PackageInfo, void, void> {
|
|
383
|
+
const command = buildCommand(
|
|
445
384
|
["package", "list", "packages"],
|
|
446
385
|
options,
|
|
447
|
-
|
|
386
|
+
ListPackagesOptionsMap,
|
|
448
387
|
);
|
|
388
|
+
|
|
449
389
|
if (options?.filter) {
|
|
450
|
-
|
|
390
|
+
command.push(options.filter);
|
|
451
391
|
}
|
|
452
392
|
|
|
453
|
-
const process = await this.#cmd.spawn(
|
|
454
|
-
|
|
393
|
+
const process = await this.#cmd.spawn(command);
|
|
394
|
+
|
|
395
|
+
const output = process.output
|
|
455
396
|
.pipeThrough(new TextDecoderStream())
|
|
456
|
-
.pipeThrough(new SplitStringStream("\n"))
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
break;
|
|
397
|
+
.pipeThrough(new SplitStringStream("\n", { trim: true }));
|
|
398
|
+
|
|
399
|
+
for await (const line of output) {
|
|
400
|
+
if (!line.startsWith(PackageManager.PackageListItemPrefix)) {
|
|
401
|
+
continue;
|
|
462
402
|
}
|
|
463
|
-
|
|
403
|
+
|
|
404
|
+
yield PackageManager.parsePackageListItem(line);
|
|
464
405
|
}
|
|
465
406
|
}
|
|
466
407
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
408
|
+
/**
|
|
409
|
+
* Gets APK file paths for a package.
|
|
410
|
+
*
|
|
411
|
+
* On supported Android versions, all split APKs are included.
|
|
412
|
+
* @param packageName The package name to query
|
|
413
|
+
* @param options The user ID to query
|
|
414
|
+
* @returns An array of APK file paths
|
|
415
|
+
*/
|
|
416
|
+
async getPackageSources(
|
|
417
|
+
packageName: string,
|
|
418
|
+
options?: {
|
|
419
|
+
/**
|
|
420
|
+
* The user ID to query
|
|
421
|
+
*/
|
|
422
|
+
user?: number | undefined;
|
|
423
|
+
},
|
|
424
|
+
): Promise<string[]> {
|
|
425
|
+
// `pm path` and `pm -p` are the same,
|
|
426
|
+
// but `pm path` allows an optional `--user` option.
|
|
427
|
+
const command = [PackageManager.ServiceName, "path"];
|
|
428
|
+
|
|
429
|
+
if (options?.user !== undefined) {
|
|
430
|
+
command.push("--user", options.user.toString());
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
command.push(packageName);
|
|
434
|
+
|
|
435
|
+
// Android 7 and 8 support `cmd package` but not `cmd package path` command
|
|
436
|
+
let process: AdbNoneProtocolProcess;
|
|
437
|
+
if (this.#apiLevel !== undefined && this.#apiLevel <= 27) {
|
|
438
|
+
command[0] = PackageManager.CommandName;
|
|
439
|
+
process = await this.#adb.subprocess.noneProtocol.spawn(
|
|
440
|
+
command.map(escapeArg),
|
|
441
|
+
);
|
|
442
|
+
} else {
|
|
443
|
+
process = await this.#cmd.spawn(command);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const lines = process.output
|
|
472
447
|
.pipeThrough(new TextDecoderStream())
|
|
473
|
-
.pipeThrough(new SplitStringStream("\n"))
|
|
474
|
-
|
|
475
|
-
|
|
448
|
+
.pipeThrough(new SplitStringStream("\n", { trim: true }));
|
|
449
|
+
|
|
450
|
+
const result: string[] = [];
|
|
451
|
+
for await (const line of lines) {
|
|
452
|
+
if (!line.startsWith(PackageManager.PackageListItemPrefix)) {
|
|
453
|
+
continue;
|
|
476
454
|
}
|
|
455
|
+
|
|
456
|
+
result.push(
|
|
457
|
+
line.substring(PackageManager.PackageListItemPrefix.length),
|
|
458
|
+
);
|
|
477
459
|
}
|
|
478
460
|
|
|
479
461
|
return result;
|
|
@@ -481,20 +463,26 @@ export class PackageManager extends AdbServiceBase {
|
|
|
481
463
|
|
|
482
464
|
async uninstall(
|
|
483
465
|
packageName: string,
|
|
484
|
-
options?:
|
|
466
|
+
options?: Optional<PackageManager.UninstallOptions>,
|
|
485
467
|
): Promise<void> {
|
|
486
|
-
const
|
|
468
|
+
const command = buildCommand(
|
|
487
469
|
[PackageManager.ServiceName, "uninstall"],
|
|
488
470
|
options,
|
|
489
|
-
|
|
471
|
+
UninstallOptionsMap,
|
|
490
472
|
);
|
|
491
|
-
|
|
473
|
+
|
|
474
|
+
command.push(packageName);
|
|
475
|
+
|
|
492
476
|
if (options?.splitNames) {
|
|
493
|
-
|
|
477
|
+
for (const splitName of options.splitNames) {
|
|
478
|
+
command.push(splitName);
|
|
479
|
+
}
|
|
494
480
|
}
|
|
495
481
|
|
|
496
482
|
const output = await this.#cmd
|
|
497
|
-
.
|
|
483
|
+
.spawn(command)
|
|
484
|
+
.wait()
|
|
485
|
+
.toString()
|
|
498
486
|
.then((output) => output.trim());
|
|
499
487
|
if (output !== "Success") {
|
|
500
488
|
throw new Error(output);
|
|
@@ -502,18 +490,22 @@ export class PackageManager extends AdbServiceBase {
|
|
|
502
490
|
}
|
|
503
491
|
|
|
504
492
|
async resolveActivity(
|
|
505
|
-
options:
|
|
493
|
+
options: PackageManager.ResolveActivityOptions,
|
|
506
494
|
): Promise<string | undefined> {
|
|
507
|
-
|
|
495
|
+
const command = buildCommand(
|
|
508
496
|
[PackageManager.ServiceName, "resolve-activity", "--components"],
|
|
509
497
|
options,
|
|
510
|
-
|
|
498
|
+
ResolveActivityOptionsMap,
|
|
511
499
|
);
|
|
512
500
|
|
|
513
|
-
|
|
501
|
+
for (const arg of serializeIntent(options.intent)) {
|
|
502
|
+
command.push(arg);
|
|
503
|
+
}
|
|
514
504
|
|
|
515
505
|
const output = await this.#cmd
|
|
516
|
-
.
|
|
506
|
+
.spawn(command)
|
|
507
|
+
.wait()
|
|
508
|
+
.toString()
|
|
517
509
|
.then((output) => output.trim());
|
|
518
510
|
|
|
519
511
|
if (output === "No activity found") {
|
|
@@ -534,20 +526,28 @@ export class PackageManager extends AdbServiceBase {
|
|
|
534
526
|
* @returns ID of the new install session
|
|
535
527
|
*/
|
|
536
528
|
async sessionCreate(
|
|
537
|
-
options?:
|
|
529
|
+
options?: PackageManager.InstallOptions,
|
|
538
530
|
): Promise<number> {
|
|
539
|
-
const
|
|
531
|
+
const command = buildInstallCommand(
|
|
532
|
+
"install-create",
|
|
533
|
+
options,
|
|
534
|
+
this.#apiLevel,
|
|
535
|
+
);
|
|
540
536
|
|
|
541
537
|
const output = await this.#cmd
|
|
542
|
-
.
|
|
538
|
+
.spawn(command)
|
|
539
|
+
.wait()
|
|
540
|
+
.toString()
|
|
543
541
|
.then((output) => output.trim());
|
|
544
542
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
543
|
+
// The output format won't change to make it easier to parse
|
|
544
|
+
// https://cs.android.com/android/platform/superproject/+/android-latest-release:frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java;l=1744;drc=e38fa24e5738513d721ec2d9fd2dd00f32e327c1
|
|
545
|
+
const match = output.match(/\[(\d+)\]/);
|
|
546
|
+
if (!match) {
|
|
547
|
+
throw new Error(output);
|
|
548
548
|
}
|
|
549
549
|
|
|
550
|
-
return Number.parseInt(
|
|
550
|
+
return Number.parseInt(match[1]!, 10);
|
|
551
551
|
}
|
|
552
552
|
|
|
553
553
|
async checkResult(stream: ReadableStream<Uint8Array>) {
|
|
@@ -566,15 +566,18 @@ export class PackageManager extends AdbServiceBase {
|
|
|
566
566
|
splitName: string,
|
|
567
567
|
path: string,
|
|
568
568
|
): Promise<void> {
|
|
569
|
-
const
|
|
570
|
-
|
|
569
|
+
const command: string[] = [
|
|
570
|
+
PackageManager.CommandName,
|
|
571
571
|
"install-write",
|
|
572
572
|
sessionId.toString(),
|
|
573
573
|
splitName,
|
|
574
574
|
path,
|
|
575
575
|
];
|
|
576
576
|
|
|
577
|
-
|
|
577
|
+
// Similar to `install`, must use `adb.subprocess` so it can read `path`
|
|
578
|
+
const process = await this.#adb.subprocess.noneProtocol.spawn(
|
|
579
|
+
command.map(escapeArg),
|
|
580
|
+
);
|
|
578
581
|
await this.checkResult(process.output);
|
|
579
582
|
}
|
|
580
583
|
|
|
@@ -584,7 +587,7 @@ export class PackageManager extends AdbServiceBase {
|
|
|
584
587
|
size: number,
|
|
585
588
|
stream: ReadableStream<MaybeConsumable<Uint8Array>>,
|
|
586
589
|
): Promise<void> {
|
|
587
|
-
const
|
|
590
|
+
const command: string[] = [
|
|
588
591
|
PackageManager.ServiceName,
|
|
589
592
|
"install-write",
|
|
590
593
|
"-S",
|
|
@@ -594,38 +597,104 @@ export class PackageManager extends AdbServiceBase {
|
|
|
594
597
|
"-",
|
|
595
598
|
];
|
|
596
599
|
|
|
597
|
-
const process = await this.#cmd.spawn(
|
|
600
|
+
const process = await this.#cmd.spawn(command);
|
|
598
601
|
await Promise.all([
|
|
599
602
|
stream.pipeTo(process.stdin),
|
|
600
603
|
this.checkResult(process.output),
|
|
601
604
|
]);
|
|
602
605
|
}
|
|
603
606
|
|
|
607
|
+
/**
|
|
608
|
+
* Commit an install session.
|
|
609
|
+
* @param sessionId ID of install session returned by `createSession`
|
|
610
|
+
* @returns A `Promise` that resolves when the session is committed
|
|
611
|
+
*/
|
|
604
612
|
async sessionCommit(sessionId: number): Promise<void> {
|
|
605
|
-
const
|
|
613
|
+
const command: string[] = [
|
|
606
614
|
PackageManager.ServiceName,
|
|
607
615
|
"install-commit",
|
|
608
616
|
sessionId.toString(),
|
|
609
617
|
];
|
|
610
|
-
|
|
618
|
+
|
|
619
|
+
// Android 7 did support `cmd package install-commit` command,
|
|
620
|
+
// but it wrote the "Success" message to an incorrect output stream,
|
|
621
|
+
// causing `checkResult` to fail with an empty message
|
|
622
|
+
let process: AdbNoneProtocolProcess;
|
|
623
|
+
if (this.#apiLevel !== undefined && this.#apiLevel <= 25) {
|
|
624
|
+
command[0] = PackageManager.CommandName;
|
|
625
|
+
process = await this.#adb.subprocess.noneProtocol.spawn(
|
|
626
|
+
command.map(escapeArg),
|
|
627
|
+
);
|
|
628
|
+
} else {
|
|
629
|
+
process = await this.#cmd.spawn(command);
|
|
630
|
+
}
|
|
631
|
+
|
|
611
632
|
await this.checkResult(process.output);
|
|
612
633
|
}
|
|
613
634
|
|
|
614
635
|
async sessionAbandon(sessionId: number): Promise<void> {
|
|
615
|
-
const
|
|
636
|
+
const command: string[] = [
|
|
616
637
|
PackageManager.ServiceName,
|
|
617
638
|
"install-abandon",
|
|
618
639
|
sessionId.toString(),
|
|
619
640
|
];
|
|
620
|
-
const process = await this.#cmd.spawn(
|
|
641
|
+
const process = await this.#cmd.spawn(command);
|
|
621
642
|
await this.checkResult(process.output);
|
|
622
643
|
}
|
|
623
644
|
}
|
|
624
645
|
|
|
646
|
+
export namespace PackageManager {
|
|
647
|
+
export type InstallOptions = {
|
|
648
|
+
[K in keyof typeof InstallOptionsDefinitions]?:
|
|
649
|
+
| (typeof InstallOptionsDefinitions)[K]["type"]
|
|
650
|
+
| undefined;
|
|
651
|
+
};
|
|
652
|
+
|
|
653
|
+
export interface ListPackagesOptions {
|
|
654
|
+
listDisabled: boolean;
|
|
655
|
+
listEnabled: boolean;
|
|
656
|
+
showSourceDir: boolean;
|
|
657
|
+
showInstaller: boolean;
|
|
658
|
+
listSystem: boolean;
|
|
659
|
+
showUid: boolean;
|
|
660
|
+
listThirdParty: boolean;
|
|
661
|
+
showVersionCode: boolean;
|
|
662
|
+
listApexOnly: boolean;
|
|
663
|
+
user: SingleUserOrAll;
|
|
664
|
+
uid: number;
|
|
665
|
+
filter: string;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
export interface PackageInfo {
|
|
669
|
+
packageName: string;
|
|
670
|
+
sourceDir?: string | undefined;
|
|
671
|
+
versionCode?: number | undefined;
|
|
672
|
+
installer?: string | undefined;
|
|
673
|
+
uid?: number | undefined;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
export interface UninstallOptions {
|
|
677
|
+
keepData: boolean;
|
|
678
|
+
user: SingleUserOrAll;
|
|
679
|
+
versionCode: number;
|
|
680
|
+
/**
|
|
681
|
+
* Only remove the specified splits, not the entire app
|
|
682
|
+
*
|
|
683
|
+
* On Android 10 and lower, only one split name can be specified.
|
|
684
|
+
*/
|
|
685
|
+
splitNames: readonly string[];
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
export interface ResolveActivityOptions {
|
|
689
|
+
user?: SingleUserOrAll;
|
|
690
|
+
intent: Intent;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
625
694
|
export class PackageManagerInstallSession {
|
|
626
695
|
static async create(
|
|
627
696
|
packageManager: PackageManager,
|
|
628
|
-
options?:
|
|
697
|
+
options?: PackageManager.InstallOptions,
|
|
629
698
|
): Promise<PackageManagerInstallSession> {
|
|
630
699
|
const id = await packageManager.sessionCreate(options);
|
|
631
700
|
return new PackageManagerInstallSession(packageManager, id);
|
|
@@ -660,6 +729,10 @@ export class PackageManagerInstallSession {
|
|
|
660
729
|
);
|
|
661
730
|
}
|
|
662
731
|
|
|
732
|
+
/**
|
|
733
|
+
* Commit this install session.
|
|
734
|
+
* @returns A `Promise` that resolves when the session is committed
|
|
735
|
+
*/
|
|
663
736
|
commit(): Promise<void> {
|
|
664
737
|
return this.#packageManager.sessionCommit(this.#id);
|
|
665
738
|
}
|