Loading libraries/adb/src/daemon/transport.ts +1 −1 Original line number Diff line number Diff line Loading @@ -147,7 +147,7 @@ export class AdbDaemonTransport implements AdbTransport { AdbFeature.AbbExec, "remount_shell", "track_app", "sendrecv_v2", AdbFeature.SendReceiveV2, "sendrecv_v2_brotli", "sendrecv_v2_lz4", "sendrecv_v2_zstd", Loading libraries/android-bin/src/dumpsys.ts 0 → 100644 +3 −0 Original line number Diff line number Diff line import { AdbCommandBase } from "@yume-chan/adb"; export class DumpSys extends AdbCommandBase {} libraries/android-bin/src/pm.ts +137 −15 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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) { Loading @@ -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); Loading @@ -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, Loading @@ -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); Loading @@ -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
libraries/adb/src/daemon/transport.ts +1 −1 Original line number Diff line number Diff line Loading @@ -147,7 +147,7 @@ export class AdbDaemonTransport implements AdbTransport { AdbFeature.AbbExec, "remount_shell", "track_app", "sendrecv_v2", AdbFeature.SendReceiveV2, "sendrecv_v2_brotli", "sendrecv_v2_lz4", "sendrecv_v2_zstd", Loading
libraries/android-bin/src/dumpsys.ts 0 → 100644 +3 −0 Original line number Diff line number Diff line import { AdbCommandBase } from "@yume-chan/adb"; export class DumpSys extends AdbCommandBase {}
libraries/android-bin/src/pm.ts +137 −15 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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) { Loading @@ -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); Loading @@ -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, Loading @@ -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); Loading @@ -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`) }