Skip to content

Commit e63cf2a

Browse files
authored
feat: android open --activity flag (#12)
1 parent e84612a commit e63cf2a

7 files changed

Lines changed: 52 additions & 8 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ Flags:
9898
- `--device <name>`
9999
- `--udid <udid>` (iOS)
100100
- `--serial <serial>` (Android)
101+
- `--activity <component>` (Android; package/Activity or package/.Activity)
101102
- `--out <path>` (screenshot)
102103
- `--session <name>`
103104
- `--verbose` for daemon and runner logs

skills/agent-device/SKILL.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ npx -y agent-device
3737

3838
```bash
3939
agent-device open [app] # Boot device/simulator; optionally launch app
40+
agent-device open [app] --activity com.example/.MainActivity # Android: open specific activity
4041
agent-device close [app] # Close app or just end session
4142
agent-device session list # List active sessions
4243
```
@@ -145,6 +146,7 @@ agent-device apps --platform android --user-installed
145146
- `open <app>` can be used within an existing session to switch apps and update the session bundle id.
146147
- If AX returns the Simulator window or empty tree, restart Simulator or use `--backend xctest`.
147148
- Use `--session <name>` for parallel sessions; avoid device contention.
149+
- Use `--activity <component>` on Android to launch a specific activity (e.g. TV apps with LEANBACK).
148150

149151
## References
150152

src/core/dispatch.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export type CommandFlags = {
2222
udid?: string;
2323
serial?: string;
2424
out?: string;
25+
activity?: string;
2526
verbose?: boolean;
2627
snapshotInteractiveOnly?: boolean;
2728
snapshotCompact?: boolean;
@@ -94,7 +95,7 @@ export async function dispatchCommand(
9495
await interactor.openDevice();
9596
return { app: null };
9697
}
97-
await interactor.open(app);
98+
await interactor.open(app, { activity: context?.activity });
9899
return { app };
99100
}
100101
case 'close': {

src/daemon.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ function contextFromFlags(
8484
traceLogPath?: string,
8585
): {
8686
appBundleId?: string;
87+
activity?: string;
8788
verbose?: boolean;
8889
logPath?: string;
8990
traceLogPath?: string;
@@ -96,6 +97,7 @@ function contextFromFlags(
9697
} {
9798
return {
9899
appBundleId,
100+
activity: flags?.activity,
99101
verbose: flags?.verbose,
100102
logPath,
101103
traceLogPath,

src/platforms/android/index.ts

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -148,25 +148,58 @@ function parseAndroidFocus(text: string): { package?: string; activity?: string
148148
return null;
149149
}
150150

151-
export async function openAndroidApp(device: DeviceInfo, app: string): Promise<void> {
151+
export async function openAndroidApp(
152+
device: DeviceInfo,
153+
app: string,
154+
activity?: string,
155+
): Promise<void> {
152156
if (!device.booted) {
153157
await waitForAndroidBoot(device.id);
154158
}
155159
const resolved = await resolveAndroidApp(device, app);
156160
if (resolved.type === 'intent') {
161+
if (activity) {
162+
throw new AppError('INVALID_ARGS', 'Activity override requires a package name, not an intent');
163+
}
157164
await runCmd('adb', adbArgs(device, ['shell', 'am', 'start', '-a', resolved.value]));
158165
return;
159166
}
167+
if (activity) {
168+
const component = activity.includes('/')
169+
? activity
170+
: `${resolved.value}/${activity.startsWith('.') ? activity : `.${activity}`}`;
171+
await runCmd(
172+
'adb',
173+
adbArgs(device, [
174+
'shell',
175+
'am',
176+
'start',
177+
'-a',
178+
'android.intent.action.MAIN',
179+
'-c',
180+
'android.intent.category.DEFAULT',
181+
'-c',
182+
'android.intent.category.LAUNCHER',
183+
'-n',
184+
component,
185+
]),
186+
);
187+
return;
188+
}
160189
await runCmd(
161190
'adb',
162191
adbArgs(device, [
163192
'shell',
164-
'monkey',
165-
'-p',
166-
resolved.value,
193+
'am',
194+
'start',
195+
'-a',
196+
'android.intent.action.MAIN',
197+
'-c',
198+
'android.intent.category.DEFAULT',
167199
'-c',
168200
'android.intent.category.LAUNCHER',
169-
'1',
201+
'-p',
202+
resolved.value,
170203
]),
171204
);
172205
}

src/utils/args.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export type ParsedArgs = {
2020
snapshotBackend?: 'ax' | 'xctest';
2121
appsFilter?: 'launchable' | 'user-installed' | 'all';
2222
appsMetadata?: boolean;
23+
activity?: string;
2324
noRecord?: boolean;
2425
recordJson?: boolean;
2526
help: boolean;
@@ -125,6 +126,9 @@ export function parseArgs(argv: string[]): ParsedArgs {
125126
case '--session':
126127
flags.session = value;
127128
break;
129+
case '--activity':
130+
flags.activity = value;
131+
break;
128132
default:
129133
throw new AppError('INVALID_ARGS', `Unknown flag: ${key}`);
130134
}
@@ -208,6 +212,7 @@ Flags:
208212
--device <name> Device name to target
209213
--udid <udid> iOS device UDID
210214
--serial <serial> Android device serial
215+
--activity <component> Android activity to launch (package/Activity)
211216
--out <path> Output path for screenshots
212217
--session <name> Named session
213218
--verbose Stream daemon/runner logs

src/utils/interactors.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import {
2828
} from '../platforms/ios/index.ts';
2929

3030
export type Interactor = {
31-
open(app: string): Promise<void>;
31+
open(app: string, options?: { activity?: string }): Promise<void>;
3232
openDevice(): Promise<void>;
3333
close(app: string): Promise<void>;
3434
tap(x: number, y: number): Promise<void>;
@@ -45,7 +45,7 @@ export function getInteractor(device: DeviceInfo): Interactor {
4545
switch (device.platform) {
4646
case 'android':
4747
return {
48-
open: (app) => openAndroidApp(device, app),
48+
open: (app, options) => openAndroidApp(device, app, options?.activity),
4949
openDevice: () => openAndroidDevice(device),
5050
close: (app) => closeAndroidApp(device, app),
5151
tap: (x, y) => pressAndroid(device, x, y),

0 commit comments

Comments
 (0)