mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-04 02:09:18 +02:00
feat(bin): add cmd wrapper
This commit is contained in:
parent
9c5d2d8a5c
commit
225e369f53
19 changed files with 511 additions and 332 deletions
|
@ -2,8 +2,30 @@
|
|||
// cspell:ignore instantapp
|
||||
// cspell:ignore apks
|
||||
|
||||
import { AdbCommandBase } from "@yume-chan/adb";
|
||||
import type { WritableStream } from "@yume-chan/stream-extra";
|
||||
import type { Adb } from "@yume-chan/adb";
|
||||
import {
|
||||
AdbCommandBase,
|
||||
AdbSubprocessNoneProtocol,
|
||||
escapeArg,
|
||||
} from "@yume-chan/adb";
|
||||
import type { ReadableStream } from "@yume-chan/stream-extra";
|
||||
import { DecodeUtf8Stream, WrapReadableStream } from "@yume-chan/stream-extra";
|
||||
|
||||
import { Cmd } from "./cmd.js";
|
||||
|
||||
export enum PackageManagerInstallLocation {
|
||||
Auto,
|
||||
InternalOnly,
|
||||
PreferExternal,
|
||||
}
|
||||
|
||||
export enum PackageManagerInstallReason {
|
||||
Unknown,
|
||||
AdminPolicy,
|
||||
DeviceRestore,
|
||||
DeviceSetup,
|
||||
UserRequest,
|
||||
}
|
||||
|
||||
// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java;l=3046;drc=6d14d35d0241f6fee145f8e54ffd77252e8d29fd
|
||||
export interface PackageManagerInstallOptions {
|
||||
|
@ -22,7 +44,7 @@ export interface PackageManagerInstallOptions {
|
|||
/**
|
||||
* `-f`
|
||||
*/
|
||||
internal: boolean;
|
||||
internalStorage: boolean;
|
||||
/**
|
||||
* `-d`
|
||||
*/
|
||||
|
@ -78,11 +100,11 @@ export interface PackageManagerInstallOptions {
|
|||
/**
|
||||
* `--install-location`
|
||||
*/
|
||||
installLocation: number;
|
||||
installLocation: PackageManagerInstallLocation;
|
||||
/**
|
||||
* `--install-reason`
|
||||
*/
|
||||
installReason: number;
|
||||
installReason: PackageManagerInstallReason;
|
||||
/**
|
||||
* `--force-uuid`
|
||||
*/
|
||||
|
@ -121,116 +143,135 @@ export interface PackageManagerInstallOptions {
|
|||
bypassLowTargetSdkBlock: boolean;
|
||||
}
|
||||
|
||||
export const PACKAGE_MANAGER_INSTALL_OPTIONS_MAP: Record<
|
||||
keyof PackageManagerInstallOptions,
|
||||
string
|
||||
> = {
|
||||
skipExisting: "-R",
|
||||
installerPackageName: "-i",
|
||||
allowTest: "-t",
|
||||
internalStorage: "-f",
|
||||
requestDowngrade: "-d",
|
||||
grantRuntimePermissions: "-g",
|
||||
restrictPermissions: "--restrict-permissions",
|
||||
doNotKill: "--dont-kill",
|
||||
originatingUri: "--originating-uri",
|
||||
refererUri: "--referrer",
|
||||
inheritFrom: "-p",
|
||||
packageName: "--pkg",
|
||||
abi: "--abi",
|
||||
instantApp: "--instant",
|
||||
full: "--full",
|
||||
preload: "--preload",
|
||||
userId: "--user",
|
||||
installLocation: "--install-location",
|
||||
installReason: "--install-reason",
|
||||
forceUuid: "--force-uuid",
|
||||
apex: "--apex",
|
||||
forceNonStaged: "--force-non-staged",
|
||||
staged: "--staged",
|
||||
forceQueryable: "--force-queryable",
|
||||
enableRollback: "--enable-rollback",
|
||||
stagedReadyTimeout: "--staged-ready-timeout",
|
||||
skipVerification: "--skip-verification",
|
||||
bypassLowTargetSdkBlock: "--bypass-low-target-sdk-block",
|
||||
};
|
||||
|
||||
export class PackageManager extends AdbCommandBase {
|
||||
private _cmd: Cmd;
|
||||
|
||||
public constructor(adb: Adb) {
|
||||
super(adb);
|
||||
this._cmd = new Cmd(adb);
|
||||
}
|
||||
|
||||
private buildInstallArgs(
|
||||
options: Partial<PackageManagerInstallOptions>
|
||||
options?: Partial<PackageManagerInstallOptions>
|
||||
): string[] {
|
||||
const args = ["pm", "install"];
|
||||
if (options.skipExisting) {
|
||||
args.push("-R");
|
||||
}
|
||||
if (options.installerPackageName) {
|
||||
args.push("-i", options.installerPackageName);
|
||||
}
|
||||
if (options.allowTest) {
|
||||
args.push("-t");
|
||||
}
|
||||
if (options.internal) {
|
||||
args.push("-f");
|
||||
}
|
||||
if (options.requestDowngrade) {
|
||||
args.push("-d");
|
||||
}
|
||||
if (options.grantRuntimePermissions) {
|
||||
args.push("-g");
|
||||
}
|
||||
if (options.restrictPermissions) {
|
||||
args.push("--restrict-permissions");
|
||||
}
|
||||
if (options.doNotKill) {
|
||||
args.push("--dont-kill");
|
||||
}
|
||||
if (options.originatingUri) {
|
||||
args.push("--originating-uri", options.originatingUri);
|
||||
}
|
||||
if (options.refererUri) {
|
||||
args.push("--referrer", options.refererUri);
|
||||
}
|
||||
if (options.inheritFrom) {
|
||||
args.push("-p", options.inheritFrom);
|
||||
}
|
||||
if (options.packageName) {
|
||||
args.push("--pkg", options.packageName);
|
||||
}
|
||||
if (options.abi) {
|
||||
args.push("--abi", options.abi);
|
||||
}
|
||||
if (options.instantApp) {
|
||||
args.push("--instant");
|
||||
}
|
||||
if (options.full) {
|
||||
args.push("--full");
|
||||
}
|
||||
if (options.preload) {
|
||||
args.push("--preload");
|
||||
}
|
||||
if (options.userId) {
|
||||
args.push("--user", options.userId.toString());
|
||||
}
|
||||
if (options.installLocation) {
|
||||
args.push("--install-location", options.installLocation.toString());
|
||||
}
|
||||
if (options.installReason) {
|
||||
args.push("--install-reason", options.installReason.toString());
|
||||
}
|
||||
if (options.forceUuid) {
|
||||
args.push("--force-uuid", options.forceUuid);
|
||||
}
|
||||
if (options.apex) {
|
||||
args.push("--apex");
|
||||
}
|
||||
if (options.forceNonStaged) {
|
||||
args.push("--force-non-staged");
|
||||
}
|
||||
if (options.staged) {
|
||||
args.push("--staged");
|
||||
}
|
||||
if (options.forceQueryable) {
|
||||
args.push("--force-queryable");
|
||||
}
|
||||
if (options.enableRollback) {
|
||||
args.push("--enable-rollback");
|
||||
}
|
||||
if (options.stagedReadyTimeout) {
|
||||
args.push(
|
||||
"--staged-ready-timeout",
|
||||
options.stagedReadyTimeout.toString()
|
||||
);
|
||||
}
|
||||
if (options.skipVerification) {
|
||||
args.push("--skip-verification");
|
||||
}
|
||||
if (options.bypassLowTargetSdkBlock) {
|
||||
args.push("--bypass-low-target-sdk-block");
|
||||
if (options) {
|
||||
for (const [key, value] of Object.entries(options)) {
|
||||
if (value) {
|
||||
const option =
|
||||
PACKAGE_MANAGER_INSTALL_OPTIONS_MAP[
|
||||
key as keyof PackageManagerInstallOptions
|
||||
];
|
||||
if (option) {
|
||||
args.push(option);
|
||||
switch (typeof value) {
|
||||
case "number":
|
||||
args.push(value.toString());
|
||||
break;
|
||||
case "string":
|
||||
args.push(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
public async install(
|
||||
options: Partial<PackageManagerInstallOptions>,
|
||||
...apks: string[]
|
||||
): Promise<void> {
|
||||
apks: string[],
|
||||
options?: Partial<PackageManagerInstallOptions>
|
||||
): Promise<string> {
|
||||
const args = this.buildInstallArgs(options);
|
||||
// WIP: old version of pm doesn't support multiple apks
|
||||
args.push(...apks);
|
||||
await this.adb.subprocess.spawnAndWaitLegacy(args);
|
||||
return await this.adb.subprocess.spawnAndWaitLegacy(args);
|
||||
}
|
||||
|
||||
public async pushAndInstallStream(
|
||||
stream: ReadableStream<Uint8Array>,
|
||||
options?: Partial<PackageManagerInstallOptions>
|
||||
): Promise<ReadableStream<string>> {
|
||||
const sync = await this.adb.sync();
|
||||
|
||||
const fileName = Math.random().toString().substring(2);
|
||||
const filePath = `/data/local/tmp/${fileName}.apk`;
|
||||
|
||||
try {
|
||||
await sync.write(filePath, stream);
|
||||
} finally {
|
||||
await sync.dispose();
|
||||
}
|
||||
|
||||
const args = this.buildInstallArgs(options);
|
||||
const process = await AdbSubprocessNoneProtocol.raw(
|
||||
this.adb,
|
||||
args.map(escapeArg).join(" ")
|
||||
);
|
||||
return new WrapReadableStream({
|
||||
start: () => process.stdout.pipeThrough(new DecodeUtf8Stream()),
|
||||
close: async () => {
|
||||
await this.adb.rm(filePath);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public async installStream(
|
||||
options: Partial<PackageManagerInstallOptions>,
|
||||
size: number
|
||||
): Promise<WritableStream<Uint8Array>> {
|
||||
size: number,
|
||||
stream: ReadableStream<Uint8Array>,
|
||||
options?: Partial<PackageManagerInstallOptions>
|
||||
): Promise<ReadableStream<string>> {
|
||||
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`
|
||||
args.shift();
|
||||
args.push("-S", size.toString());
|
||||
return (await this.adb.subprocess.spawn(args)).stdin;
|
||||
const process = await this._cmd.spawn(false, "package", ...args);
|
||||
await stream.pipeTo(process.stdin);
|
||||
return process.stdout.pipeThrough(new DecodeUtf8Stream());
|
||||
}
|
||||
|
||||
// TODO: install: support split apk formats (`adb install-multiple`)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue