mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-03 17:59:50 +02:00
feat(bin): support pm list packages
This commit is contained in:
parent
d9685acc6f
commit
bf76ce006f
3 changed files with 141 additions and 16 deletions
|
@ -177,6 +177,47 @@ export const PACKAGE_MANAGER_INSTALL_OPTIONS_MAP: Record<
|
|||
bypassLowTargetSdkBlock: "--bypass-low-target-sdk-block",
|
||||
};
|
||||
|
||||
export interface PackageManagerListPackagesOptions {
|
||||
listDisabled: boolean;
|
||||
listEnabled: boolean;
|
||||
showSourceDir: boolean;
|
||||
showInstaller: boolean;
|
||||
listSystem: boolean;
|
||||
showUid: boolean;
|
||||
listThirdParty: boolean;
|
||||
showVersionCode: boolean;
|
||||
listApexOnly: boolean;
|
||||
user: "all" | "current" | number;
|
||||
uid: number;
|
||||
filter: string;
|
||||
}
|
||||
|
||||
export const PACKAGE_MANAGER_LIST_PACKAGES_OPTIONS_MAP: Record<
|
||||
keyof PackageManagerListPackagesOptions,
|
||||
string
|
||||
> = {
|
||||
listDisabled: "-d",
|
||||
listEnabled: "-e",
|
||||
showSourceDir: "-f",
|
||||
showInstaller: "-i",
|
||||
listSystem: "-s",
|
||||
showUid: "-U",
|
||||
listThirdParty: "-3",
|
||||
showVersionCode: "--show-versioncode",
|
||||
listApexOnly: "--apex-only",
|
||||
user: "--user",
|
||||
uid: "--uid",
|
||||
filter: "",
|
||||
};
|
||||
|
||||
export interface PackageManagerListPackagesResult {
|
||||
packageName: string;
|
||||
sourceDir?: string | undefined;
|
||||
versionCode?: number | undefined;
|
||||
installer?: string | undefined;
|
||||
uid?: number | undefined;
|
||||
}
|
||||
|
||||
export class PackageManager extends AdbCommandBase {
|
||||
private _cmd: Cmd;
|
||||
|
||||
|
@ -185,17 +226,16 @@ export class PackageManager extends AdbCommandBase {
|
|||
this._cmd = new Cmd(adb);
|
||||
}
|
||||
|
||||
private buildInstallArgs(
|
||||
options?: Partial<PackageManagerInstallOptions>
|
||||
private buildArguments<T>(
|
||||
commands: string[],
|
||||
options: Partial<T> | undefined,
|
||||
map: Record<keyof T, string>
|
||||
): string[] {
|
||||
const args = ["pm", "install"];
|
||||
const args = ["pm", ...commands];
|
||||
if (options) {
|
||||
for (const [key, value] of Object.entries(options)) {
|
||||
if (value) {
|
||||
const option =
|
||||
PACKAGE_MANAGER_INSTALL_OPTIONS_MAP[
|
||||
key as keyof PackageManagerInstallOptions
|
||||
];
|
||||
const option = map[key as keyof T];
|
||||
if (option) {
|
||||
args.push(option);
|
||||
switch (typeof value) {
|
||||
|
@ -213,11 +253,21 @@ export class PackageManager extends AdbCommandBase {
|
|||
return args;
|
||||
}
|
||||
|
||||
private buildInstallArguments(
|
||||
options: Partial<PackageManagerInstallOptions> | undefined
|
||||
): string[] {
|
||||
return this.buildArguments(
|
||||
["install"],
|
||||
options,
|
||||
PACKAGE_MANAGER_INSTALL_OPTIONS_MAP
|
||||
);
|
||||
}
|
||||
|
||||
public async install(
|
||||
apks: string[],
|
||||
options?: Partial<PackageManagerInstallOptions>
|
||||
): Promise<string> {
|
||||
const args = this.buildInstallArgs(options);
|
||||
const args = this.buildInstallArguments(options);
|
||||
// WIP: old version of pm doesn't support multiple apks
|
||||
args.push(...apks);
|
||||
return await this.adb.subprocess.spawnAndWaitLegacy(args);
|
||||
|
@ -241,7 +291,11 @@ export class PackageManager extends AdbCommandBase {
|
|||
await sync.dispose();
|
||||
}
|
||||
|
||||
const args = this.buildInstallArgs(options);
|
||||
// Starting from Android 7, `pm` is a only wrapper for `cmd package`,
|
||||
// and `cmd package` launches faster than `pm`.
|
||||
// But `cmd package` can't read `/data/local/tmp` folder due to SELinux policy,
|
||||
// so installing a file must use `pm`.
|
||||
const args = this.buildInstallArguments(options);
|
||||
args.push(filePath);
|
||||
const process = await AdbSubprocessNoneProtocol.raw(
|
||||
this.adb,
|
||||
|
@ -260,16 +314,15 @@ export class PackageManager extends AdbCommandBase {
|
|||
stream: ReadableStream<Consumable<Uint8Array>>,
|
||||
options?: Partial<PackageManagerInstallOptions>
|
||||
): Promise<ReadableStream<string>> {
|
||||
// Android 7 added both `cmd` command and streaming install support,
|
||||
// we can't detect whether `pm` supports streaming install,
|
||||
// so we detect `cmd` command support instead.
|
||||
if (!this._cmd.supportsCmd) {
|
||||
return this.pushAndInstallStream(stream, options);
|
||||
}
|
||||
|
||||
// Android 7 added `cmd package` and piping apk to stdin,
|
||||
// the source code suggests using `cmd package` over `pm`, but didn't say why.
|
||||
// However, `cmd package install` can't read `/data/local/tmp` folder due to SELinux policy,
|
||||
// so even ADB today is still using `pm install` for non-streaming installs.
|
||||
const args = this.buildInstallArgs(options);
|
||||
// Remove `pm` from args, final command will be `cmd package install`
|
||||
const args = this.buildInstallArguments(options);
|
||||
// Remove `pm` from args, final command will starts with `cmd package install`
|
||||
args.shift();
|
||||
args.push("-S", size.toString());
|
||||
const process = await this._cmd.spawn(false, "package", ...args);
|
||||
|
@ -277,5 +330,74 @@ export class PackageManager extends AdbCommandBase {
|
|||
return process.stdout.pipeThrough(new DecodeUtf8Stream());
|
||||
}
|
||||
|
||||
public static parsePackageListItem(
|
||||
line: string
|
||||
): PackageManagerListPackagesResult {
|
||||
line = line.substring("package:".length);
|
||||
|
||||
let packageName: string;
|
||||
let sourceDir: string | undefined;
|
||||
let versionCode: number | undefined;
|
||||
let installer: string | undefined;
|
||||
let uid: number | undefined;
|
||||
|
||||
// Parse backwards
|
||||
let index = line.indexOf(" uid:");
|
||||
if (index !== -1) {
|
||||
uid = Number.parseInt(line.substring(index + " uid:".length), 10);
|
||||
line = line.substring(0, index);
|
||||
}
|
||||
|
||||
index = line.indexOf(" installer=");
|
||||
if (index !== -1) {
|
||||
installer = line.substring(index + " installer=".length);
|
||||
line = line.substring(0, index);
|
||||
}
|
||||
|
||||
index = line.indexOf(" versionCode:");
|
||||
if (index !== -1) {
|
||||
versionCode = Number.parseInt(
|
||||
line.substring(index + " versionCode:".length),
|
||||
10
|
||||
);
|
||||
line = line.substring(0, index);
|
||||
}
|
||||
|
||||
// `sourceDir` may contain `=` so use `lastIndexOf`
|
||||
index = line.lastIndexOf("=");
|
||||
if (index !== -1) {
|
||||
sourceDir = line.substring(0, index);
|
||||
packageName = line.substring(index + "=".length);
|
||||
} else {
|
||||
packageName = line;
|
||||
}
|
||||
|
||||
return {
|
||||
packageName,
|
||||
sourceDir,
|
||||
versionCode,
|
||||
installer,
|
||||
uid,
|
||||
};
|
||||
}
|
||||
|
||||
public async listPackages(
|
||||
options?: Partial<PackageManagerListPackagesOptions>
|
||||
): Promise<PackageManagerListPackagesResult[]> {
|
||||
const args = this.buildArguments(
|
||||
["list", "packages"],
|
||||
options,
|
||||
PACKAGE_MANAGER_LIST_PACKAGES_OPTIONS_MAP
|
||||
);
|
||||
if (options?.filter) {
|
||||
args.push(options.filter);
|
||||
}
|
||||
const output = await this.adb.subprocess.spawnAndWaitLegacy(args);
|
||||
return output
|
||||
.split("\n")
|
||||
.filter((line) => !!line)
|
||||
.map(PackageManager.parsePackageListItem);
|
||||
}
|
||||
|
||||
// TODO: install: support split apk formats (`adb install-multiple`)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue