Skip to content

Commit 0286ecb

Browse files
committed
feat: expand android adb provider boundary
1 parent af73a10 commit 0286ecb

32 files changed

Lines changed: 1097 additions & 538 deletions

.fallowrc.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
"src/metro.ts",
99
"src/remote-config.ts",
1010
"src/install-source.ts",
11-
"src/android-apps.ts",
1211
"src/android-adb.ts",
1312
"src/android-snapshot-helper.ts",
1413
"src/contracts.ts",

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ Snapshots assign refs like `@e1`, `@e2`, and `@e3` to current-screen elements. R
9494

9595
`agent-device` runs session-aware commands through platform backends: XCTest for iOS and tvOS, ADB plus the Android snapshot helper for Android, a local helper for macOS desktop automation, and AT-SPI for Linux desktop targets. See [Introduction](https://incubator.callstack.com/agent-device/docs/introduction) and [Commands](https://incubator.callstack.com/agent-device/docs/commands) for platform details.
9696

97-
Node consumers can use the typed client and public subpaths for bridge integrations. `agent-device/android-adb` exposes the Android ADB provider contract and reusable helpers for ADB-backed app listing and foreground state. `agent-device/daemon` exposes the supported daemon embedding surface for integrations that intentionally reuse the upstream request router.
97+
Node consumers can use the typed client and public subpaths for bridge integrations. `agent-device/android-adb` exposes the Android ADB provider contract, logcat/clipboard/keyboard/app helpers, and port reverse management. `agent-device/daemon` exposes the supported daemon embedding surface for integrations that intentionally reuse the upstream request router.
9898

9999
## Used By
100100

package.json

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,6 @@
4545
"import": "./dist/src/install-source.js",
4646
"types": "./dist/src/install-source.d.ts"
4747
},
48-
"./android-apps": {
49-
"import": "./dist/src/android-apps.js",
50-
"types": "./dist/src/android-apps.d.ts"
51-
},
5248
"./android-adb": {
5349
"import": "./dist/src/android-adb.js",
5450
"types": "./dist/src/android-adb.d.ts"

rslib.config.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ export default defineConfig({
2323
metro: 'src/metro.ts',
2424
'remote-config': 'src/remote-config.ts',
2525
'install-source': 'src/install-source.ts',
26-
'android-apps': 'src/android-apps.ts',
2726
'android-adb': 'src/android-adb.ts',
2827
'android-snapshot-helper': 'src/android-snapshot-helper.ts',
2928
'daemon-embedding': 'src/daemon-embedding.ts',

src/__tests__/android-apps-public.test.ts

Lines changed: 0 additions & 73 deletions
This file was deleted.

src/android-adb.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,42 @@
11
export {
2-
createDeviceAdbExecutor,
3-
resolveAndroidAdbExecutor,
4-
spawnAndroidAdbBySerial,
5-
withAndroidAdbProvider,
2+
createAndroidPortReverseManager,
3+
createLocalAndroidAdbProvider,
4+
resolveAndroidAdbProvider,
65
type AndroidAdbExecutor,
76
type AndroidAdbExecutorOptions,
7+
type AndroidAdbProcess,
88
type AndroidAdbExecutorResult,
99
type AndroidAdbProvider,
1010
type AndroidAdbSpawner,
11+
type AndroidPortReverseEndpoint,
12+
type AndroidPortReverseManager,
13+
type AndroidPortReverseMapping,
14+
type AndroidPortReverseOptions,
15+
type AndroidPortReverseProvider,
1116
} from './platforms/android/adb-executor.ts';
1217
export {
1318
getAndroidAppStateWithAdb,
1419
listAndroidAppsWithAdb,
1520
} from './platforms/android/app-helpers.ts';
21+
export {
22+
forceStopAndroidAppWithAdb,
23+
openAndroidAppWithAdb,
24+
resolveAndroidLaunchComponentWithAdb,
25+
type AndroidOpenAppWithAdbOptions,
26+
} from './platforms/android/app-control.ts';
27+
export {
28+
captureAndroidLogcatWithAdb,
29+
streamAndroidLogcatWithAdb,
30+
type AndroidLogcatCaptureOptions,
31+
type AndroidLogcatStreamOptions,
32+
} from './platforms/android/logcat.ts';
33+
export {
34+
dismissAndroidKeyboardWithAdb,
35+
getAndroidKeyboardStatusWithAdb,
36+
readAndroidClipboardWithAdb,
37+
writeAndroidClipboardWithAdb,
38+
type AndroidKeyboardState,
39+
} from './platforms/android/device-input-state.ts';
1640
export type {
1741
AndroidAppListFilter,
1842
AndroidAppListOptions,

src/android-apps.ts

Lines changed: 0 additions & 6 deletions
This file was deleted.

src/daemon-embedding.ts

Lines changed: 8 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,11 @@
11
export { createRequestHandler } from './daemon/request-router.ts';
2-
export type { AndroidAdbProviderResolver, RequestRouterDeps } from './daemon/request-router.ts';
3-
export { withDeviceInventoryProvider } from './core/dispatch-resolve.ts';
4-
export type { DeviceInventoryProvider, DeviceInventoryRequest } from './core/dispatch-resolve.ts';
5-
export { SessionStore } from './daemon/session-store.ts';
6-
export { LeaseRegistry } from './daemon/lease-registry.ts';
7-
export type {
8-
AdmissionRequest,
9-
AllocateLeaseRequest,
10-
HeartbeatLeaseRequest,
11-
LeaseRegistryOptions,
12-
ReleaseLeaseRequest,
13-
SimulatorLease,
14-
} from './daemon/lease-registry.ts';
15-
export {
16-
cleanupDownloadableArtifact,
17-
cleanupUploadedArtifact,
18-
prepareDownloadableArtifact,
19-
prepareUploadedArtifact,
20-
trackDownloadableArtifact,
21-
trackUploadedArtifact,
22-
} from './daemon/artifact-tracking.ts';
232
export type {
24-
DaemonArtifact,
25-
DaemonInstallSource,
26-
DaemonRequest,
27-
DaemonResponse,
28-
DaemonResponseData,
29-
SessionRuntimeHints,
30-
SessionState,
31-
} from './daemon/types.ts';
3+
AndroidAdbProviderRequestSession,
4+
AndroidAdbProviderResolver,
5+
RequestLeaseRegistry,
6+
RequestRouterDeps,
7+
RequestSessionStore,
8+
} from './daemon/request-router.ts';
9+
export type { DeviceInventoryProvider, DeviceInventoryRequest } from './core/dispatch-resolve.ts';
10+
export type { DaemonRequest, DaemonResponse } from './daemon/types.ts';
3211
export type { DeviceInfo, Platform, PlatformSelector } from './utils/device.ts';
33-
export type {
34-
AndroidAdbExecutor,
35-
AndroidAdbExecutorOptions,
36-
AndroidAdbExecutorResult,
37-
AndroidAdbProvider,
38-
} from './android-adb.ts';

src/daemon/__tests__/app-log-android.test.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,13 @@ import path from 'node:path';
77
import { PassThrough } from 'node:stream';
88

99
vi.mock('node:child_process', () => ({ spawn: vi.fn() }));
10-
vi.mock('../../utils/exec.ts', () => ({
11-
runCmd: vi.fn(async () => ({ stdout: '', stderr: '', exitCode: 0 })),
12-
}));
10+
vi.mock('../../utils/exec.ts', async (importOriginal) => {
11+
const actual = await importOriginal<typeof import('../../utils/exec.ts')>();
12+
return {
13+
...actual,
14+
runCmd: vi.fn(async () => ({ stdout: '', stderr: '', exitCode: 0 })),
15+
};
16+
});
1317
vi.mock('../app-log-stream.ts', async (importOriginal) => {
1418
const actual = await importOriginal<typeof import('../app-log-stream.ts')>();
1519
return { ...actual, sleep: vi.fn(async () => {}) };
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { expect, test, vi } from 'vitest';
2+
3+
vi.mock('../../utils/exec.ts', async (importOriginal) => {
4+
const actual = await importOriginal<typeof import('../../utils/exec.ts')>();
5+
return {
6+
...actual,
7+
runCmd: vi.fn(async (cmd: string) => {
8+
if (cmd === 'adb') {
9+
throw new Error('local adb must not be used');
10+
}
11+
return { stdout: '', stderr: '', exitCode: 0 };
12+
}),
13+
whichCmd: vi.fn(async (cmd: string) => cmd !== 'adb'),
14+
};
15+
});
16+
vi.mock('../device-ready.ts', () => ({
17+
DEVICE_READY_CACHE_TTL_MS: 5_000,
18+
clearDeviceReadyCacheForTests: vi.fn(),
19+
ensureDeviceReady: vi.fn(async () => {}),
20+
}));
21+
22+
import { createRequestHandler } from '../request-router.ts';
23+
import { LeaseRegistry } from '../lease-registry.ts';
24+
import { makeSessionStore } from '../../__tests__/test-utils/store-factory.ts';
25+
import type { AndroidAdbProvider } from '../../platforms/android/adb-executor.ts';
26+
27+
test('Android daemon commands route through injected provider without host adb', async () => {
28+
const sessionStore = makeSessionStore('agent-device-router-adb-provider-conformance-');
29+
sessionStore.set('default', {
30+
name: 'default',
31+
createdAt: Date.now(),
32+
device: {
33+
platform: 'android',
34+
id: 'remote-android-1',
35+
name: 'Remote Android',
36+
kind: 'device',
37+
booted: true,
38+
},
39+
appBundleId: 'com.example.app',
40+
actions: [],
41+
});
42+
const adbCalls: string[][] = [];
43+
const provider: AndroidAdbProvider = {
44+
exec: async (args) => {
45+
adbCalls.push(args);
46+
if (args.join(' ') === 'shell cmd clipboard get text') {
47+
return { stdout: 'clipboard text: hello', stderr: '', exitCode: 0 };
48+
}
49+
if (args.join(' ') === 'shell dumpsys input_method') {
50+
return { stdout: 'mInputShown=false inputType=0x1', stderr: '', exitCode: 0 };
51+
}
52+
return { stdout: 'ok', stderr: '', exitCode: 0 };
53+
},
54+
};
55+
const handler = createRequestHandler({
56+
logPath: '/tmp/daemon.log',
57+
token: 'token',
58+
sessionStore,
59+
leaseRegistry: new LeaseRegistry(),
60+
trackDownloadableArtifact: () => 'artifact-id',
61+
androidAdbProvider: () => provider,
62+
});
63+
64+
const clipboard = await handler({
65+
token: 'token',
66+
session: 'default',
67+
command: 'clipboard',
68+
positionals: ['read'],
69+
flags: {},
70+
});
71+
const keyboard = await handler({
72+
token: 'token',
73+
session: 'default',
74+
command: 'keyboard',
75+
positionals: ['status'],
76+
flags: {},
77+
});
78+
const doctor = await handler({
79+
token: 'token',
80+
session: 'default',
81+
command: 'logs',
82+
positionals: ['doctor'],
83+
flags: {},
84+
});
85+
86+
expect(clipboard.ok).toBe(true);
87+
expect(keyboard.ok).toBe(true);
88+
expect(doctor.ok).toBe(true);
89+
expect(adbCalls).toContainEqual(['shell', 'cmd', 'clipboard', 'get', 'text']);
90+
expect(adbCalls).toContainEqual(['shell', 'dumpsys', 'input_method']);
91+
expect(adbCalls).toContainEqual(['shell', 'echo', 'ok']);
92+
expect(adbCalls).toContainEqual(['shell', 'pidof', 'com.example.app']);
93+
});

0 commit comments

Comments
 (0)