feat(bin): add wrapper for am start

This commit is contained in:
Simon Chan 2023-09-21 17:01:46 +08:00
parent 9c2a069658
commit e7827b011a
No known key found for this signature in database
GPG key ID: A8B69F750B9BCEDD
6 changed files with 214 additions and 39 deletions

View file

@ -0,0 +1,69 @@
import type { Adb } from "@yume-chan/adb";
import { AdbCommandBase } from "@yume-chan/adb";
import { ConcatStringStream, DecodeUtf8Stream } from "@yume-chan/stream-extra";
import { Cmd } from "./cmd.js";
import type { IntentBuilder } from "./intent.js";
import type { SingleUser } from "./utils.js";
import { buildArguments } from "./utils.js";
export interface ActivityManagerStartActivityOptions {
displayId?: number;
windowingMode?: number;
forceStop?: boolean;
user?: SingleUser;
intent: IntentBuilder;
}
const START_ACTIVITY_OPTIONS_MAP: Partial<
Record<keyof ActivityManagerStartActivityOptions, string>
> = {
displayId: "--display",
windowingMode: "--windowingMode",
forceStop: "-S",
user: "--user",
};
export class ActivityManager extends AdbCommandBase {
#cmd: Cmd;
constructor(adb: Adb) {
super(adb);
this.#cmd = new Cmd(adb);
}
async #cmdOrSubprocess(args: string[]) {
if (this.#cmd.supportsCmd) {
args.shift();
return await this.#cmd.spawn(false, "activity", ...args);
}
return this.adb.subprocess.spawn(args);
}
async startActivity(options: ActivityManagerStartActivityOptions) {
let args = buildArguments(
["am", "start-activity", "-W"],
options,
START_ACTIVITY_OPTIONS_MAP,
);
args = args.concat(options.intent.build());
const process = await this.#cmdOrSubprocess(args);
const output = await process.stdout
.pipeThrough(new DecodeUtf8Stream())
.pipeThrough(new ConcatStringStream())
.then((output) => output.trim());
for (const line of output) {
if (line.startsWith("Error:")) {
throw new Error(line.substring("Error:".length).trim());
}
if (line === "Complete") {
return;
}
}
}
}

View file

@ -1,10 +1,12 @@
// cspell: ignore logcat // cspell: ignore logcat
export * from "./am.js";
export * from "./bu.js"; export * from "./bu.js";
export * from "./bug-report.js"; export * from "./bug-report.js";
export * from "./cmd.js"; export * from "./cmd.js";
export * from "./demo-mode.js"; export * from "./demo-mode.js";
export * from "./dumpsys.js"; export * from "./dumpsys.js";
export * from "./intent.js";
export * from "./logcat.js"; export * from "./logcat.js";
export * from "./overlay-display.js"; export * from "./overlay-display.js";
export * from "./pm.js"; export * from "./pm.js";

View file

@ -0,0 +1,63 @@
export class IntentBuilder {
#action: string | undefined;
#categories: string[] = [];
#packageName: string | undefined;
#component: string | undefined;
#data: string | undefined;
#type: string | undefined;
setAction(action: string): this {
this.#action = action;
return this;
}
addCategory(category: string): this {
this.#categories.push(category);
return this;
}
setPackage(packageName: string): this {
this.#packageName = packageName;
return this;
}
setComponent(component: string): this {
this.#component = component;
return this;
}
setData(data: string): this {
this.#data = data;
return this;
}
build(): string[] {
const result: string[] = [];
if (this.#action) {
result.push("-a", this.#action);
}
for (const category of this.#categories) {
result.push("-c", category);
}
if (this.#packageName) {
result.push("-p", this.#packageName);
}
if (this.#component) {
result.push("-n", this.#component);
}
if (this.#data) {
result.push("-d", this.#data);
}
if (this.#type) {
result.push("-t", this.#type);
}
return result;
}
}

View file

@ -17,6 +17,9 @@ import {
} from "@yume-chan/stream-extra"; } from "@yume-chan/stream-extra";
import { Cmd } from "./cmd.js"; import { Cmd } from "./cmd.js";
import type { IntentBuilder } from "./intent.js";
import type { SingleUserOrAll } from "./utils.js";
import { buildArguments } from "./utils.js";
export enum PackageManagerInstallLocation { export enum PackageManagerInstallLocation {
Auto, Auto,
@ -101,7 +104,7 @@ export interface PackageManagerInstallOptions {
/** /**
* `--user` * `--user`
*/ */
userId: number; user: SingleUserOrAll;
/** /**
* `--install-location` * `--install-location`
*/ */
@ -168,7 +171,7 @@ export const PACKAGE_MANAGER_INSTALL_OPTIONS_MAP: Record<
instantApp: "--instant", instantApp: "--instant",
full: "--full", full: "--full",
preload: "--preload", preload: "--preload",
userId: "--user", user: "--user",
installLocation: "--install-location", installLocation: "--install-location",
installReason: "--install-reason", installReason: "--install-reason",
forceUuid: "--force-uuid", forceUuid: "--force-uuid",
@ -192,7 +195,7 @@ export interface PackageManagerListPackagesOptions {
listThirdParty: boolean; listThirdParty: boolean;
showVersionCode: boolean; showVersionCode: boolean;
listApexOnly: boolean; listApexOnly: boolean;
user: "all" | "current" | number; user: SingleUserOrAll;
uid: number; uid: number;
filter: string; filter: string;
} }
@ -225,7 +228,7 @@ export interface PackageManagerListPackagesResult {
export interface PackageManagerUninstallOptions { export interface PackageManagerUninstallOptions {
keepData: boolean; keepData: boolean;
user: "all" | "current" | number; user: SingleUserOrAll;
versionCode: number; versionCode: number;
splitNames: string[]; splitNames: string[];
} }
@ -240,6 +243,17 @@ const PACKAGE_MANAGER_UNINSTALL_OPTIONS_MAP: Record<
splitNames: "", splitNames: "",
}; };
export interface PackageManagerResolveActivityOptions {
user?: SingleUserOrAll;
intent: IntentBuilder;
}
const PACKAGE_MANAGER_RESOLVE_ACTIVITY_OPTIONS_MAP: Partial<
Record<keyof PackageManagerResolveActivityOptions, string>
> = {
user: "--user",
};
export class PackageManager extends AdbCommandBase { export class PackageManager extends AdbCommandBase {
#cmd: Cmd; #cmd: Cmd;
@ -248,38 +262,11 @@ export class PackageManager extends AdbCommandBase {
this.#cmd = new Cmd(adb); this.#cmd = new Cmd(adb);
} }
#buildArguments<T>(
commands: string[],
options: Partial<T> | undefined,
map: Record<keyof T, string>,
): string[] {
const args = ["pm", ...commands];
if (options) {
for (const [key, value] of Object.entries(options)) {
if (value) {
const option = map[key as keyof T];
if (option) {
args.push(option);
switch (typeof value) {
case "number":
args.push(value.toString());
break;
case "string":
args.push(value);
break;
}
}
}
}
}
return args;
}
#buildInstallArguments( #buildInstallArguments(
options: Partial<PackageManagerInstallOptions> | undefined, options: Partial<PackageManagerInstallOptions> | undefined,
): string[] { ): string[] {
return this.#buildArguments( return buildArguments(
["install"], ["pm", "install"],
options, options,
PACKAGE_MANAGER_INSTALL_OPTIONS_MAP, PACKAGE_MANAGER_INSTALL_OPTIONS_MAP,
); );
@ -313,7 +300,7 @@ export class PackageManager extends AdbCommandBase {
await sync.dispose(); await sync.dispose();
} }
// Starting from Android 7, `pm` is a only wrapper for `cmd package`, // Starting from Android 7, `pm` is only a wrapper for `cmd package`,
// and `cmd package` launches faster than `pm`. // and `cmd package` launches faster than `pm`.
// But `cmd package` can't read `/data/local/tmp` folder due to SELinux policy, // But `cmd package` can't read `/data/local/tmp` folder due to SELinux policy,
// so installing a file must use `pm`. // so installing a file must use `pm`.
@ -434,8 +421,8 @@ export class PackageManager extends AdbCommandBase {
async *listPackages( async *listPackages(
options?: Partial<PackageManagerListPackagesOptions>, options?: Partial<PackageManagerListPackagesOptions>,
): AsyncGenerator<PackageManagerListPackagesResult, void, void> { ): AsyncGenerator<PackageManagerListPackagesResult, void, void> {
const args = this.#buildArguments( const args = buildArguments(
["list", "packages"], ["pm", "list", "packages"],
options, options,
PACKAGE_MANAGER_LIST_PACKAGES_OPTIONS_MAP, PACKAGE_MANAGER_LIST_PACKAGES_OPTIONS_MAP,
); );
@ -461,8 +448,8 @@ export class PackageManager extends AdbCommandBase {
packageName: string, packageName: string,
options?: Partial<PackageManagerUninstallOptions>, options?: Partial<PackageManagerUninstallOptions>,
): Promise<void> { ): Promise<void> {
const args = this.#buildArguments( const args = buildArguments(
["uninstall"], ["pm", "uninstall"],
options, options,
PACKAGE_MANAGER_UNINSTALL_OPTIONS_MAP, PACKAGE_MANAGER_UNINSTALL_OPTIONS_MAP,
); );
@ -480,4 +467,28 @@ export class PackageManager extends AdbCommandBase {
throw new Error(output); throw new Error(output);
} }
} }
async resolveActivity(
options: PackageManagerResolveActivityOptions,
): Promise<string | undefined> {
let args = buildArguments(
["pm", "resolve-activity", "--components"],
options,
PACKAGE_MANAGER_RESOLVE_ACTIVITY_OPTIONS_MAP,
);
args = args.concat(options.intent.build());
const process = await this.#cmdOrSubprocess(args);
const output = await process.stdout
.pipeThrough(new DecodeUtf8Stream())
.pipeThrough(new ConcatStringStream())
.then((output) => output.trim());
if (output === "No activity found") {
return undefined;
}
return output;
}
} }

View file

@ -2,6 +2,7 @@ import type { Adb, AdbSubprocessWaitResult } from "@yume-chan/adb";
import { AdbCommandBase } from "@yume-chan/adb"; import { AdbCommandBase } from "@yume-chan/adb";
import { Cmd } from "./cmd.js"; import { Cmd } from "./cmd.js";
import type { SingleUser } from "./utils.js";
export type SettingsNamespace = "system" | "secure" | "global"; export type SettingsNamespace = "system" | "secure" | "global";
@ -12,7 +13,7 @@ export enum SettingsResetMode {
} }
export interface SettingsOptions { export interface SettingsOptions {
user?: number | "current"; user?: SingleUser;
} }
export interface SettingsPutOptions extends SettingsOptions { export interface SettingsPutOptions extends SettingsOptions {

View file

@ -0,0 +1,29 @@
export function buildArguments<T>(
commands: string[],
options: Partial<T> | undefined,
map: Partial<Record<keyof T, string>>,
): string[] {
const args = commands;
if (options) {
for (const [key, value] of Object.entries(options)) {
if (value) {
const option = map[key as keyof T];
if (option) {
args.push(option);
switch (typeof value) {
case "number":
args.push(value.toString());
break;
case "string":
args.push(value);
break;
}
}
}
}
}
return args;
}
export type SingleUser = number | "current";
export type SingleUserOrAll = SingleUser | "all";