@aiscene/android 1.6.7 → 1.6.8

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.
package/dist/lib/index.js CHANGED
@@ -468,6 +468,7 @@ var __webpack_exports__ = {};
468
468
  });
469
469
  const external_node_assert_namespaceObject = require("node:assert");
470
470
  var external_node_assert_default = /*#__PURE__*/ __webpack_require__.n(external_node_assert_namespaceObject);
471
+ const external_node_child_process_namespaceObject = require("node:child_process");
471
472
  var external_node_fs_ = __webpack_require__("node:fs");
472
473
  var external_node_fs_default = /*#__PURE__*/ __webpack_require__.n(external_node_fs_);
473
474
  var external_node_module_ = __webpack_require__("node:module");
@@ -611,6 +612,9 @@ var __webpack_exports__ = {};
611
612
  const IME_STRATEGY_ALWAYS_YADB = 'always-yadb';
612
613
  const IME_STRATEGY_YADB_FOR_NON_ASCII = 'yadb-for-non-ascii';
613
614
  const debugDevice = (0, logger_.getDebug)('android:device');
615
+ function escapeForShell(text) {
616
+ return text.replace(/'/g, "'\\''").replace(/\n/g, '\\n');
617
+ }
614
618
  class AndroidDevice {
615
619
  actionSpace() {
616
620
  const defaultActions = [
@@ -638,6 +642,12 @@ var __webpack_exports__ = {};
638
642
  ]).default('replace').optional().describe('Input mode: "replace" (default) - clear the field and input the value; "typeOnly" - type the value directly without clearing the field first; "clear" - clear the field without inputting new text.')),
639
643
  locate: (0, core_namespaceObject.getMidsceneLocationSchema)().describe('The input field to be filled').optional()
640
644
  }),
645
+ sample: {
646
+ value: 'test@example.com',
647
+ locate: {
648
+ prompt: 'the email input field'
649
+ }
650
+ },
641
651
  call: async (param)=>{
642
652
  const element = param.locate;
643
653
  if ('typeOnly' !== param.mode) await this.clearInput(element);
@@ -683,9 +693,21 @@ var __webpack_exports__ = {};
683
693
  y: to.center[1]
684
694
  });
685
695
  }),
696
+ (0, device_namespaceObject.defineActionSwipe)(async (param)=>{
697
+ const { startPoint, endPoint, duration, repeatCount } = (0, device_namespaceObject.normalizeMobileSwipeParam)(param, await this.size());
698
+ for(let i = 0; i < repeatCount; i++)await this.mouseDrag(startPoint, endPoint, duration);
699
+ }),
686
700
  (0, device_namespaceObject.defineActionKeyboardPress)(async (param)=>{
687
701
  await this.keyboardPress(param.keyName);
688
702
  }),
703
+ (0, device_namespaceObject.defineActionCursorMove)(async (param)=>{
704
+ const arrowKey = 'left' === param.direction ? 'ArrowLeft' : 'ArrowRight';
705
+ const times = param.times ?? 1;
706
+ for(let i = 0; i < times; i++){
707
+ await this.keyboardPress(arrowKey);
708
+ await (0, utils_namespaceObject.sleep)(100);
709
+ }
710
+ }),
689
711
  (0, device_namespaceObject.defineAction)({
690
712
  name: 'LongPress',
691
713
  description: 'Trigger a long press on the screen at specified element',
@@ -693,6 +715,11 @@ var __webpack_exports__ = {};
693
715
  duration: core_namespaceObject.z.number().optional().describe('The duration of the long press in milliseconds'),
694
716
  locate: (0, core_namespaceObject.getMidsceneLocationSchema)().describe('The element to be long pressed')
695
717
  }),
718
+ sample: {
719
+ locate: {
720
+ prompt: 'the message bubble'
721
+ }
722
+ },
696
723
  call: async (param)=>{
697
724
  const element = param.locate;
698
725
  if (!element) throw new Error('LongPress requires an element to be located');
@@ -712,6 +739,12 @@ var __webpack_exports__ = {};
712
739
  duration: core_namespaceObject.z.number().optional().describe('The duration of the pull (in milliseconds)'),
713
740
  locate: (0, core_namespaceObject.getMidsceneLocationSchema)().optional().describe('The element to start the pull from (optional)')
714
741
  }),
742
+ sample: {
743
+ direction: 'down',
744
+ locate: {
745
+ prompt: 'the center of the content list area'
746
+ }
747
+ },
715
748
  call: async (param)=>{
716
749
  const element = param.locate;
717
750
  const startPoint = element ? {
@@ -724,6 +757,16 @@ var __webpack_exports__ = {};
724
757
  else throw new Error(`Unknown pull direction: ${param.direction}`);
725
758
  }
726
759
  }),
760
+ (0, device_namespaceObject.defineActionPinch)(async (param)=>{
761
+ const { centerX, centerY, startDistance, endDistance, duration } = (0, device_namespaceObject.normalizePinchParam)(param, await this.size());
762
+ const { x: adjCenterX, y: adjCenterY } = await this.adjustCoordinates(centerX, centerY);
763
+ const ratio = 0 !== adjCenterX && 0 !== centerX ? adjCenterX / centerX : 1;
764
+ const adjStartDist = Math.round(startDistance * ratio);
765
+ const adjEndDist = Math.round(endDistance * ratio);
766
+ await this.ensureYadb();
767
+ const adb = await this.getAdb();
768
+ await adb.shell(`app_process${this.getDisplayArg()} -Djava.class.path=/data/local/tmp/yadb /data/local/tmp com.ysbing.yadb.Main -pinch ${adjCenterX} ${adjCenterY} ${adjStartDist} ${adjEndDist} ${duration}`);
769
+ }),
727
770
  (0, device_namespaceObject.defineActionClearInput)(async (param)=>{
728
771
  await this.clearInput(param.locate);
729
772
  })
@@ -868,66 +911,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
868
911
  async execYadb(keyboardContent) {
869
912
  await this.ensureYadb();
870
913
  const adb = await this.getAdb();
871
- try {
872
- const command = `app_process${this.getDisplayArg()} -Djava.class.path=/data/local/tmp/yadb /data/local/tmp com.ysbing.yadb.Main -keyboard "${keyboardContent}"`;
873
- debugDevice(`Executing YADB input: "${keyboardContent}"`);
874
- const inputPromise = adb.shell(command);
875
- const timeoutPromise = new Promise((_, reject)=>setTimeout(()=>reject(new Error('YADB timeout')), 1000));
876
- await Promise.race([
877
- inputPromise,
878
- timeoutPromise
879
- ]);
880
- debugDevice(`YADB input completed: "${keyboardContent}"`);
881
- } catch (error) {
882
- const isAccessibilityConflict = error?.cause?.stderr?.includes('UiAutomationService') || error?.cause?.stderr?.includes('already registered') || error?.message?.includes('UiAutomationService');
883
- if (isAccessibilityConflict) {
884
- debugDevice("YADB failed due to AccessibilityService conflict (likely Appium running), falling back to clipboard method");
885
- await this.inputViaClipboard(keyboardContent);
886
- } else 'YADB timeout' === error.message ? debugDevice(`YADB timed out after 2s, assuming input succeeded: "${keyboardContent}"`) : debugDevice(`YADB execution may have completed despite error: ${error}`);
887
- }
888
- }
889
- async inputViaClipboard(text) {
890
- const adb = await this.getAdb();
891
- try {
892
- debugDevice(`Inputting via clipboard: "${text}"`);
893
- const escapedText = text.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
894
- const setClipboardCmd = `
895
- content insert --uri content://settings/system --bind name:s:clipboard_text --bind value:s:"${escapedText}"
896
- `;
897
- await adb.shell(setClipboardCmd);
898
- await (0, utils_namespaceObject.sleep)(100);
899
- await adb.shell('input keyevent KEYCODE_PASTE');
900
- await (0, utils_namespaceObject.sleep)(100);
901
- debugDevice(`Clipboard input completed via content provider: "${text}"`);
902
- } catch (error1) {
903
- debugDevice(`Content provider clipboard failed, trying clipper app: ${error1}`);
904
- try {
905
- const base64Text = Buffer.from(text, 'utf-8').toString('base64');
906
- await adb.shell(`am broadcast -a clipper.set -e text "${base64Text}"`);
907
- await (0, utils_namespaceObject.sleep)(100);
908
- await adb.shell('input keyevent KEYCODE_PASTE');
909
- await (0, utils_namespaceObject.sleep)(100);
910
- debugDevice(`Clipboard input completed via clipper: "${text}"`);
911
- } catch (error2) {
912
- debugDevice(`All clipboard methods failed: ${error2}`);
913
- const isPureAscii = /^[\x00-\x7F]*$/.test(text);
914
- if (isPureAscii) {
915
- debugDevice(`Using ADB inputText for ASCII text: "${text}"`);
916
- await adb.inputText(text);
917
- } else await this.inputCharByChar(text);
918
- }
919
- }
920
- }
921
- async inputCharByChar(text) {
922
- const adb = await this.getAdb();
923
- debugDevice(`Inputting character by character (slow method): "${text}"`);
924
- const chars = Array.from(text);
925
- for (const char of chars){
926
- if (' ' === char) await adb.shell('input keyevent KEYCODE_SPACE');
927
- else await adb.shell(`input text "${char}"`);
928
- await (0, utils_namespaceObject.sleep)(50);
929
- }
930
- debugDevice("Character-by-character input completed");
914
+ await adb.shell(`app_process${this.getDisplayArg()} -Djava.class.path=/data/local/tmp/yadb /data/local/tmp com.ysbing.yadb.Main -keyboard '${keyboardContent}'`);
931
915
  }
932
916
  async getElementsInfo() {
933
917
  return [];
@@ -940,6 +924,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
940
924
  }
941
925
  async getScreenSize() {
942
926
  const shouldCache = !(this.options?.alwaysRefreshScreenInfo ?? false);
927
+ debugDevice(`getScreenSize: alwaysRefreshScreenInfo=${this.options?.alwaysRefreshScreenInfo}, shouldCache=${shouldCache}, hasCachedSize=${!!this.cachedScreenSize}`);
943
928
  if (shouldCache && this.cachedScreenSize) return this.cachedScreenSize;
944
929
  const adb = await this.getAdb();
945
930
  if ('number' == typeof this.options?.displayId) try {
@@ -1073,6 +1058,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1073
1058
  }
1074
1059
  async getDisplayOrientation() {
1075
1060
  const shouldCache = !(this.options?.alwaysRefreshScreenInfo ?? false);
1061
+ debugDevice(`getDisplayOrientation: alwaysRefreshScreenInfo=${this.options?.alwaysRefreshScreenInfo}, shouldCache=${shouldCache}, hasCachedOrientation=${null !== this.cachedOrientation}`);
1076
1062
  if (shouldCache && null !== this.cachedOrientation) return this.cachedOrientation;
1077
1063
  const adb = await this.getAdb();
1078
1064
  let orientation = 0;
@@ -1098,6 +1084,15 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1098
1084
  if (shouldCache) this.cachedOrientation = orientation;
1099
1085
  return orientation;
1100
1086
  }
1087
+ async getOrientedPhysicalSize() {
1088
+ const info = await this.getDevicePhysicalInfo();
1089
+ const isLandscape = 1 === info.orientation || 3 === info.orientation;
1090
+ const shouldSwap = true !== info.isCurrentOrientation && isLandscape;
1091
+ return {
1092
+ width: shouldSwap ? info.physicalHeight : info.physicalWidth,
1093
+ height: shouldSwap ? info.physicalWidth : info.physicalHeight
1094
+ };
1095
+ }
1101
1096
  async size() {
1102
1097
  const deviceInfo = await this.getDevicePhysicalInfo();
1103
1098
  const adapter = this.getScrcpyAdapter();
@@ -1124,7 +1119,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1124
1119
  height: logicalHeight
1125
1120
  };
1126
1121
  }
1127
- async cacheFeatureForPoint(center, options) {
1122
+ async cacheFeatureForPoint(center) {
1128
1123
  const { width, height } = await this.size();
1129
1124
  debugDevice('cacheFeatureForPoint: center=[%s,%s], screen=[%s,%s]', center[0], center[1], width, height);
1130
1125
  return {
@@ -1204,14 +1199,23 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1204
1199
  const androidScreenshotPath = `/data/local/tmp/ms_${screenshotId}.png`;
1205
1200
  const useShellScreencap = 'number' == typeof this.options?.displayId;
1206
1201
  try {
1207
- if (useShellScreencap) throw new Error('Using shell screencap for displayId');
1208
- debugDevice('Taking screenshot via adb.takeScreenshot');
1209
- screenshotBuffer = await adb.takeScreenshot(null);
1210
- debugDevice('adb.takeScreenshot completed');
1211
- if (!screenshotBuffer) throw new Error('Failed to capture screenshot: screenshotBuffer is null');
1212
- if (!(0, img_namespaceObject.isValidPNGImageBuffer)(screenshotBuffer)) {
1213
- debugDevice('Invalid image buffer detected: not a valid image format');
1214
- throw new Error('Screenshot buffer has invalid format: could not find valid image signature');
1202
+ if (!useShellScreencap && this.takeScreenshotFailCount < AndroidDevice.TAKE_SCREENSHOT_FAIL_THRESHOLD) {
1203
+ debugDevice('Taking screenshot via adb.takeScreenshot');
1204
+ screenshotBuffer = await adb.takeScreenshot(null);
1205
+ debugDevice('adb.takeScreenshot completed');
1206
+ if (!screenshotBuffer) {
1207
+ this.takeScreenshotFailCount++;
1208
+ throw new Error('Failed to capture screenshot: screenshotBuffer is null');
1209
+ }
1210
+ if (!(0, img_namespaceObject.isValidImageBuffer)(screenshotBuffer)) {
1211
+ debugDevice('Invalid image buffer detected: not a valid image format');
1212
+ this.takeScreenshotFailCount++;
1213
+ throw new Error('Screenshot buffer has invalid format: could not find valid image signature');
1214
+ }
1215
+ this.takeScreenshotFailCount = 0;
1216
+ } else {
1217
+ if (this.takeScreenshotFailCount >= AndroidDevice.TAKE_SCREENSHOT_FAIL_THRESHOLD) debugDevice('Skipping takeScreenshot (failed %d consecutive times), using shell screencap directly', this.takeScreenshotFailCount);
1218
+ throw new Error('Using shell screencap directly');
1215
1219
  }
1216
1220
  const validScreenshotBufferSize = this.options?.minScreenshotBufferSize ?? 10240;
1217
1221
  if (validScreenshotBufferSize > 0 && screenshotBuffer.length < validScreenshotBufferSize) {
@@ -1240,12 +1244,21 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1240
1244
  screenshotBuffer = await external_node_fs_default().promises.readFile(screenshotPath);
1241
1245
  const validScreenshotBufferSize = this.options?.minScreenshotBufferSize ?? 10240;
1242
1246
  if (!screenshotBuffer || validScreenshotBufferSize > 0 && screenshotBuffer.length < validScreenshotBufferSize) throw new Error(`Fallback screenshot validation failed: buffer size ${screenshotBuffer?.length || 0} bytes (minimum: ${validScreenshotBufferSize})`);
1243
- if (!(0, img_namespaceObject.isValidPNGImageBuffer)(screenshotBuffer)) throw new Error('Fallback screenshot buffer has invalid PNG format');
1247
+ if (!(0, img_namespaceObject.isValidImageBuffer)(screenshotBuffer)) throw new Error('Fallback screenshot buffer has invalid PNG format');
1244
1248
  debugDevice(`Fallback screenshot validated successfully: ${screenshotBuffer.length} bytes`);
1245
1249
  } finally{
1246
- Promise.resolve().then(()=>adb.shell(`rm ${androidScreenshotPath}`)).catch((error)=>{
1247
- debugDevice(`Failed to delete remote screenshot: ${error}`);
1250
+ const adbPath = adb.executable?.path ?? 'adb';
1251
+ const child = (0, external_node_child_process_namespaceObject.execFile)(adbPath, [
1252
+ '-s',
1253
+ this.deviceId,
1254
+ 'shell',
1255
+ `rm ${androidScreenshotPath}`
1256
+ ], {
1257
+ timeout: 3000
1258
+ }, (err)=>{
1259
+ if (err) debugDevice('Failed to delete remote screenshot: %s', err.message);
1248
1260
  });
1261
+ child.unref();
1249
1262
  }
1250
1263
  }
1251
1264
  if (!screenshotBuffer) throw new Error('Failed to capture screenshot: all methods failed');
@@ -1408,24 +1421,32 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1408
1421
  if (!this.yadbPushed) {
1409
1422
  const adb = await this.getAdb();
1410
1423
  const androidPkgJson = (0, external_node_module_.createRequire)(__rslib_import_meta_url__).resolve('@aiscene/android/package.json');
1411
- const yadbDir = external_node_path_default().join(external_node_path_default().dirname(androidPkgJson), 'bin');
1412
- await adb.push(yadbDir, '/data/local/tmp');
1424
+ const yadbBin = external_node_path_default().join(external_node_path_default().dirname(androidPkgJson), 'bin', 'yadb');
1425
+ await adb.push(yadbBin, '/data/local/tmp');
1413
1426
  this.yadbPushed = true;
1414
1427
  }
1415
1428
  }
1416
1429
  shouldUseYadbForText(text) {
1417
1430
  const hasNonAscii = /[\x80-\uFFFF]/.test(text);
1418
1431
  const hasFormatSpecifiers = /%[a-zA-Z]/.test(text);
1419
- return hasNonAscii || hasFormatSpecifiers;
1432
+ const hasShellSpecialChars = /[\\`$]/.test(text);
1433
+ const hasBothQuotes = text.includes('"') && text.includes("'");
1434
+ return hasNonAscii || hasFormatSpecifiers || hasShellSpecialChars || hasBothQuotes;
1420
1435
  }
1421
1436
  async keyboardType(text, options) {
1422
1437
  if (!text) return;
1423
1438
  const adb = await this.getAdb();
1424
- const shouldUseYadb = this.shouldUseYadbForText(text);
1425
1439
  const IME_STRATEGY = (this.options?.imeStrategy || env_namespaceObject.globalConfigManager.getEnvConfigValue(env_namespaceObject.MIDSCENE_ANDROID_IME_STRATEGY)) ?? IME_STRATEGY_YADB_FOR_NON_ASCII;
1426
1440
  const shouldAutoDismissKeyboard = options?.autoDismissKeyboard ?? this.options?.autoDismissKeyboard ?? true;
1427
- if (IME_STRATEGY === IME_STRATEGY_ALWAYS_YADB || IME_STRATEGY === IME_STRATEGY_YADB_FOR_NON_ASCII && shouldUseYadb) await this.execYadb(text);
1428
- else await adb.inputText(text);
1441
+ const useYadb = IME_STRATEGY === IME_STRATEGY_ALWAYS_YADB || IME_STRATEGY === IME_STRATEGY_YADB_FOR_NON_ASCII && this.shouldUseYadbForText(text);
1442
+ if (useYadb) await this.execYadb(escapeForShell(text));
1443
+ else {
1444
+ const segments = text.split('\n');
1445
+ for(let i = 0; i < segments.length; i++){
1446
+ if (segments[i].length > 0) await adb.inputText(segments[i]);
1447
+ if (i < segments.length - 1) await adb.keyevent(66);
1448
+ }
1449
+ }
1429
1450
  if (true === shouldAutoDismissKeyboard) await this.hideKeyboard(options);
1430
1451
  }
1431
1452
  normalizeKeyName(key) {
@@ -1473,12 +1494,12 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1473
1494
  }
1474
1495
  async mouseClick(x, y) {
1475
1496
  const adb = await this.getAdb();
1476
- const { x: adjustedX, y: adjustedY } = this.adjustCoordinates(x, y);
1497
+ const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
1477
1498
  await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedX} ${adjustedY} ${adjustedX} ${adjustedY} 150`);
1478
1499
  }
1479
1500
  async mouseDoubleClick(x, y) {
1480
1501
  const adb = await this.getAdb();
1481
- const { x: adjustedX, y: adjustedY } = this.adjustCoordinates(x, y);
1502
+ const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
1482
1503
  const tapCommand = `input${this.getDisplayArg()} tap ${adjustedX} ${adjustedY}`;
1483
1504
  await adb.shell(tapCommand);
1484
1505
  await (0, utils_namespaceObject.sleep)(50);
@@ -1489,8 +1510,8 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1489
1510
  }
1490
1511
  async mouseDrag(from, to, duration) {
1491
1512
  const adb = await this.getAdb();
1492
- const { x: fromX, y: fromY } = this.adjustCoordinates(from.x, from.y);
1493
- const { x: toX, y: toY } = this.adjustCoordinates(to.x, to.y);
1513
+ const { x: fromX, y: fromY } = await this.adjustCoordinates(from.x, from.y);
1514
+ const { x: toX, y: toY } = await this.adjustCoordinates(to.x, to.y);
1494
1515
  const swipeDuration = duration ?? defaultNormalScrollDuration;
1495
1516
  await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${swipeDuration}`);
1496
1517
  }
@@ -1500,16 +1521,16 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1500
1521
  const n = 4;
1501
1522
  const startX = Math.round(deltaX < 0 ? width / n * (n - 1) : width / n);
1502
1523
  const startY = Math.round(deltaY < 0 ? height / n * (n - 1) : height / n);
1503
- const maxNegativeDeltaX = startX;
1504
- const maxPositiveDeltaX = Math.round(width / n * (n - 1));
1505
- const maxNegativeDeltaY = startY;
1506
- const maxPositiveDeltaY = Math.round(height / n * (n - 1));
1524
+ const maxPositiveDeltaX = startX;
1525
+ const maxNegativeDeltaX = width - startX;
1526
+ const maxPositiveDeltaY = startY;
1527
+ const maxNegativeDeltaY = height - startY;
1507
1528
  deltaX = Math.max(-maxNegativeDeltaX, Math.min(deltaX, maxPositiveDeltaX));
1508
1529
  deltaY = Math.max(-maxNegativeDeltaY, Math.min(deltaY, maxPositiveDeltaY));
1509
1530
  const endX = Math.round(startX - deltaX);
1510
1531
  const endY = Math.round(startY - deltaY);
1511
- const { x: adjustedStartX, y: adjustedStartY } = this.adjustCoordinates(startX, startY);
1512
- const { x: adjustedEndX, y: adjustedEndY } = this.adjustCoordinates(endX, endY);
1532
+ const { x: adjustedStartX, y: adjustedStartY } = await this.adjustCoordinates(startX, startY);
1533
+ const { x: adjustedEndX, y: adjustedEndY } = await this.adjustCoordinates(endX, endY);
1513
1534
  const adb = await this.getAdb();
1514
1535
  const swipeDuration = duration ?? defaultNormalScrollDuration;
1515
1536
  await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedStartX} ${adjustedStartY} ${adjustedEndX} ${adjustedEndY} ${swipeDuration}`);
@@ -1520,6 +1541,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1520
1541
  this.cachedPhysicalDisplayId = void 0;
1521
1542
  this.cachedScreenSize = null;
1522
1543
  this.cachedOrientation = null;
1544
+ this.scalingRatio = 1;
1523
1545
  if (this.scrcpyAdapter) {
1524
1546
  await this.scrcpyAdapter.disconnect();
1525
1547
  this.scrcpyAdapter = null;
@@ -1559,7 +1581,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1559
1581
  }
1560
1582
  async longPress(x, y, duration = 2000) {
1561
1583
  const adb = await this.getAdb();
1562
- const { x: adjustedX, y: adjustedY } = this.adjustCoordinates(x, y);
1584
+ const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
1563
1585
  await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedX} ${adjustedY} ${adjustedX} ${adjustedY} ${duration}`);
1564
1586
  }
1565
1587
  async pullDown(startPoint, distance, duration = 800) {
@@ -1581,8 +1603,8 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1581
1603
  }
1582
1604
  async pullDrag(from, to, duration) {
1583
1605
  const adb = await this.getAdb();
1584
- const { x: fromX, y: fromY } = this.adjustCoordinates(from.x, from.y);
1585
- const { x: toX, y: toY } = this.adjustCoordinates(to.x, to.y);
1606
+ const { x: fromX, y: fromY } = await this.adjustCoordinates(from.x, from.y);
1607
+ const { x: toX, y: toY } = await this.adjustCoordinates(to.x, to.y);
1586
1608
  await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${duration}`);
1587
1609
  }
1588
1610
  async pullUp(startPoint, distance, duration = 600) {
@@ -1669,7 +1691,6 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1669
1691
  device_define_property(this, "yadbPushed", false);
1670
1692
  device_define_property(this, "devicePixelRatio", 1);
1671
1693
  device_define_property(this, "devicePixelRatioInitialized", false);
1672
- device_define_property(this, "scalingRatio", 1);
1673
1694
  device_define_property(this, "adb", null);
1674
1695
  device_define_property(this, "connectingAdb", null);
1675
1696
  device_define_property(this, "destroyed", false);
@@ -1680,6 +1701,8 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1680
1701
  device_define_property(this, "cachedPhysicalDisplayId", void 0);
1681
1702
  device_define_property(this, "scrcpyAdapter", null);
1682
1703
  device_define_property(this, "appNameMapping", {});
1704
+ device_define_property(this, "scalingRatio", 1);
1705
+ device_define_property(this, "takeScreenshotFailCount", 0);
1683
1706
  device_define_property(this, "interfaceType", 'android');
1684
1707
  device_define_property(this, "uri", void 0);
1685
1708
  device_define_property(this, "options", void 0);
@@ -1689,6 +1712,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1689
1712
  this.customActions = options?.customActions;
1690
1713
  }
1691
1714
  }
1715
+ device_define_property(AndroidDevice, "TAKE_SCREENSHOT_FAIL_THRESHOLD", 3);
1692
1716
  const runAdbShellParamSchema = core_namespaceObject.z.object({
1693
1717
  command: core_namespaceObject.z.string().describe('ADB shell command to execute')
1694
1718
  });
@@ -1701,6 +1725,9 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1701
1725
  description: 'Execute ADB shell command on Android device',
1702
1726
  interfaceAlias: 'runAdbShell',
1703
1727
  paramSchema: runAdbShellParamSchema,
1728
+ sample: {
1729
+ command: 'dumpsys window displays | grep -E "mCurrentFocus"'
1730
+ },
1704
1731
  call: async (param)=>{
1705
1732
  if (!param.command || '' === param.command.trim()) throw new Error('RunAdbShell requires a non-empty command parameter');
1706
1733
  const adb = await device.getAdb();
@@ -1712,6 +1739,9 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1712
1739
  description: 'Launch an Android app or URL',
1713
1740
  interfaceAlias: 'launch',
1714
1741
  paramSchema: launchParamSchema,
1742
+ sample: {
1743
+ uri: 'com.example.app'
1744
+ },
1715
1745
  call: async (param)=>{
1716
1746
  if (!param.uri || '' === param.uri.trim()) throw new Error('Launch requires a non-empty uri parameter');
1717
1747
  await device.launch(param.uri);