@mobileaidev/ai-app-bridge 0.2.6 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -31,6 +31,8 @@ Commands:
31
31
  network Read generic in-app network records.
32
32
  state Read generic in-app state records.
33
33
  events Read generic in-app event records.
34
+ freeze-app Stop the target app processes with SIGSTOP.
35
+ thaw-app Resume the target app processes with SIGCONT.
34
36
 
35
37
  H5/WebView commands:
36
38
  h5-dom Read native Android WebView DOM.
@@ -48,7 +50,7 @@ Flutter commands:
48
50
  flutter-nodes Read Flutter operable nodes.
49
51
  flutter-action Dispatch a raw Flutter action payload.
50
52
  tap-flutter-text Tap a Flutter node by text.
51
- input-flutter-text Set Flutter text through the Flutter action bridge.
53
+ input-flutter-text Set Flutter text through the Flutter action bridge.
52
54
  scroll-flutter Scroll Flutter content.
53
55
  flutter-h5-dom Read DOM through a Flutter H5 adapter.
54
56
  flutter-h5-eval Execute JavaScript through a Flutter H5 adapter.
@@ -62,19 +64,19 @@ Device/action commands:
62
64
  tap-text Tap a visible node by exact text or content description.
63
65
  tap-uia-text Tap a UIAutomator node by text.
64
66
  wait-text Wait until text appears in bridge or UIAutomator output.
65
- input-text Set native Android text through the in-app bridge; ASCII can fall back to ADB.
67
+ input-text Set native Android text through the in-app bridge; ASCII can fall back to ADB.
66
68
  swipe Swipe device coordinates through ADB.
67
69
  keyevent Send an Android keyevent through ADB.
68
70
  keyboard-state Read Android soft keyboard visibility from dumpsys.
69
71
  hide-keyboard Hide the Android soft keyboard when it is visible.
70
72
 
71
- App/permission commands:
72
- install-apk Install an APK and assist device-side installer screens.
73
- clear-app-data Clear target app local data through the bridge runtime.
74
- launch-app Launch the target package LAUNCHER Activity.
75
- launch-activity Launch an explicit Android Activity component.
76
- launch-native-test Launch the debug native Android bridge test Activity.
77
- launch-flutter Launch the Flutter Activity.
73
+ App/permission commands:
74
+ install-apk Install an APK and assist device-side installer screens.
75
+ clear-app-data Clear target app local data through the bridge runtime.
76
+ launch-app Launch the target package LAUNCHER Activity.
77
+ launch-activity Launch an explicit Android Activity component.
78
+ launch-native-test Launch the debug native Android bridge test Activity.
79
+ launch-flutter Launch the Flutter Activity.
78
80
  permission-state Read Android runtime permission state.
79
81
  permission-grant Grant an Android runtime permission.
80
82
  permission-revoke Revoke an Android runtime permission.
@@ -95,14 +97,14 @@ Options:
95
97
  --adb-timeout-ms <ms> Timeout for ADB subprocesses.
96
98
  --out-file <path> Screenshot output path.
97
99
  --artifact-dir <path> Directory for generated screenshot/artifact defaults.
98
- --apk-path <path> APK path used by install-apk.
99
- --activity <name> Activity class for launch-activity or launch-app override.
100
- --component <pkg/act> Explicit Android component for launch-activity.
101
- --action <name> Intent action for launch-activity.
102
- --category <name> Intent category for launch-activity; may be repeated.
103
- --data <uri> Intent data URI for launch-activity.
104
- --extra <key=value> String intent extra for launch-activity; may be repeated.
105
- --text <text> Text used by Unicode-safe bridge input commands.
100
+ --apk-path <path> APK path used by install-apk.
101
+ --activity <name> Activity class for launch-activity or launch-app override.
102
+ --component <pkg/act> Explicit Android component for launch-activity.
103
+ --action <name> Intent action for launch-activity.
104
+ --category <name> Intent category for launch-activity; may be repeated.
105
+ --data <uri> Intent data URI for launch-activity.
106
+ --extra <key=value> String intent extra for launch-activity; may be repeated.
107
+ --text <text> Text used by Unicode-safe bridge input commands.
106
108
  --value <text> Text value used by h5-input or flutter-h5-input.
107
109
  --selector <css> CSS selector used by H5 commands.
108
110
  --target-text <text> Text used by tap-text or wait-text.
@@ -156,7 +158,7 @@ Options:
156
158
  --permission <name> Android permission for permission-* commands.
157
159
  --op <name> App-op name for appops-set.
158
160
  --mode <mode> App-op mode for appops-set.
159
- --pid <pid|current> PID filter for logcat.
161
+ --pid <pid|current> PID filter for logcat, or explicit process for freeze/thaw.
160
162
  --app-pid Filter logcat by current app pid.
161
163
  --tag <tag[,tag]> Logcat tag filter.
162
164
  --level <level> Minimum logcat level.
@@ -230,19 +232,19 @@ async function runCommand(command, options, ctx) {
230
232
  }
231
233
  case 'flutter-nodes':
232
234
  return flutterNodes(ctx);
233
- case 'tap-flutter-text':
234
- return flutterAction(ctx, { action: 'tapText', text: requiredString(options.targetText, 'targetText') });
235
- case 'input-flutter-text': {
236
- const result = await flutterAction(ctx, {
237
- action: 'inputText',
238
- text: requiredString(options.text, 'text'),
239
- ...(options.tapX !== undefined && options.tapY !== undefined ? { x: Number(options.tapX), y: Number(options.tapY) } : {}),
240
- });
241
- if (booleanOption(options.hideKeyboard)) {
242
- result.keyboard = await hideKeyboard(ctx, options);
243
- }
244
- return result;
245
- }
235
+ case 'tap-flutter-text':
236
+ return flutterAction(ctx, { action: 'tapText', text: requiredString(options.targetText, 'targetText') });
237
+ case 'input-flutter-text': {
238
+ const result = await flutterAction(ctx, {
239
+ action: 'inputText',
240
+ text: requiredString(options.text, 'text'),
241
+ ...(options.tapX !== undefined && options.tapY !== undefined ? { x: Number(options.tapX), y: Number(options.tapY) } : {}),
242
+ });
243
+ if (booleanOption(options.hideKeyboard)) {
244
+ result.keyboard = await hideKeyboard(ctx, options);
245
+ }
246
+ return result;
247
+ }
246
248
  case 'scroll-flutter':
247
249
  return options.targetText
248
250
  ? flutterAction(ctx, { action: 'scrollUntilText', text: options.targetText, maxSwipes: Number(options.maxSwipes || 12) })
@@ -257,6 +259,10 @@ async function runCommand(command, options, ctx) {
257
259
  return bridgeGet(ctx, withQuery('/v1/state', captureQuery(options)));
258
260
  case 'events':
259
261
  return bridgeGet(ctx, withQuery('/v1/events', captureQuery(options)));
262
+ case 'freeze-app':
263
+ return freezeApp(ctx, options);
264
+ case 'thaw-app':
265
+ return thawApp(ctx, options);
260
266
  case 'h5-dom':
261
267
  return bridgeGet(ctx, '/v1/h5/dom');
262
268
  case 'h5-eval':
@@ -297,12 +303,12 @@ async function runCommand(command, options, ctx) {
297
303
  return keyboardState(ctx);
298
304
  case 'hide-keyboard':
299
305
  return hideKeyboard(ctx, options);
300
- case 'install-apk':
301
- return installApk(ctx, options);
302
- case 'clear-app-data':
303
- return clearAppData(ctx);
304
- case 'webview-pages':
305
- return webviewPages(ctx, options);
306
+ case 'install-apk':
307
+ return installApk(ctx, options);
308
+ case 'clear-app-data':
309
+ return clearAppData(ctx);
310
+ case 'webview-pages':
311
+ return webviewPages(ctx, options);
306
312
  case 'webview-network':
307
313
  return webviewCdpCapture(ctx, { ...options, captureNetwork: true, captureConsole: true });
308
314
  case 'webview-console':
@@ -330,15 +336,15 @@ async function runCommand(command, options, ctx) {
330
336
  return appopsSet(ctx, requiredString(options.op, 'op'), requiredString(options.mode, 'mode'));
331
337
  case 'tap-uia-text':
332
338
  return tapUiaText(ctx, requiredString(options.targetText, 'targetText'), options);
333
- case 'permission-dialog':
334
- return permissionDialog(ctx, options);
335
- case 'launch-app':
336
- return launchApp(ctx, options);
337
- case 'launch-activity':
338
- return launchActivity(ctx, options);
339
- case 'launch-native-test':
340
- return launchNativeTest(ctx);
341
- case 'launch-flutter':
339
+ case 'permission-dialog':
340
+ return permissionDialog(ctx, options);
341
+ case 'launch-app':
342
+ return launchApp(ctx, options);
343
+ case 'launch-activity':
344
+ return launchActivity(ctx, options);
345
+ case 'launch-native-test':
346
+ return launchNativeTest(ctx);
347
+ case 'launch-flutter':
342
348
  return launchFlutter(ctx, options.initialRoute || '');
343
349
  case 'smoke':
344
350
  return smoke(ctx, options);
@@ -362,25 +368,25 @@ function parseArgs(argv) {
362
368
  const rawName = arg.slice(2);
363
369
  const name = rawName.replace(/-([a-z])/g, (_, value) => value.toUpperCase());
364
370
  const next = argv[index + 1];
365
- if (next === undefined || next.startsWith('--')) {
366
- appendOption(options, name, true);
367
- continue;
368
- }
369
- appendOption(options, name, next);
370
- index += 1;
371
- }
372
- return { command, options };
373
- }
374
-
375
- function appendOption(options, name, value) {
376
- if (name === 'extra' || name === 'category') {
377
- if (!Array.isArray(options[name])) options[name] = [];
378
- options[name].push(value);
379
- return;
380
- }
381
- options[name] = value;
382
- }
383
-
371
+ if (next === undefined || next.startsWith('--')) {
372
+ appendOption(options, name, true);
373
+ continue;
374
+ }
375
+ appendOption(options, name, next);
376
+ index += 1;
377
+ }
378
+ return { command, options };
379
+ }
380
+
381
+ function appendOption(options, name, value) {
382
+ if (name === 'extra' || name === 'category') {
383
+ if (!Array.isArray(options[name])) options[name] = [];
384
+ options[name].push(value);
385
+ return;
386
+ }
387
+ options[name] = value;
388
+ }
389
+
384
390
  async function adb(ctx, args, { binary = false } = {}) {
385
391
  const allArgs = adbArgs(ctx, args);
386
392
  return new Promise((resolve, reject) => {
@@ -785,41 +791,41 @@ async function ensureForward(ctx) {
785
791
  };
786
792
  }
787
793
 
788
- async function resolveDevicePort(ctx) {
789
- if (ctx.explicitPort) return { port: ctx.port, source: 'explicit-port' };
790
- try {
791
- const result = await adb(ctx, ['shell', 'run-as', ctx.packageName, 'cat', 'files/ai_app_bridge_port.json']);
792
- const state = JSON.parse(result.stdout.trim());
793
- const discoveredPort = Number(state.port);
794
- if (state.ok === true && Number.isInteger(discoveredPort) && discoveredPort > 0) {
795
- return { port: discoveredPort, source: 'package-port-file', state };
796
- }
797
- } catch (error) {
798
- if (!shouldUseDefaultPortFallback(ctx)) {
799
- ctx.devicePortSource = 'package-port-file';
800
- ctx.devicePortError = firstErrorLine(error);
801
- error.aiAppBridgePortDiscovery = true;
802
- throw error;
803
- }
804
- return {
805
- port: ctx.port,
806
- source: 'default-port',
807
- error: firstErrorLine(error),
808
- };
809
- }
810
- if (!shouldUseDefaultPortFallback(ctx)) {
811
- const error = new Error(`bridge port discovery failed for explicit package: ${ctx.packageName}`);
812
- error.aiAppBridgePortDiscovery = true;
813
- ctx.devicePortSource = 'package-port-file';
814
- ctx.devicePortError = firstErrorLine(error);
815
- throw error;
816
- }
817
- return { port: ctx.port, source: 'default-port' };
818
- }
819
-
820
- function shouldUseDefaultPortFallback(ctx) {
821
- return !ctx.explicitPackageName;
822
- }
794
+ async function resolveDevicePort(ctx) {
795
+ if (ctx.explicitPort) return { port: ctx.port, source: 'explicit-port' };
796
+ try {
797
+ const result = await adb(ctx, ['shell', 'run-as', ctx.packageName, 'cat', 'files/ai_app_bridge_port.json']);
798
+ const state = JSON.parse(result.stdout.trim());
799
+ const discoveredPort = Number(state.port);
800
+ if (state.ok === true && Number.isInteger(discoveredPort) && discoveredPort > 0) {
801
+ return { port: discoveredPort, source: 'package-port-file', state };
802
+ }
803
+ } catch (error) {
804
+ if (!shouldUseDefaultPortFallback(ctx)) {
805
+ ctx.devicePortSource = 'package-port-file';
806
+ ctx.devicePortError = firstErrorLine(error);
807
+ error.aiAppBridgePortDiscovery = true;
808
+ throw error;
809
+ }
810
+ return {
811
+ port: ctx.port,
812
+ source: 'default-port',
813
+ error: firstErrorLine(error),
814
+ };
815
+ }
816
+ if (!shouldUseDefaultPortFallback(ctx)) {
817
+ const error = new Error(`bridge port discovery failed for explicit package: ${ctx.packageName}`);
818
+ error.aiAppBridgePortDiscovery = true;
819
+ ctx.devicePortSource = 'package-port-file';
820
+ ctx.devicePortError = firstErrorLine(error);
821
+ throw error;
822
+ }
823
+ return { port: ctx.port, source: 'default-port' };
824
+ }
825
+
826
+ function shouldUseDefaultPortFallback(ctx) {
827
+ return !ctx.explicitPackageName;
828
+ }
823
829
 
824
830
  async function bridgeStatus(ctx, options = {}) {
825
831
  try {
@@ -972,24 +978,24 @@ function normalizeBridgeError(error) {
972
978
  suggestion: 'Confirm the target app is running and that the resolved bridge port belongs to this package.',
973
979
  };
974
980
  }
975
- if (lower.includes('adb timed out')) {
976
- return {
977
- code: 'adb_timeout',
978
- message,
979
- suggestion: 'Check the device state and retry; if the bridge port is known, pass --port to skip package port discovery.',
980
- };
981
- }
982
- if (error?.aiAppBridgePackageMismatch || lower.includes('bridge package mismatch')) {
983
- return {
984
- code: 'bridge_package_mismatch',
985
- message,
986
- suggestion: 'The resolved bridge port belongs to another package. Relaunch the target app and retry with the explicit packageName.',
987
- };
988
- }
989
- if (error?.aiAppBridgePortDiscovery || lower.includes('bridge port discovery failed') || lower.includes('run-as') || lower.includes('package not found')) {
990
- return {
991
- code: 'bridge_port_discovery_failed',
992
- message,
981
+ if (lower.includes('adb timed out')) {
982
+ return {
983
+ code: 'adb_timeout',
984
+ message,
985
+ suggestion: 'Check the device state and retry; if the bridge port is known, pass --port to skip package port discovery.',
986
+ };
987
+ }
988
+ if (error?.aiAppBridgePackageMismatch || lower.includes('bridge package mismatch')) {
989
+ return {
990
+ code: 'bridge_package_mismatch',
991
+ message,
992
+ suggestion: 'The resolved bridge port belongs to another package. Relaunch the target app and retry with the explicit packageName.',
993
+ };
994
+ }
995
+ if (error?.aiAppBridgePortDiscovery || lower.includes('bridge port discovery failed') || lower.includes('run-as') || lower.includes('package not found')) {
996
+ return {
997
+ code: 'bridge_port_discovery_failed',
998
+ message,
993
999
  suggestion: 'Install a debuggable build for the requested package or pass --port explicitly.',
994
1000
  };
995
1001
  }
@@ -1011,38 +1017,38 @@ function firstErrorLine(error) {
1011
1017
  return String(error?.message || error || 'unknown_error').split(/\r?\n/).find(Boolean) || 'unknown_error';
1012
1018
  }
1013
1019
 
1014
- async function bridgeGet(ctx, requestPath) {
1015
- await ensureForward(ctx);
1016
- const body = await httpGet(bridgeUrl(ctx, requestPath));
1017
- const payload = JSON.parse(body);
1018
- verifyBridgeTargetPackage(ctx, payload, requestPath);
1019
- return payload;
1020
- }
1021
-
1022
- async function bridgePost(ctx, requestPath, payload) {
1023
- await ensureForward(ctx);
1024
- const body = await httpPost(bridgeUrl(ctx, requestPath), payload);
1025
- const responsePayload = JSON.parse(body);
1026
- verifyBridgeTargetPackage(ctx, responsePayload, requestPath);
1027
- return responsePayload;
1028
- }
1029
-
1030
- function bridgeUrl(ctx, requestPath) {
1031
- return `http://127.0.0.1:${ctx.hostPort || ctx.port}${requestPath}`;
1032
- }
1033
-
1034
- function verifyBridgeTargetPackage(ctx, payload, requestPath) {
1035
- if (!ctx.explicitPackageName || !payload || typeof payload !== 'object') return;
1036
- const responsePackageName = payload.app && typeof payload.app === 'object'
1037
- ? payload.app.packageName
1038
- : undefined;
1039
- if (!responsePackageName || responsePackageName === ctx.packageName) return;
1040
- const error = new Error(`bridge package mismatch for ${requestPath}: expected ${ctx.packageName}, got ${responsePackageName}`);
1041
- error.aiAppBridgePackageMismatch = true;
1042
- error.expectedPackageName = ctx.packageName;
1043
- error.actualPackageName = responsePackageName;
1044
- throw error;
1045
- }
1020
+ async function bridgeGet(ctx, requestPath) {
1021
+ await ensureForward(ctx);
1022
+ const body = await httpGet(bridgeUrl(ctx, requestPath));
1023
+ const payload = JSON.parse(body);
1024
+ verifyBridgeTargetPackage(ctx, payload, requestPath);
1025
+ return payload;
1026
+ }
1027
+
1028
+ async function bridgePost(ctx, requestPath, payload) {
1029
+ await ensureForward(ctx);
1030
+ const body = await httpPost(bridgeUrl(ctx, requestPath), payload);
1031
+ const responsePayload = JSON.parse(body);
1032
+ verifyBridgeTargetPackage(ctx, responsePayload, requestPath);
1033
+ return responsePayload;
1034
+ }
1035
+
1036
+ function bridgeUrl(ctx, requestPath) {
1037
+ return `http://127.0.0.1:${ctx.hostPort || ctx.port}${requestPath}`;
1038
+ }
1039
+
1040
+ function verifyBridgeTargetPackage(ctx, payload, requestPath) {
1041
+ if (!ctx.explicitPackageName || !payload || typeof payload !== 'object') return;
1042
+ const responsePackageName = payload.app && typeof payload.app === 'object'
1043
+ ? payload.app.packageName
1044
+ : undefined;
1045
+ if (!responsePackageName || responsePackageName === ctx.packageName) return;
1046
+ const error = new Error(`bridge package mismatch for ${requestPath}: expected ${ctx.packageName}, got ${responsePackageName}`);
1047
+ error.aiAppBridgePackageMismatch = true;
1048
+ error.expectedPackageName = ctx.packageName;
1049
+ error.actualPackageName = responsePackageName;
1050
+ throw error;
1051
+ }
1046
1052
 
1047
1053
  function httpGet(url) {
1048
1054
  return new Promise((resolve, reject) => {
@@ -1531,6 +1537,95 @@ async function packagePidsFor(ctx) {
1531
1537
  }
1532
1538
  }
1533
1539
 
1540
+ async function freezeApp(ctx, options = {}) {
1541
+ return signalAppProcesses(ctx, options, 'SIGSTOP', 'freeze-app');
1542
+ }
1543
+
1544
+ async function thawApp(ctx, options = {}) {
1545
+ return signalAppProcesses(ctx, options, 'SIGCONT', 'thaw-app');
1546
+ }
1547
+
1548
+ async function signalAppProcesses(ctx, options, signal, action) {
1549
+ const pids = await targetAppPids(ctx, options);
1550
+ if (pids.length === 0) {
1551
+ return {
1552
+ ok: false,
1553
+ packageName: ctx.packageName,
1554
+ action,
1555
+ signal,
1556
+ error: 'app_process_not_found',
1557
+ };
1558
+ }
1559
+
1560
+ const results = [];
1561
+ for (const pid of pids) {
1562
+ try {
1563
+ const result = await adb(ctx, ['shell', 'run-as', ctx.packageName, 'kill', `-${signal}`, pid]);
1564
+ results.push({
1565
+ pid,
1566
+ ok: true,
1567
+ stdout: result.stdout.trim(),
1568
+ stderr: result.stderr.trim(),
1569
+ });
1570
+ } catch (error) {
1571
+ results.push({
1572
+ pid,
1573
+ ok: false,
1574
+ error: firstErrorLine(error),
1575
+ });
1576
+ }
1577
+ }
1578
+
1579
+ const failed = results.filter((item) => !item.ok);
1580
+ return {
1581
+ ok: failed.length === 0,
1582
+ packageName: ctx.packageName,
1583
+ action,
1584
+ signal,
1585
+ pids,
1586
+ results,
1587
+ ...(failed.length > 0 ? { error: 'signal_failed' } : {}),
1588
+ };
1589
+ }
1590
+
1591
+ async function targetAppPids(ctx, options = {}) {
1592
+ if (options.pid && options.pid !== true && options.pid !== 'current') {
1593
+ const explicitPid = String(options.pid).trim();
1594
+ return /^\d+$/.test(explicitPid) ? [explicitPid] : [];
1595
+ }
1596
+ const psPids = await packagePidsFromPs(ctx);
1597
+ if (psPids.length > 0) return psPids;
1598
+ return packagePidsFor(ctx);
1599
+ }
1600
+
1601
+ async function packagePidsFromPs(ctx) {
1602
+ if (!ctx.packageName) return [];
1603
+ try {
1604
+ const result = await adb(ctx, ['shell', 'ps', '-A', '-o', 'PID,NAME']);
1605
+ return parsePackagePidsFromPs(result.stdout, ctx.packageName);
1606
+ } catch (_) {
1607
+ return [];
1608
+ }
1609
+ }
1610
+
1611
+ function parsePackagePidsFromPs(stdout, packageName) {
1612
+ const pids = [];
1613
+ const seen = new Set();
1614
+ for (const line of String(stdout || '').split(/\r?\n/)) {
1615
+ const trimmed = line.trim();
1616
+ if (!trimmed || /^PID\s+NAME$/i.test(trimmed)) continue;
1617
+ const match = /^(\d+)\s+(\S+)$/.exec(trimmed);
1618
+ if (!match) continue;
1619
+ const pid = match[1];
1620
+ const processName = match[2];
1621
+ if (processName !== packageName && !processName.startsWith(`${packageName}:`)) continue;
1622
+ if (seen.has(pid)) continue;
1623
+ seen.add(pid);
1624
+ pids.push(pid);
1625
+ }
1626
+ return pids;
1627
+ }
1628
+
1534
1629
  function parseWebViewDevToolsSockets(procNetUnix, packagePids = []) {
1535
1630
  const packagePidSet = new Set(packagePids.map(String));
1536
1631
  const sockets = [];
@@ -2846,19 +2941,19 @@ async function tapFlutterText(ctx, targetText, options = {}) {
2846
2941
  };
2847
2942
  }
2848
2943
 
2849
- async function inputFlutterText(ctx, targetText, text) {
2850
- const operable = await flutterNodes(ctx);
2851
- const node = findFlutterNode(operable, targetText, 'input');
2852
- if (!node) {
2853
- throw new Error(`flutter input node not found: ${targetText}`);
2854
- }
2855
- const point = flutterNodePoint(node.input?.bounds || node.bounds, operable.viewport);
2856
- const result = await flutterAction(ctx, { action: 'inputText', text, x: point.x, y: point.y });
2857
- return {
2858
- ...result,
2859
- source: 'flutter-operable-tree',
2860
- targetText,
2861
- text,
2944
+ async function inputFlutterText(ctx, targetText, text) {
2945
+ const operable = await flutterNodes(ctx);
2946
+ const node = findFlutterNode(operable, targetText, 'input');
2947
+ if (!node) {
2948
+ throw new Error(`flutter input node not found: ${targetText}`);
2949
+ }
2950
+ const point = flutterNodePoint(node.input?.bounds || node.bounds, operable.viewport);
2951
+ const result = await flutterAction(ctx, { action: 'inputText', text, x: point.x, y: point.y });
2952
+ return {
2953
+ ...result,
2954
+ source: 'flutter-operable-tree',
2955
+ targetText,
2956
+ text,
2862
2957
  node,
2863
2958
  x: point.x,
2864
2959
  y: point.y,
@@ -3532,78 +3627,78 @@ function shouldDismissKeyboardForPoint({ point, viewport, keyboardVisible }) {
3532
3627
  };
3533
3628
  }
3534
3629
 
3535
- async function inputText(ctx, text, options = {}) {
3536
- const bridgePayload = inputTextBridgePayload(text, options);
3537
- let bridgeAttempt = null;
3538
- try {
3539
- const bridgeResult = await bridgePost(ctx, '/v1/action/input-text', bridgePayload);
3540
- if (bridgeResult?.ok) {
3541
- const result = {
3542
- ...bridgeResult,
3543
- transport: 'bridge',
3544
- source: bridgeResult.source || 'native-view',
3545
- request: bridgePayload,
3546
- };
3547
- if (booleanOption(options.hideKeyboard)) {
3548
- result.keyboard = await hideKeyboard(ctx, options);
3549
- }
3550
- return result;
3551
- }
3552
- bridgeAttempt = {
3553
- ok: false,
3554
- command: 'input-text',
3555
- requestPath: '/v1/action/input-text',
3556
- result: bridgeResult,
3557
- error: bridgeResult?.error || 'bridge_input_failed',
3558
- message: bridgeResult?.message || 'The app bridge did not accept native text input.',
3559
- };
3560
- } catch (error) {
3561
- bridgeAttempt = buildBridgeFailureResult(ctx, 'input-text', '/v1/action/input-text', error);
3562
- }
3563
-
3564
- if (!isAdbInputTextSafe(text)) {
3565
- return {
3566
- ok: false,
3567
- error: 'unicode_text_requires_bridge_input',
3568
- message: 'This text contains non-ASCII characters. Android adb shell input text cannot reliably enter Unicode; use an app build with AI App Bridge Android runtime 0.1.9+ and retry input-text/input_text.',
3569
- textLength: text.length,
3570
- bridge: bridgeAttempt,
3571
- suggestion: 'Update the target app bridge dependency to ai-app-bridge-android 0.1.9+ and pass --package-name so the CLI can discover the app bridge port.',
3572
- };
3573
- }
3574
-
3575
- await adb(ctx, ['shell', 'input', 'text', text.replace(/ /g, '%s')]);
3576
- const result = {
3577
- ok: true,
3578
- transport: 'adb',
3579
- source: 'adb-input-text-fallback',
3580
- text,
3581
- bridge: bridgeAttempt,
3582
- };
3583
- if (booleanOption(options.hideKeyboard)) {
3584
- result.keyboard = await hideKeyboard(ctx, options);
3585
- }
3586
- return result;
3587
- }
3588
-
3589
- function inputTextBridgePayload(text, options = {}) {
3590
- const payload = { text };
3591
- const x = Number(options.tapX);
3592
- const y = Number(options.tapY);
3593
- if (Number.isFinite(x) && Number.isFinite(y)) {
3594
- payload.x = x;
3595
- payload.y = y;
3596
- }
3597
- return payload;
3598
- }
3599
-
3600
- function isAdbInputTextSafe(text) {
3601
- return /^[\x20-\x7e]*$/.test(String(text));
3602
- }
3603
-
3604
- async function swipe(ctx, startX, startY, endX, endY, durationMs) {
3605
- await adb(ctx, ['shell', 'input', 'swipe', String(startX), String(startY), String(endX), String(endY), String(durationMs)]);
3606
- return { ok: true, transport: 'adb', startX, startY, endX, endY, durationMs };
3630
+ async function inputText(ctx, text, options = {}) {
3631
+ const bridgePayload = inputTextBridgePayload(text, options);
3632
+ let bridgeAttempt = null;
3633
+ try {
3634
+ const bridgeResult = await bridgePost(ctx, '/v1/action/input-text', bridgePayload);
3635
+ if (bridgeResult?.ok) {
3636
+ const result = {
3637
+ ...bridgeResult,
3638
+ transport: 'bridge',
3639
+ source: bridgeResult.source || 'native-view',
3640
+ request: bridgePayload,
3641
+ };
3642
+ if (booleanOption(options.hideKeyboard)) {
3643
+ result.keyboard = await hideKeyboard(ctx, options);
3644
+ }
3645
+ return result;
3646
+ }
3647
+ bridgeAttempt = {
3648
+ ok: false,
3649
+ command: 'input-text',
3650
+ requestPath: '/v1/action/input-text',
3651
+ result: bridgeResult,
3652
+ error: bridgeResult?.error || 'bridge_input_failed',
3653
+ message: bridgeResult?.message || 'The app bridge did not accept native text input.',
3654
+ };
3655
+ } catch (error) {
3656
+ bridgeAttempt = buildBridgeFailureResult(ctx, 'input-text', '/v1/action/input-text', error);
3657
+ }
3658
+
3659
+ if (!isAdbInputTextSafe(text)) {
3660
+ return {
3661
+ ok: false,
3662
+ error: 'unicode_text_requires_bridge_input',
3663
+ message: 'This text contains non-ASCII characters. Android adb shell input text cannot reliably enter Unicode; use an app build with AI App Bridge Android runtime 0.1.9+ and retry input-text/input_text.',
3664
+ textLength: text.length,
3665
+ bridge: bridgeAttempt,
3666
+ suggestion: 'Update the target app bridge dependency to ai-app-bridge-android 0.1.9+ and pass --package-name so the CLI can discover the app bridge port.',
3667
+ };
3668
+ }
3669
+
3670
+ await adb(ctx, ['shell', 'input', 'text', text.replace(/ /g, '%s')]);
3671
+ const result = {
3672
+ ok: true,
3673
+ transport: 'adb',
3674
+ source: 'adb-input-text-fallback',
3675
+ text,
3676
+ bridge: bridgeAttempt,
3677
+ };
3678
+ if (booleanOption(options.hideKeyboard)) {
3679
+ result.keyboard = await hideKeyboard(ctx, options);
3680
+ }
3681
+ return result;
3682
+ }
3683
+
3684
+ function inputTextBridgePayload(text, options = {}) {
3685
+ const payload = { text };
3686
+ const x = Number(options.tapX);
3687
+ const y = Number(options.tapY);
3688
+ if (Number.isFinite(x) && Number.isFinite(y)) {
3689
+ payload.x = x;
3690
+ payload.y = y;
3691
+ }
3692
+ return payload;
3693
+ }
3694
+
3695
+ function isAdbInputTextSafe(text) {
3696
+ return /^[\x20-\x7e]*$/.test(String(text));
3697
+ }
3698
+
3699
+ async function swipe(ctx, startX, startY, endX, endY, durationMs) {
3700
+ await adb(ctx, ['shell', 'input', 'swipe', String(startX), String(startY), String(endX), String(endY), String(durationMs)]);
3701
+ return { ok: true, transport: 'adb', startX, startY, endX, endY, durationMs };
3607
3702
  }
3608
3703
 
3609
3704
  async function keyevent(ctx, keyCode) {
@@ -3703,70 +3798,70 @@ async function safePermissionState(ctx, permission) {
3703
3798
  }
3704
3799
  }
3705
3800
 
3706
- async function appopsSet(ctx, op, mode) {
3707
- await adb(ctx, ['shell', 'appops', 'set', ctx.packageName, op, mode]);
3708
- return {
3801
+ async function appopsSet(ctx, op, mode) {
3802
+ await adb(ctx, ['shell', 'appops', 'set', ctx.packageName, op, mode]);
3803
+ return {
3709
3804
  ok: true,
3710
3805
  packageName: ctx.packageName,
3711
3806
  op,
3712
3807
  mode,
3713
- };
3714
- }
3715
-
3716
- async function clearAppData(ctx) {
3717
- if (!ctx.explicitPackageName) {
3718
- throw new Error('packageName is required for clear-app-data');
3719
- }
3720
- let bridgeError = null;
3721
- try {
3722
- const bridgeResult = await bridgePost(ctx, '/v1/app/clear-data', {});
3723
- return {
3724
- ...bridgeResult,
3725
- packageName: ctx.packageName,
3726
- method: 'bridge-runtime',
3727
- };
3728
- } catch (error) {
3729
- bridgeError = firstErrorLine(error);
3730
- }
3731
-
3732
- let result;
3733
- try {
3734
- result = await adb(ctx, clearAppDataAdbArgs(ctx.packageName));
3735
- } catch (error) {
3736
- return {
3737
- ok: false,
3738
- packageName: ctx.packageName,
3739
- action: 'clear-app-data',
3740
- error: 'clear_app_data_failed',
3741
- bridgeError,
3742
- message: error.message || String(error),
3743
- };
3744
- }
3745
- const output = [result.stdout, result.stderr].filter(Boolean).join('\n').trim();
3746
- if (!/^Success\b/im.test(output)) {
3747
- return {
3748
- ok: false,
3749
- packageName: ctx.packageName,
3750
- error: 'clear_app_data_failed',
3751
- bridgeError,
3752
- output,
3753
- };
3754
- }
3755
- return {
3756
- ok: true,
3757
- packageName: ctx.packageName,
3758
- action: 'clear-app-data',
3759
- method: 'pm-clear',
3760
- bridgeError,
3761
- output,
3762
- };
3763
- }
3764
-
3765
- function clearAppDataAdbArgs(packageName) {
3766
- return ['shell', 'pm', 'clear', packageName];
3767
- }
3768
-
3769
- async function resolveLogcatPid(ctx, options) {
3808
+ };
3809
+ }
3810
+
3811
+ async function clearAppData(ctx) {
3812
+ if (!ctx.explicitPackageName) {
3813
+ throw new Error('packageName is required for clear-app-data');
3814
+ }
3815
+ let bridgeError = null;
3816
+ try {
3817
+ const bridgeResult = await bridgePost(ctx, '/v1/app/clear-data', {});
3818
+ return {
3819
+ ...bridgeResult,
3820
+ packageName: ctx.packageName,
3821
+ method: 'bridge-runtime',
3822
+ };
3823
+ } catch (error) {
3824
+ bridgeError = firstErrorLine(error);
3825
+ }
3826
+
3827
+ let result;
3828
+ try {
3829
+ result = await adb(ctx, clearAppDataAdbArgs(ctx.packageName));
3830
+ } catch (error) {
3831
+ return {
3832
+ ok: false,
3833
+ packageName: ctx.packageName,
3834
+ action: 'clear-app-data',
3835
+ error: 'clear_app_data_failed',
3836
+ bridgeError,
3837
+ message: error.message || String(error),
3838
+ };
3839
+ }
3840
+ const output = [result.stdout, result.stderr].filter(Boolean).join('\n').trim();
3841
+ if (!/^Success\b/im.test(output)) {
3842
+ return {
3843
+ ok: false,
3844
+ packageName: ctx.packageName,
3845
+ error: 'clear_app_data_failed',
3846
+ bridgeError,
3847
+ output,
3848
+ };
3849
+ }
3850
+ return {
3851
+ ok: true,
3852
+ packageName: ctx.packageName,
3853
+ action: 'clear-app-data',
3854
+ method: 'pm-clear',
3855
+ bridgeError,
3856
+ output,
3857
+ };
3858
+ }
3859
+
3860
+ function clearAppDataAdbArgs(packageName) {
3861
+ return ['shell', 'pm', 'clear', packageName];
3862
+ }
3863
+
3864
+ async function resolveLogcatPid(ctx, options) {
3770
3865
  if (options.pid && options.pid !== true && options.pid !== 'current') {
3771
3866
  return String(options.pid);
3772
3867
  }
@@ -3781,17 +3876,17 @@ async function resolveLogcatPid(ctx, options) {
3781
3876
  return '';
3782
3877
  }
3783
3878
 
3784
- function filterLogcat(text, options) {
3785
- const tags = splitCsv(options.tag || options.tags);
3786
- const grep = options.grep ? String(options.grep) : '';
3787
- const grepCaseSensitive = Boolean(options.grepCaseSensitive);
3788
- const minLevel = priorityValue(options.level || options.minLevel || '');
3789
- const pid = options.pid ? String(options.pid) : '';
3790
- const requiresAppPid = options.appPid || options.packagePid || options.pid === 'current';
3791
- if (requiresAppPid && !pid) {
3792
- return '';
3793
- }
3794
- const lines = String(text || '').split(/\r?\n/);
3879
+ function filterLogcat(text, options) {
3880
+ const tags = splitCsv(options.tag || options.tags);
3881
+ const grep = options.grep ? String(options.grep) : '';
3882
+ const grepCaseSensitive = Boolean(options.grepCaseSensitive);
3883
+ const minLevel = priorityValue(options.level || options.minLevel || '');
3884
+ const pid = options.pid ? String(options.pid) : '';
3885
+ const requiresAppPid = options.appPid || options.packagePid || options.pid === 'current';
3886
+ if (requiresAppPid && !pid) {
3887
+ return '';
3888
+ }
3889
+ const lines = String(text || '').split(/\r?\n/);
3795
3890
  const filtered = [];
3796
3891
  let previousIncluded = false;
3797
3892
  for (const line of lines) {
@@ -3874,134 +3969,134 @@ function adbFollow(ctx, args, durationMs) {
3874
3969
  });
3875
3970
  }
3876
3971
 
3877
- async function launchApp(ctx, options = {}) {
3878
- if (options.component || options.activity) {
3879
- return launchActivity(ctx, options);
3880
- }
3881
-
3882
- const candidates = await launcherActivityCandidates(ctx);
3883
- if (candidates.length === 0) {
3884
- return {
3885
- ok: false,
3886
- error: 'launcher_not_found',
3887
- packageName: ctx.packageName,
3888
- launcherCandidates: candidates,
3889
- };
3890
- }
3891
- if (candidates.length > 1) {
3892
- return {
3893
- ok: false,
3894
- error: 'launcher_ambiguous',
3895
- packageName: ctx.packageName,
3896
- launcherCandidates: candidates,
3897
- suggestion: 'Pass --component or --activity to choose the intended launcher Activity.',
3898
- };
3899
- }
3900
-
3901
- return startActivity(ctx, candidates[0], options, {
3902
- packageName: ctx.packageName,
3903
- launcherCandidates: candidates,
3904
- });
3905
- }
3906
-
3907
- async function launchActivity(ctx, options = {}) {
3908
- const component = normalizeActivityComponent(
3909
- ctx.packageName,
3910
- options.component || requiredString(options.activity, 'activity'),
3911
- );
3912
- return startActivity(ctx, component, options, { packageName: ctx.packageName });
3913
- }
3914
-
3915
- async function launchNativeTest(ctx) {
3916
- const component = normalizeActivityComponent(ctx.packageName, ctx.nativeActivity);
3917
- return startActivity(ctx, component, {}, { packageName: ctx.packageName });
3918
- }
3919
-
3920
- async function launchFlutter(ctx, initialRoute) {
3921
- const component = normalizeActivityComponent(ctx.packageName, ctx.flutterActivity);
3922
- const options = initialRoute ? { extra: [`ai_app_initial_route=${initialRoute}`] } : {};
3923
- const result = await startActivity(ctx, component, options, { packageName: ctx.packageName });
3924
- return { ...result, initialRoute };
3925
- }
3926
-
3927
- async function launcherActivityCandidates(ctx) {
3928
- const result = await adb(ctx, [
3929
- 'shell',
3930
- 'cmd',
3931
- 'package',
3932
- 'query-activities',
3933
- '--brief',
3934
- '-a',
3935
- 'android.intent.action.MAIN',
3936
- '-c',
3937
- 'android.intent.category.LAUNCHER',
3938
- ctx.packageName,
3939
- ]);
3940
- return parseLauncherActivityCandidates(result.stdout);
3941
- }
3942
-
3943
- function parseLauncherActivityCandidates(stdout) {
3944
- const candidates = [];
3945
- for (const line of String(stdout || '').split(/\r?\n/)) {
3946
- const candidate = line.trim();
3947
- if (/^[A-Za-z0-9_.$]+\/[A-Za-z0-9_.$]+$/.test(candidate) && !candidates.includes(candidate)) {
3948
- candidates.push(candidate);
3949
- }
3950
- }
3951
- return candidates;
3952
- }
3953
-
3954
- function normalizeActivityComponent(packageName, activityOrComponent) {
3955
- const value = requiredString(activityOrComponent, 'activity');
3956
- if (value.includes('/')) return value;
3957
- return `${packageName}/${value}`;
3958
- }
3959
-
3960
- async function startActivity(ctx, component, options = {}, extraResult = {}) {
3961
- const args = buildAmStartArgs(component, options);
3962
- const result = await adb(ctx, args);
3963
- return {
3964
- ok: true,
3965
- transport: 'adb',
3966
- component,
3967
- ...extraResult,
3968
- stdout: result.stdout.trim(),
3969
- stderr: result.stderr.trim(),
3970
- };
3971
- }
3972
-
3973
- function buildAmStartArgs(component, options = {}) {
3974
- const args = ['shell', 'am', 'start'];
3975
- if (options.action) args.push('-a', options.action);
3976
- for (const category of optionList(options.category)) {
3977
- args.push('-c', category);
3978
- }
3979
- if (options.data) args.push('-d', options.data);
3980
- for (const extra of parseStartExtras(options.extra)) {
3981
- args.push('-e', extra.key, extra.value);
3982
- }
3983
- args.push('-n', component);
3984
- return args;
3985
- }
3986
-
3987
- function parseStartExtras(rawExtras) {
3988
- return optionList(rawExtras).map((rawExtra) => {
3989
- const value = String(rawExtra);
3990
- const separator = value.indexOf('=');
3991
- if (separator <= 0) {
3992
- throw new Error('extra must use key=value');
3993
- }
3994
- return {
3995
- key: value.slice(0, separator),
3996
- value: value.slice(separator + 1),
3997
- };
3998
- });
3999
- }
4000
-
4001
- function optionList(value) {
4002
- if (value === undefined || value === null || value === false || value === '') return [];
4003
- return Array.isArray(value) ? value : [value];
4004
- }
3972
+ async function launchApp(ctx, options = {}) {
3973
+ if (options.component || options.activity) {
3974
+ return launchActivity(ctx, options);
3975
+ }
3976
+
3977
+ const candidates = await launcherActivityCandidates(ctx);
3978
+ if (candidates.length === 0) {
3979
+ return {
3980
+ ok: false,
3981
+ error: 'launcher_not_found',
3982
+ packageName: ctx.packageName,
3983
+ launcherCandidates: candidates,
3984
+ };
3985
+ }
3986
+ if (candidates.length > 1) {
3987
+ return {
3988
+ ok: false,
3989
+ error: 'launcher_ambiguous',
3990
+ packageName: ctx.packageName,
3991
+ launcherCandidates: candidates,
3992
+ suggestion: 'Pass --component or --activity to choose the intended launcher Activity.',
3993
+ };
3994
+ }
3995
+
3996
+ return startActivity(ctx, candidates[0], options, {
3997
+ packageName: ctx.packageName,
3998
+ launcherCandidates: candidates,
3999
+ });
4000
+ }
4001
+
4002
+ async function launchActivity(ctx, options = {}) {
4003
+ const component = normalizeActivityComponent(
4004
+ ctx.packageName,
4005
+ options.component || requiredString(options.activity, 'activity'),
4006
+ );
4007
+ return startActivity(ctx, component, options, { packageName: ctx.packageName });
4008
+ }
4009
+
4010
+ async function launchNativeTest(ctx) {
4011
+ const component = normalizeActivityComponent(ctx.packageName, ctx.nativeActivity);
4012
+ return startActivity(ctx, component, {}, { packageName: ctx.packageName });
4013
+ }
4014
+
4015
+ async function launchFlutter(ctx, initialRoute) {
4016
+ const component = normalizeActivityComponent(ctx.packageName, ctx.flutterActivity);
4017
+ const options = initialRoute ? { extra: [`ai_app_initial_route=${initialRoute}`] } : {};
4018
+ const result = await startActivity(ctx, component, options, { packageName: ctx.packageName });
4019
+ return { ...result, initialRoute };
4020
+ }
4021
+
4022
+ async function launcherActivityCandidates(ctx) {
4023
+ const result = await adb(ctx, [
4024
+ 'shell',
4025
+ 'cmd',
4026
+ 'package',
4027
+ 'query-activities',
4028
+ '--brief',
4029
+ '-a',
4030
+ 'android.intent.action.MAIN',
4031
+ '-c',
4032
+ 'android.intent.category.LAUNCHER',
4033
+ ctx.packageName,
4034
+ ]);
4035
+ return parseLauncherActivityCandidates(result.stdout);
4036
+ }
4037
+
4038
+ function parseLauncherActivityCandidates(stdout) {
4039
+ const candidates = [];
4040
+ for (const line of String(stdout || '').split(/\r?\n/)) {
4041
+ const candidate = line.trim();
4042
+ if (/^[A-Za-z0-9_.$]+\/[A-Za-z0-9_.$]+$/.test(candidate) && !candidates.includes(candidate)) {
4043
+ candidates.push(candidate);
4044
+ }
4045
+ }
4046
+ return candidates;
4047
+ }
4048
+
4049
+ function normalizeActivityComponent(packageName, activityOrComponent) {
4050
+ const value = requiredString(activityOrComponent, 'activity');
4051
+ if (value.includes('/')) return value;
4052
+ return `${packageName}/${value}`;
4053
+ }
4054
+
4055
+ async function startActivity(ctx, component, options = {}, extraResult = {}) {
4056
+ const args = buildAmStartArgs(component, options);
4057
+ const result = await adb(ctx, args);
4058
+ return {
4059
+ ok: true,
4060
+ transport: 'adb',
4061
+ component,
4062
+ ...extraResult,
4063
+ stdout: result.stdout.trim(),
4064
+ stderr: result.stderr.trim(),
4065
+ };
4066
+ }
4067
+
4068
+ function buildAmStartArgs(component, options = {}) {
4069
+ const args = ['shell', 'am', 'start'];
4070
+ if (options.action) args.push('-a', options.action);
4071
+ for (const category of optionList(options.category)) {
4072
+ args.push('-c', category);
4073
+ }
4074
+ if (options.data) args.push('-d', options.data);
4075
+ for (const extra of parseStartExtras(options.extra)) {
4076
+ args.push('-e', extra.key, extra.value);
4077
+ }
4078
+ args.push('-n', component);
4079
+ return args;
4080
+ }
4081
+
4082
+ function parseStartExtras(rawExtras) {
4083
+ return optionList(rawExtras).map((rawExtra) => {
4084
+ const value = String(rawExtra);
4085
+ const separator = value.indexOf('=');
4086
+ if (separator <= 0) {
4087
+ throw new Error('extra must use key=value');
4088
+ }
4089
+ return {
4090
+ key: value.slice(0, separator),
4091
+ value: value.slice(separator + 1),
4092
+ };
4093
+ });
4094
+ }
4095
+
4096
+ function optionList(value) {
4097
+ if (value === undefined || value === null || value === false || value === '') return [];
4098
+ return Array.isArray(value) ? value : [value];
4099
+ }
4005
4100
 
4006
4101
  async function smoke(ctx, options) {
4007
4102
  const summary = {
@@ -4359,50 +4454,51 @@ function requiredNumber(value, name) {
4359
4454
  }
4360
4455
 
4361
4456
  module.exports = {
4362
- buildBridgeFailureResult,
4363
- clearAppDataAdbArgs,
4364
- defaultInstallerButtonTexts,
4457
+ buildBridgeFailureResult,
4458
+ clearAppDataAdbArgs,
4459
+ defaultInstallerButtonTexts,
4365
4460
  artifactTimestamp,
4366
4461
  compactBridgeTree,
4367
4462
  compactStatus,
4368
4463
  compactUiaTree,
4369
4464
  defaultArtifactDirectory,
4370
4465
  defaultArtifactPath,
4371
- findFlutterNode,
4372
- findTappableNodeByText,
4373
- filterLogcat,
4374
- firstErrorLine,
4466
+ findFlutterNode,
4467
+ findTappableNodeByText,
4468
+ filterLogcat,
4469
+ firstErrorLine,
4375
4470
  flutterNodePoint,
4376
4471
  flutterPhysicalViewport,
4377
4472
  helpText,
4378
- installerButtonTextsForSurface,
4379
- isAdbInputTextSafe,
4380
- isLikelyInstallerSurface,
4473
+ installerButtonTextsForSurface,
4474
+ isAdbInputTextSafe,
4475
+ isLikelyInstallerSurface,
4381
4476
  nodeTapState,
4382
- normalizeBridgeError,
4383
- normalizeActivityComponent,
4384
- parseWebViewDevToolsSockets,
4385
- parseArgs,
4386
- parseKeyboardState,
4387
- parseLauncherActivityCandidates,
4388
- parseStartExtras,
4389
- parseUiaBounds,
4477
+ normalizeBridgeError,
4478
+ normalizeActivityComponent,
4479
+ parseWebViewDevToolsSockets,
4480
+ parseArgs,
4481
+ parseKeyboardState,
4482
+ parsePackagePidsFromPs,
4483
+ parseLauncherActivityCandidates,
4484
+ parseStartExtras,
4485
+ parseUiaBounds,
4390
4486
  parseUiaViewport,
4391
4487
  parseComponentFromWindowLine,
4392
4488
  parseForegroundWindow,
4393
4489
  chooseWebViewDevToolsSocket,
4394
4490
  chooseWebViewPage,
4395
- shapeNetworkCapture,
4396
- compactNetworkRecord,
4397
- pruneGeneratedArtifacts,
4398
- shouldSkipInstallerTapForInstalledPackage,
4399
- shouldDismissKeyboardForPoint,
4400
- shouldUseDefaultPortFallback,
4401
- screenshotOutputPath,
4402
- statusSearchText,
4403
- uiautomatorLockPath,
4404
- verifyBridgeTargetPackage,
4405
- waitTextConditionsMet,
4406
- withFileLock,
4407
- };
4491
+ shapeNetworkCapture,
4492
+ compactNetworkRecord,
4493
+ pruneGeneratedArtifacts,
4494
+ shouldSkipInstallerTapForInstalledPackage,
4495
+ shouldDismissKeyboardForPoint,
4496
+ shouldUseDefaultPortFallback,
4497
+ screenshotOutputPath,
4498
+ statusSearchText,
4499
+ uiautomatorLockPath,
4500
+ verifyBridgeTargetPackage,
4501
+ waitTextConditionsMet,
4502
+ withFileLock,
4503
+ };
4408
4504