mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-03 01:39:21 +02:00
feat(bin): handle pm.install options based on API level
This commit is contained in:
parent
9fdab3d677
commit
0185333bab
1 changed files with 143 additions and 158 deletions
|
@ -2,6 +2,7 @@
|
||||||
// cspell:ignore instantapp
|
// cspell:ignore instantapp
|
||||||
// cspell:ignore apks
|
// cspell:ignore apks
|
||||||
// cspell:ignore versioncode
|
// cspell:ignore versioncode
|
||||||
|
// cspell:ignore dexopt
|
||||||
|
|
||||||
import type { Adb, AdbNoneProtocolProcess } from "@yume-chan/adb";
|
import type { Adb, AdbNoneProtocolProcess } from "@yume-chan/adb";
|
||||||
import { AdbServiceBase, escapeArg } from "@yume-chan/adb";
|
import { AdbServiceBase, escapeArg } from "@yume-chan/adb";
|
||||||
|
@ -31,154 +32,83 @@ export enum PackageManagerInstallReason {
|
||||||
UserRequest,
|
UserRequest,
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java;l=3046;drc=6d14d35d0241f6fee145f8e54ffd77252e8d29fd
|
interface OptionDefinition<T> {
|
||||||
export interface PackageManagerInstallOptions {
|
type: T;
|
||||||
/**
|
name: string;
|
||||||
* `-R`
|
minApiLevel?: number;
|
||||||
*/
|
maxApiLevel?: number;
|
||||||
skipExisting: boolean;
|
|
||||||
/**
|
|
||||||
* `-i`
|
|
||||||
*/
|
|
||||||
installerPackageName: string;
|
|
||||||
/**
|
|
||||||
* `-t`
|
|
||||||
*/
|
|
||||||
allowTest: boolean;
|
|
||||||
/**
|
|
||||||
* `-f`
|
|
||||||
*/
|
|
||||||
internalStorage: boolean;
|
|
||||||
/**
|
|
||||||
* `-d`
|
|
||||||
*/
|
|
||||||
requestDowngrade: boolean;
|
|
||||||
/**
|
|
||||||
* `-g`
|
|
||||||
*/
|
|
||||||
grantRuntimePermissions: boolean;
|
|
||||||
/**
|
|
||||||
* `--restrict-permissions`
|
|
||||||
*/
|
|
||||||
restrictPermissions: boolean;
|
|
||||||
/**
|
|
||||||
* `--dont-kill`
|
|
||||||
*/
|
|
||||||
doNotKill: boolean;
|
|
||||||
/**
|
|
||||||
* `--originating-uri`
|
|
||||||
*/
|
|
||||||
originatingUri: string;
|
|
||||||
/**
|
|
||||||
* `--referrer`
|
|
||||||
*/
|
|
||||||
refererUri: string;
|
|
||||||
/**
|
|
||||||
* `-p`
|
|
||||||
*/
|
|
||||||
inheritFrom: string;
|
|
||||||
/**
|
|
||||||
* `--pkg`
|
|
||||||
*/
|
|
||||||
packageName: string;
|
|
||||||
/**
|
|
||||||
* `--abi`
|
|
||||||
*/
|
|
||||||
abi: string;
|
|
||||||
/**
|
|
||||||
* `--ephemeral`/`--instant`/`--instantapp`
|
|
||||||
*/
|
|
||||||
instantApp: boolean;
|
|
||||||
/**
|
|
||||||
* `--full`
|
|
||||||
*/
|
|
||||||
full: boolean;
|
|
||||||
/**
|
|
||||||
* `--preload`
|
|
||||||
*/
|
|
||||||
preload: boolean;
|
|
||||||
/**
|
|
||||||
* `--user`
|
|
||||||
*/
|
|
||||||
user: SingleUserOrAll;
|
|
||||||
/**
|
|
||||||
* `--install-location`
|
|
||||||
*/
|
|
||||||
installLocation: PackageManagerInstallLocation;
|
|
||||||
/**
|
|
||||||
* `--install-reason`
|
|
||||||
*/
|
|
||||||
installReason: PackageManagerInstallReason;
|
|
||||||
/**
|
|
||||||
* `--force-uuid`
|
|
||||||
*/
|
|
||||||
forceUuid: string;
|
|
||||||
/**
|
|
||||||
* `--apex`
|
|
||||||
*/
|
|
||||||
apex: boolean;
|
|
||||||
/**
|
|
||||||
* `--force-non-staged`
|
|
||||||
*/
|
|
||||||
forceNonStaged: boolean;
|
|
||||||
/**
|
|
||||||
* `--staged`
|
|
||||||
*/
|
|
||||||
staged: boolean;
|
|
||||||
/**
|
|
||||||
* `--force-queryable`
|
|
||||||
*/
|
|
||||||
forceQueryable: boolean;
|
|
||||||
/**
|
|
||||||
* `--enable-rollback`
|
|
||||||
*/
|
|
||||||
enableRollback: boolean;
|
|
||||||
/**
|
|
||||||
* `--staged-ready-timeout`
|
|
||||||
*/
|
|
||||||
stagedReadyTimeout: number;
|
|
||||||
/**
|
|
||||||
* `--skip-verification`
|
|
||||||
*/
|
|
||||||
skipVerification: boolean;
|
|
||||||
/**
|
|
||||||
* `--bypass-low-target-sdk-block`
|
|
||||||
*/
|
|
||||||
bypassLowTargetSdkBlock: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PACKAGE_MANAGER_INSTALL_OPTIONS_MAP: Record<
|
function option<T>(
|
||||||
keyof PackageManagerInstallOptions,
|
name: string,
|
||||||
string
|
minApiLevel?: number,
|
||||||
> = {
|
maxApiLevel?: number,
|
||||||
skipExisting: "-R",
|
): OptionDefinition<T> {
|
||||||
installerPackageName: "-i",
|
return {
|
||||||
allowTest: "-t",
|
name,
|
||||||
internalStorage: "-f",
|
minApiLevel,
|
||||||
requestDowngrade: "-d",
|
maxApiLevel,
|
||||||
grantRuntimePermissions: "-g",
|
} as OptionDefinition<T>;
|
||||||
restrictPermissions: "--restrict-permissions",
|
}
|
||||||
doNotKill: "--dont-kill",
|
|
||||||
originatingUri: "--originating-uri",
|
// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java;l=3046;drc=6d14d35d0241f6fee145f8e54ffd77252e8d29fd
|
||||||
refererUri: "--referrer",
|
export const PackageManagerInstallOptions = {
|
||||||
inheritFrom: "-p",
|
forwardLock: option<boolean>("-l", undefined, 28),
|
||||||
packageName: "--pkg",
|
replaceExisting: option<boolean>("-r", undefined, 27),
|
||||||
abi: "--abi",
|
skipExisting: option<boolean>("-R", 28),
|
||||||
instantApp: "--instant",
|
installerPackageName: option<string>("-i"),
|
||||||
full: "--full",
|
allowTest: option<boolean>("-t"),
|
||||||
preload: "--preload",
|
externalStorage: option<boolean>("-s", undefined, 28),
|
||||||
user: "--user",
|
internalStorage: option<boolean>("-f"),
|
||||||
installLocation: "--install-location",
|
requestDowngrade: option<boolean>("-d"),
|
||||||
installReason: "--install-reason",
|
grantRuntimePermissions: option<boolean>("-g", 23),
|
||||||
forceUuid: "--force-uuid",
|
restrictPermissions: option<boolean>("--restrict-permissions", 29),
|
||||||
apex: "--apex",
|
doNotKill: option<boolean>("--dont-kill"),
|
||||||
forceNonStaged: "--force-non-staged",
|
originatingUri: option<string>("--originating-uri"),
|
||||||
staged: "--staged",
|
refererUri: option<string>("--referrer"),
|
||||||
forceQueryable: "--force-queryable",
|
inheritFrom: option<string>("-p", 24),
|
||||||
enableRollback: "--enable-rollback",
|
packageName: option<string>("--pkg", 28),
|
||||||
stagedReadyTimeout: "--staged-ready-timeout",
|
abi: option<string>("--abi", 21),
|
||||||
skipVerification: "--skip-verification",
|
instantApp: option<boolean>("--ephemeral", 24),
|
||||||
bypassLowTargetSdkBlock: "--bypass-low-target-sdk-block",
|
full: option<boolean>("--full", 26),
|
||||||
|
preload: option<boolean>("--preload", 28),
|
||||||
|
user: option<SingleUserOrAll>("--user", 21),
|
||||||
|
installLocation: option<PackageManagerInstallLocation>(
|
||||||
|
"--install-location",
|
||||||
|
24,
|
||||||
|
),
|
||||||
|
installReason: option<PackageManagerInstallReason>("--install-reason", 29),
|
||||||
|
updateOwnership: option<boolean>("--update-ownership", 34),
|
||||||
|
forceUuid: option<string>("--force-uuid", 24),
|
||||||
|
forceSdk: option<number>("--force-sdk", 24),
|
||||||
|
apex: option<boolean>("--apex", 29),
|
||||||
|
forceNonStaged: option<boolean>("--force-non-staged", 31),
|
||||||
|
multiPackage: option<boolean>("--multi-package", 29),
|
||||||
|
staged: option<boolean>("--staged", 29),
|
||||||
|
nonStaged: option<boolean>("--non-staged", 35),
|
||||||
|
forceQueryable: option<boolean>("--force-queryable", 30),
|
||||||
|
enableRollback: option<boolean | number>("--enable-rollback", 29),
|
||||||
|
rollbackImpactLevel: option<number>("--rollback-impact-level", 35),
|
||||||
|
wait: option<boolean | number>("--wait", 30, 30),
|
||||||
|
noWait: option<boolean>("--no-wait", 30, 30),
|
||||||
|
stagedReadyTimeout: option<number>("--staged-ready-timeout", 31),
|
||||||
|
skipVerification: option<boolean>("--skip-verification", 30),
|
||||||
|
skipEnable: option<boolean>("--skip-enable", 34),
|
||||||
|
bypassLowTargetSdkBlock: option<boolean>(
|
||||||
|
"--bypass-low-target-sdk-block",
|
||||||
|
34,
|
||||||
|
),
|
||||||
|
ignoreDexoptProfile: option<boolean>("--ignore-dexopt-profile", 35),
|
||||||
|
packageSource: option<number>("--package-source", 35),
|
||||||
|
dexoptCompilerFilter: option<string>("--dexopt-compiler-filter", 35),
|
||||||
|
disableAutoInstallDependencies: option<boolean>(
|
||||||
|
"--disable-auto-install-dependencies",
|
||||||
|
36,
|
||||||
|
),
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type PackageManagerInstallOptions = {
|
||||||
|
[K in keyof typeof PackageManagerInstallOptions]: (typeof PackageManagerInstallOptions)[K]["type"];
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface PackageManagerListPackagesOptions {
|
export interface PackageManagerListPackagesOptions {
|
||||||
|
@ -258,12 +188,10 @@ const PACKAGE_MANAGER_RESOLVE_ACTIVITY_OPTIONS_MAP: Partial<
|
||||||
function buildInstallArguments(
|
function buildInstallArguments(
|
||||||
command: string,
|
command: string,
|
||||||
options: Optional<PackageManagerInstallOptions> | undefined,
|
options: Optional<PackageManagerInstallOptions> | undefined,
|
||||||
|
apiLevel: number | undefined,
|
||||||
): string[] {
|
): string[] {
|
||||||
const args = buildArguments(
|
const args = [PackageManager.ServiceName, command];
|
||||||
[PackageManager.ServiceName, command],
|
|
||||||
options,
|
|
||||||
PACKAGE_MANAGER_INSTALL_OPTIONS_MAP,
|
|
||||||
);
|
|
||||||
if (!options?.skipExisting) {
|
if (!options?.skipExisting) {
|
||||||
/*
|
/*
|
||||||
* | behavior | previous version | modern version |
|
* | behavior | previous version | modern version |
|
||||||
|
@ -278,6 +206,59 @@ function buildInstallArguments(
|
||||||
*/
|
*/
|
||||||
args.push("-r");
|
args.push("-r");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!options) {
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(options)) {
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const option =
|
||||||
|
PackageManagerInstallOptions[
|
||||||
|
key as keyof PackageManagerInstallOptions
|
||||||
|
];
|
||||||
|
|
||||||
|
if (option === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apiLevel !== undefined) {
|
||||||
|
if (
|
||||||
|
option.minApiLevel !== undefined &&
|
||||||
|
apiLevel < option.minApiLevel
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
option.maxApiLevel !== undefined &&
|
||||||
|
apiLevel > option.maxApiLevel
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (typeof value) {
|
||||||
|
case "boolean":
|
||||||
|
if (value) {
|
||||||
|
args.push(option.name);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "number":
|
||||||
|
args.push(option.name, value.toString());
|
||||||
|
break;
|
||||||
|
case "string":
|
||||||
|
args.push(option.name, escapeArg(value));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(
|
||||||
|
`Unsupported type for option ${key}: ${typeof value}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,10 +266,10 @@ export class PackageManager extends AdbServiceBase {
|
||||||
static readonly ServiceName = "package";
|
static readonly ServiceName = "package";
|
||||||
static readonly CommandName = "pm";
|
static readonly CommandName = "pm";
|
||||||
|
|
||||||
#apiLevel: number;
|
#apiLevel: number | undefined;
|
||||||
#cmd: Cmd.NoneProtocolService;
|
#cmd: Cmd.NoneProtocolService;
|
||||||
|
|
||||||
constructor(adb: Adb, apiLevel = 0) {
|
constructor(adb: Adb, apiLevel?: number) {
|
||||||
super(adb);
|
super(adb);
|
||||||
|
|
||||||
this.#apiLevel = apiLevel;
|
this.#apiLevel = apiLevel;
|
||||||
|
@ -304,7 +285,7 @@ export class PackageManager extends AdbServiceBase {
|
||||||
apks: readonly string[],
|
apks: readonly string[],
|
||||||
options?: Optional<PackageManagerInstallOptions>,
|
options?: Optional<PackageManagerInstallOptions>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const args = buildInstallArguments("install", options);
|
const args = buildInstallArguments("install", options, this.#apiLevel);
|
||||||
args[0] = PackageManager.CommandName;
|
args[0] = PackageManager.CommandName;
|
||||||
// WIP: old version of pm doesn't support multiple apks
|
// WIP: old version of pm doesn't support multiple apks
|
||||||
args.push(...apks.map(escapeArg));
|
args.push(...apks.map(escapeArg));
|
||||||
|
@ -369,7 +350,7 @@ export class PackageManager extends AdbServiceBase {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const args = buildInstallArguments("install", options);
|
const args = buildInstallArguments("install", options, this.#apiLevel);
|
||||||
args.push("-S", size.toString());
|
args.push("-S", size.toString());
|
||||||
const process = await this.#cmd.spawn(args);
|
const process = await this.#cmd.spawn(args);
|
||||||
|
|
||||||
|
@ -498,7 +479,7 @@ export class PackageManager extends AdbServiceBase {
|
||||||
|
|
||||||
// `cmd package` doesn't support `path` command on Android 7 and 8.
|
// `cmd package` doesn't support `path` command on Android 7 and 8.
|
||||||
let process: AdbNoneProtocolProcess;
|
let process: AdbNoneProtocolProcess;
|
||||||
if (this.#apiLevel <= 27) {
|
if (this.#apiLevel !== undefined && this.#apiLevel <= 27) {
|
||||||
args[0] = PackageManager.CommandName;
|
args[0] = PackageManager.CommandName;
|
||||||
process = await this.adb.subprocess.noneProtocol.spawn(args);
|
process = await this.adb.subprocess.noneProtocol.spawn(args);
|
||||||
} else {
|
} else {
|
||||||
|
@ -584,7 +565,11 @@ export class PackageManager extends AdbServiceBase {
|
||||||
async sessionCreate(
|
async sessionCreate(
|
||||||
options?: Optional<PackageManagerInstallOptions>,
|
options?: Optional<PackageManagerInstallOptions>,
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
const args = buildInstallArguments("install-create", options);
|
const args = buildInstallArguments(
|
||||||
|
"install-create",
|
||||||
|
options,
|
||||||
|
this.#apiLevel,
|
||||||
|
);
|
||||||
|
|
||||||
const output = await this.#cmd
|
const output = await this.#cmd
|
||||||
.spawn(args)
|
.spawn(args)
|
||||||
|
@ -670,7 +655,7 @@ export class PackageManager extends AdbServiceBase {
|
||||||
// but the "Success" message is not forwarded back to the client,
|
// but the "Success" message is not forwarded back to the client,
|
||||||
// causing this function to fail with an empty message.
|
// causing this function to fail with an empty message.
|
||||||
let process: AdbNoneProtocolProcess;
|
let process: AdbNoneProtocolProcess;
|
||||||
if (this.#apiLevel <= 25) {
|
if (this.#apiLevel !== undefined && this.#apiLevel <= 25) {
|
||||||
args[0] = PackageManager.CommandName;
|
args[0] = PackageManager.CommandName;
|
||||||
process = await this.adb.subprocess.noneProtocol.spawn(args);
|
process = await this.adb.subprocess.noneProtocol.spawn(args);
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue