@aiscene/android 1.6.6 → 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/cli.js CHANGED
@@ -550,6 +550,7 @@ var __webpack_exports__ = {};
550
550
  };
551
551
  const external_node_assert_namespaceObject = require("node:assert");
552
552
  var external_node_assert_default = /*#__PURE__*/ __webpack_require__.n(external_node_assert_namespaceObject);
553
+ const external_node_child_process_namespaceObject = require("node:child_process");
553
554
  var external_node_fs_ = __webpack_require__("node:fs");
554
555
  var external_node_fs_default = /*#__PURE__*/ __webpack_require__.n(external_node_fs_);
555
556
  var external_node_module_ = __webpack_require__("node:module");
@@ -690,6 +691,9 @@ var __webpack_exports__ = {};
690
691
  const IME_STRATEGY_ALWAYS_YADB = 'always-yadb';
691
692
  const IME_STRATEGY_YADB_FOR_NON_ASCII = 'yadb-for-non-ascii';
692
693
  const debugDevice = (0, logger_.getDebug)('android:device');
694
+ function escapeForShell(text) {
695
+ return text.replace(/'/g, "'\\''").replace(/\n/g, '\\n');
696
+ }
693
697
  class AndroidDevice {
694
698
  actionSpace() {
695
699
  const defaultActions = [
@@ -717,6 +721,12 @@ var __webpack_exports__ = {};
717
721
  ]).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.')),
718
722
  locate: (0, core_namespaceObject.getMidsceneLocationSchema)().describe('The input field to be filled').optional()
719
723
  }),
724
+ sample: {
725
+ value: 'test@example.com',
726
+ locate: {
727
+ prompt: 'the email input field'
728
+ }
729
+ },
720
730
  call: async (param)=>{
721
731
  const element = param.locate;
722
732
  if ('typeOnly' !== param.mode) await this.clearInput(element);
@@ -762,9 +772,21 @@ var __webpack_exports__ = {};
762
772
  y: to.center[1]
763
773
  });
764
774
  }),
775
+ (0, device_namespaceObject.defineActionSwipe)(async (param)=>{
776
+ const { startPoint, endPoint, duration, repeatCount } = (0, device_namespaceObject.normalizeMobileSwipeParam)(param, await this.size());
777
+ for(let i = 0; i < repeatCount; i++)await this.mouseDrag(startPoint, endPoint, duration);
778
+ }),
765
779
  (0, device_namespaceObject.defineActionKeyboardPress)(async (param)=>{
766
780
  await this.keyboardPress(param.keyName);
767
781
  }),
782
+ (0, device_namespaceObject.defineActionCursorMove)(async (param)=>{
783
+ const arrowKey = 'left' === param.direction ? 'ArrowLeft' : 'ArrowRight';
784
+ const times = param.times ?? 1;
785
+ for(let i = 0; i < times; i++){
786
+ await this.keyboardPress(arrowKey);
787
+ await (0, core_utils_namespaceObject.sleep)(100);
788
+ }
789
+ }),
768
790
  (0, device_namespaceObject.defineAction)({
769
791
  name: 'LongPress',
770
792
  description: 'Trigger a long press on the screen at specified element',
@@ -772,6 +794,11 @@ var __webpack_exports__ = {};
772
794
  duration: core_namespaceObject.z.number().optional().describe('The duration of the long press in milliseconds'),
773
795
  locate: (0, core_namespaceObject.getMidsceneLocationSchema)().describe('The element to be long pressed')
774
796
  }),
797
+ sample: {
798
+ locate: {
799
+ prompt: 'the message bubble'
800
+ }
801
+ },
775
802
  call: async (param)=>{
776
803
  const element = param.locate;
777
804
  if (!element) throw new Error('LongPress requires an element to be located');
@@ -791,6 +818,12 @@ var __webpack_exports__ = {};
791
818
  duration: core_namespaceObject.z.number().optional().describe('The duration of the pull (in milliseconds)'),
792
819
  locate: (0, core_namespaceObject.getMidsceneLocationSchema)().optional().describe('The element to start the pull from (optional)')
793
820
  }),
821
+ sample: {
822
+ direction: 'down',
823
+ locate: {
824
+ prompt: 'the center of the content list area'
825
+ }
826
+ },
794
827
  call: async (param)=>{
795
828
  const element = param.locate;
796
829
  const startPoint = element ? {
@@ -803,6 +836,16 @@ var __webpack_exports__ = {};
803
836
  else throw new Error(`Unknown pull direction: ${param.direction}`);
804
837
  }
805
838
  }),
839
+ (0, device_namespaceObject.defineActionPinch)(async (param)=>{
840
+ const { centerX, centerY, startDistance, endDistance, duration } = (0, device_namespaceObject.normalizePinchParam)(param, await this.size());
841
+ const { x: adjCenterX, y: adjCenterY } = await this.adjustCoordinates(centerX, centerY);
842
+ const ratio = 0 !== adjCenterX && 0 !== centerX ? adjCenterX / centerX : 1;
843
+ const adjStartDist = Math.round(startDistance * ratio);
844
+ const adjEndDist = Math.round(endDistance * ratio);
845
+ await this.ensureYadb();
846
+ const adb = await this.getAdb();
847
+ 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}`);
848
+ }),
806
849
  (0, device_namespaceObject.defineActionClearInput)(async (param)=>{
807
850
  await this.clearInput(param.locate);
808
851
  })
@@ -947,61 +990,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
947
990
  async execYadb(keyboardContent) {
948
991
  await this.ensureYadb();
949
992
  const adb = await this.getAdb();
950
- try {
951
- const command = `app_process${this.getDisplayArg()} -Djava.class.path=/data/local/tmp/yadb /data/local/tmp com.ysbing.yadb.Main -keyboard "${keyboardContent}"`;
952
- debugDevice(`Executing YADB input: "${keyboardContent}"`);
953
- await adb.shell(command);
954
- debugDevice(`YADB input completed: "${keyboardContent}"`);
955
- } catch (error) {
956
- const isAccessibilityConflict = error?.cause?.stderr?.includes('UiAutomationService') || error?.cause?.stderr?.includes('already registered') || error?.message?.includes('UiAutomationService');
957
- if (isAccessibilityConflict) {
958
- debugDevice("YADB failed due to AccessibilityService conflict (likely Appium running), falling back to clipboard method");
959
- await this.inputViaClipboard(keyboardContent);
960
- } else debugDevice(`YADB execution may have completed despite error: ${error}`);
961
- }
962
- }
963
- async inputViaClipboard(text) {
964
- const adb = await this.getAdb();
965
- try {
966
- debugDevice(`Inputting via clipboard: "${text}"`);
967
- const escapedText = text.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
968
- const setClipboardCmd = `
969
- content insert --uri content://settings/system --bind name:s:clipboard_text --bind value:s:"${escapedText}"
970
- `;
971
- await adb.shell(setClipboardCmd);
972
- await (0, core_utils_namespaceObject.sleep)(100);
973
- await adb.shell('input keyevent KEYCODE_PASTE');
974
- await (0, core_utils_namespaceObject.sleep)(100);
975
- debugDevice(`Clipboard input completed via content provider: "${text}"`);
976
- } catch (error1) {
977
- debugDevice(`Content provider clipboard failed, trying clipper app: ${error1}`);
978
- try {
979
- const base64Text = Buffer.from(text, 'utf-8').toString('base64');
980
- await adb.shell(`am broadcast -a clipper.set -e text "${base64Text}"`);
981
- await (0, core_utils_namespaceObject.sleep)(100);
982
- await adb.shell('input keyevent KEYCODE_PASTE');
983
- await (0, core_utils_namespaceObject.sleep)(100);
984
- debugDevice(`Clipboard input completed via clipper: "${text}"`);
985
- } catch (error2) {
986
- debugDevice(`All clipboard methods failed: ${error2}`);
987
- const isPureAscii = /^[\x00-\x7F]*$/.test(text);
988
- if (isPureAscii) {
989
- debugDevice(`Using ADB inputText for ASCII text: "${text}"`);
990
- await adb.inputText(text);
991
- } else await this.inputCharByChar(text);
992
- }
993
- }
994
- }
995
- async inputCharByChar(text) {
996
- const adb = await this.getAdb();
997
- debugDevice(`Inputting character by character (slow method): "${text}"`);
998
- const chars = Array.from(text);
999
- for (const char of chars){
1000
- if (' ' === char) await adb.shell('input keyevent KEYCODE_SPACE');
1001
- else await adb.shell(`input text "${char}"`);
1002
- await (0, core_utils_namespaceObject.sleep)(50);
1003
- }
1004
- debugDevice("Character-by-character input completed");
993
+ await adb.shell(`app_process${this.getDisplayArg()} -Djava.class.path=/data/local/tmp/yadb /data/local/tmp com.ysbing.yadb.Main -keyboard '${keyboardContent}'`);
1005
994
  }
1006
995
  async getElementsInfo() {
1007
996
  return [];
@@ -1014,6 +1003,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1014
1003
  }
1015
1004
  async getScreenSize() {
1016
1005
  const shouldCache = !(this.options?.alwaysRefreshScreenInfo ?? false);
1006
+ debugDevice(`getScreenSize: alwaysRefreshScreenInfo=${this.options?.alwaysRefreshScreenInfo}, shouldCache=${shouldCache}, hasCachedSize=${!!this.cachedScreenSize}`);
1017
1007
  if (shouldCache && this.cachedScreenSize) return this.cachedScreenSize;
1018
1008
  const adb = await this.getAdb();
1019
1009
  if ('number' == typeof this.options?.displayId) try {
@@ -1147,6 +1137,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1147
1137
  }
1148
1138
  async getDisplayOrientation() {
1149
1139
  const shouldCache = !(this.options?.alwaysRefreshScreenInfo ?? false);
1140
+ debugDevice(`getDisplayOrientation: alwaysRefreshScreenInfo=${this.options?.alwaysRefreshScreenInfo}, shouldCache=${shouldCache}, hasCachedOrientation=${null !== this.cachedOrientation}`);
1150
1141
  if (shouldCache && null !== this.cachedOrientation) return this.cachedOrientation;
1151
1142
  const adb = await this.getAdb();
1152
1143
  let orientation = 0;
@@ -1172,6 +1163,15 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1172
1163
  if (shouldCache) this.cachedOrientation = orientation;
1173
1164
  return orientation;
1174
1165
  }
1166
+ async getOrientedPhysicalSize() {
1167
+ const info = await this.getDevicePhysicalInfo();
1168
+ const isLandscape = 1 === info.orientation || 3 === info.orientation;
1169
+ const shouldSwap = true !== info.isCurrentOrientation && isLandscape;
1170
+ return {
1171
+ width: shouldSwap ? info.physicalHeight : info.physicalWidth,
1172
+ height: shouldSwap ? info.physicalWidth : info.physicalHeight
1173
+ };
1174
+ }
1175
1175
  async size() {
1176
1176
  const deviceInfo = await this.getDevicePhysicalInfo();
1177
1177
  const adapter = this.getScrcpyAdapter();
@@ -1198,7 +1198,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1198
1198
  height: logicalHeight
1199
1199
  };
1200
1200
  }
1201
- async cacheFeatureForPoint(center, options) {
1201
+ async cacheFeatureForPoint(center) {
1202
1202
  const { width, height } = await this.size();
1203
1203
  debugDevice('cacheFeatureForPoint: center=[%s,%s], screen=[%s,%s]', center[0], center[1], width, height);
1204
1204
  return {
@@ -1278,14 +1278,23 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1278
1278
  const androidScreenshotPath = `/data/local/tmp/ms_${screenshotId}.png`;
1279
1279
  const useShellScreencap = 'number' == typeof this.options?.displayId;
1280
1280
  try {
1281
- if (useShellScreencap) throw new Error('Using shell screencap for displayId');
1282
- debugDevice('Taking screenshot via adb.takeScreenshot');
1283
- screenshotBuffer = await adb.takeScreenshot(null);
1284
- debugDevice('adb.takeScreenshot completed');
1285
- if (!screenshotBuffer) throw new Error('Failed to capture screenshot: screenshotBuffer is null');
1286
- if (!(0, img_namespaceObject.isValidPNGImageBuffer)(screenshotBuffer)) {
1287
- debugDevice('Invalid image buffer detected: not a valid image format');
1288
- throw new Error('Screenshot buffer has invalid format: could not find valid image signature');
1281
+ if (!useShellScreencap && this.takeScreenshotFailCount < AndroidDevice.TAKE_SCREENSHOT_FAIL_THRESHOLD) {
1282
+ debugDevice('Taking screenshot via adb.takeScreenshot');
1283
+ screenshotBuffer = await adb.takeScreenshot(null);
1284
+ debugDevice('adb.takeScreenshot completed');
1285
+ if (!screenshotBuffer) {
1286
+ this.takeScreenshotFailCount++;
1287
+ throw new Error('Failed to capture screenshot: screenshotBuffer is null');
1288
+ }
1289
+ if (!(0, img_namespaceObject.isValidImageBuffer)(screenshotBuffer)) {
1290
+ debugDevice('Invalid image buffer detected: not a valid image format');
1291
+ this.takeScreenshotFailCount++;
1292
+ throw new Error('Screenshot buffer has invalid format: could not find valid image signature');
1293
+ }
1294
+ this.takeScreenshotFailCount = 0;
1295
+ } else {
1296
+ if (this.takeScreenshotFailCount >= AndroidDevice.TAKE_SCREENSHOT_FAIL_THRESHOLD) debugDevice('Skipping takeScreenshot (failed %d consecutive times), using shell screencap directly', this.takeScreenshotFailCount);
1297
+ throw new Error('Using shell screencap directly');
1289
1298
  }
1290
1299
  const validScreenshotBufferSize = this.options?.minScreenshotBufferSize ?? 10240;
1291
1300
  if (validScreenshotBufferSize > 0 && screenshotBuffer.length < validScreenshotBufferSize) {
@@ -1314,12 +1323,21 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1314
1323
  screenshotBuffer = await external_node_fs_default().promises.readFile(screenshotPath);
1315
1324
  const validScreenshotBufferSize = this.options?.minScreenshotBufferSize ?? 10240;
1316
1325
  if (!screenshotBuffer || validScreenshotBufferSize > 0 && screenshotBuffer.length < validScreenshotBufferSize) throw new Error(`Fallback screenshot validation failed: buffer size ${screenshotBuffer?.length || 0} bytes (minimum: ${validScreenshotBufferSize})`);
1317
- if (!(0, img_namespaceObject.isValidPNGImageBuffer)(screenshotBuffer)) throw new Error('Fallback screenshot buffer has invalid PNG format');
1326
+ if (!(0, img_namespaceObject.isValidImageBuffer)(screenshotBuffer)) throw new Error('Fallback screenshot buffer has invalid PNG format');
1318
1327
  debugDevice(`Fallback screenshot validated successfully: ${screenshotBuffer.length} bytes`);
1319
1328
  } finally{
1320
- Promise.resolve().then(()=>adb.shell(`rm ${androidScreenshotPath}`)).catch((error)=>{
1321
- debugDevice(`Failed to delete remote screenshot: ${error}`);
1329
+ const adbPath = adb.executable?.path ?? 'adb';
1330
+ const child = (0, external_node_child_process_namespaceObject.execFile)(adbPath, [
1331
+ '-s',
1332
+ this.deviceId,
1333
+ 'shell',
1334
+ `rm ${androidScreenshotPath}`
1335
+ ], {
1336
+ timeout: 3000
1337
+ }, (err)=>{
1338
+ if (err) debugDevice('Failed to delete remote screenshot: %s', err.message);
1322
1339
  });
1340
+ child.unref();
1323
1341
  }
1324
1342
  }
1325
1343
  if (!screenshotBuffer) throw new Error('Failed to capture screenshot: all methods failed');
@@ -1482,24 +1500,32 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1482
1500
  if (!this.yadbPushed) {
1483
1501
  const adb = await this.getAdb();
1484
1502
  const androidPkgJson = (0, external_node_module_.createRequire)(__rslib_import_meta_url__).resolve('@aiscene/android/package.json');
1485
- const yadbDir = external_node_path_default().join(external_node_path_default().dirname(androidPkgJson), 'bin');
1486
- await adb.push(yadbDir, '/data/local/tmp');
1503
+ const yadbBin = external_node_path_default().join(external_node_path_default().dirname(androidPkgJson), 'bin', 'yadb');
1504
+ await adb.push(yadbBin, '/data/local/tmp');
1487
1505
  this.yadbPushed = true;
1488
1506
  }
1489
1507
  }
1490
1508
  shouldUseYadbForText(text) {
1491
1509
  const hasNonAscii = /[\x80-\uFFFF]/.test(text);
1492
1510
  const hasFormatSpecifiers = /%[a-zA-Z]/.test(text);
1493
- return hasNonAscii || hasFormatSpecifiers;
1511
+ const hasShellSpecialChars = /[\\`$]/.test(text);
1512
+ const hasBothQuotes = text.includes('"') && text.includes("'");
1513
+ return hasNonAscii || hasFormatSpecifiers || hasShellSpecialChars || hasBothQuotes;
1494
1514
  }
1495
1515
  async keyboardType(text, options) {
1496
1516
  if (!text) return;
1497
1517
  const adb = await this.getAdb();
1498
- const shouldUseYadb = this.shouldUseYadbForText(text);
1499
1518
  const IME_STRATEGY = (this.options?.imeStrategy || env_namespaceObject.globalConfigManager.getEnvConfigValue(env_namespaceObject.MIDSCENE_ANDROID_IME_STRATEGY)) ?? IME_STRATEGY_YADB_FOR_NON_ASCII;
1500
1519
  const shouldAutoDismissKeyboard = options?.autoDismissKeyboard ?? this.options?.autoDismissKeyboard ?? true;
1501
- if (IME_STRATEGY === IME_STRATEGY_ALWAYS_YADB || IME_STRATEGY === IME_STRATEGY_YADB_FOR_NON_ASCII && shouldUseYadb) await this.execYadb(text);
1502
- else await adb.inputText(text);
1520
+ const useYadb = IME_STRATEGY === IME_STRATEGY_ALWAYS_YADB || IME_STRATEGY === IME_STRATEGY_YADB_FOR_NON_ASCII && this.shouldUseYadbForText(text);
1521
+ if (useYadb) await this.execYadb(escapeForShell(text));
1522
+ else {
1523
+ const segments = text.split('\n');
1524
+ for(let i = 0; i < segments.length; i++){
1525
+ if (segments[i].length > 0) await adb.inputText(segments[i]);
1526
+ if (i < segments.length - 1) await adb.keyevent(66);
1527
+ }
1528
+ }
1503
1529
  if (true === shouldAutoDismissKeyboard) await this.hideKeyboard(options);
1504
1530
  }
1505
1531
  normalizeKeyName(key) {
@@ -1547,12 +1573,12 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1547
1573
  }
1548
1574
  async mouseClick(x, y) {
1549
1575
  const adb = await this.getAdb();
1550
- const { x: adjustedX, y: adjustedY } = this.adjustCoordinates(x, y);
1576
+ const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
1551
1577
  await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedX} ${adjustedY} ${adjustedX} ${adjustedY} 150`);
1552
1578
  }
1553
1579
  async mouseDoubleClick(x, y) {
1554
1580
  const adb = await this.getAdb();
1555
- const { x: adjustedX, y: adjustedY } = this.adjustCoordinates(x, y);
1581
+ const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
1556
1582
  const tapCommand = `input${this.getDisplayArg()} tap ${adjustedX} ${adjustedY}`;
1557
1583
  await adb.shell(tapCommand);
1558
1584
  await (0, core_utils_namespaceObject.sleep)(50);
@@ -1563,8 +1589,8 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1563
1589
  }
1564
1590
  async mouseDrag(from, to, duration) {
1565
1591
  const adb = await this.getAdb();
1566
- const { x: fromX, y: fromY } = this.adjustCoordinates(from.x, from.y);
1567
- const { x: toX, y: toY } = this.adjustCoordinates(to.x, to.y);
1592
+ const { x: fromX, y: fromY } = await this.adjustCoordinates(from.x, from.y);
1593
+ const { x: toX, y: toY } = await this.adjustCoordinates(to.x, to.y);
1568
1594
  const swipeDuration = duration ?? defaultNormalScrollDuration;
1569
1595
  await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${swipeDuration}`);
1570
1596
  }
@@ -1574,16 +1600,16 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1574
1600
  const n = 4;
1575
1601
  const startX = Math.round(deltaX < 0 ? width / n * (n - 1) : width / n);
1576
1602
  const startY = Math.round(deltaY < 0 ? height / n * (n - 1) : height / n);
1577
- const maxNegativeDeltaX = startX;
1578
- const maxPositiveDeltaX = Math.round(width / n * (n - 1));
1579
- const maxNegativeDeltaY = startY;
1580
- const maxPositiveDeltaY = Math.round(height / n * (n - 1));
1603
+ const maxPositiveDeltaX = startX;
1604
+ const maxNegativeDeltaX = width - startX;
1605
+ const maxPositiveDeltaY = startY;
1606
+ const maxNegativeDeltaY = height - startY;
1581
1607
  deltaX = Math.max(-maxNegativeDeltaX, Math.min(deltaX, maxPositiveDeltaX));
1582
1608
  deltaY = Math.max(-maxNegativeDeltaY, Math.min(deltaY, maxPositiveDeltaY));
1583
1609
  const endX = Math.round(startX - deltaX);
1584
1610
  const endY = Math.round(startY - deltaY);
1585
- const { x: adjustedStartX, y: adjustedStartY } = this.adjustCoordinates(startX, startY);
1586
- const { x: adjustedEndX, y: adjustedEndY } = this.adjustCoordinates(endX, endY);
1611
+ const { x: adjustedStartX, y: adjustedStartY } = await this.adjustCoordinates(startX, startY);
1612
+ const { x: adjustedEndX, y: adjustedEndY } = await this.adjustCoordinates(endX, endY);
1587
1613
  const adb = await this.getAdb();
1588
1614
  const swipeDuration = duration ?? defaultNormalScrollDuration;
1589
1615
  await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedStartX} ${adjustedStartY} ${adjustedEndX} ${adjustedEndY} ${swipeDuration}`);
@@ -1594,6 +1620,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1594
1620
  this.cachedPhysicalDisplayId = void 0;
1595
1621
  this.cachedScreenSize = null;
1596
1622
  this.cachedOrientation = null;
1623
+ this.scalingRatio = 1;
1597
1624
  if (this.scrcpyAdapter) {
1598
1625
  await this.scrcpyAdapter.disconnect();
1599
1626
  this.scrcpyAdapter = null;
@@ -1633,7 +1660,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1633
1660
  }
1634
1661
  async longPress(x, y, duration = 2000) {
1635
1662
  const adb = await this.getAdb();
1636
- const { x: adjustedX, y: adjustedY } = this.adjustCoordinates(x, y);
1663
+ const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
1637
1664
  await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedX} ${adjustedY} ${adjustedX} ${adjustedY} ${duration}`);
1638
1665
  }
1639
1666
  async pullDown(startPoint, distance, duration = 800) {
@@ -1655,8 +1682,8 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1655
1682
  }
1656
1683
  async pullDrag(from, to, duration) {
1657
1684
  const adb = await this.getAdb();
1658
- const { x: fromX, y: fromY } = this.adjustCoordinates(from.x, from.y);
1659
- const { x: toX, y: toY } = this.adjustCoordinates(to.x, to.y);
1685
+ const { x: fromX, y: fromY } = await this.adjustCoordinates(from.x, from.y);
1686
+ const { x: toX, y: toY } = await this.adjustCoordinates(to.x, to.y);
1660
1687
  await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${duration}`);
1661
1688
  }
1662
1689
  async pullUp(startPoint, distance, duration = 600) {
@@ -1743,7 +1770,6 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1743
1770
  device_define_property(this, "yadbPushed", false);
1744
1771
  device_define_property(this, "devicePixelRatio", 1);
1745
1772
  device_define_property(this, "devicePixelRatioInitialized", false);
1746
- device_define_property(this, "scalingRatio", 1);
1747
1773
  device_define_property(this, "adb", null);
1748
1774
  device_define_property(this, "connectingAdb", null);
1749
1775
  device_define_property(this, "destroyed", false);
@@ -1754,6 +1780,8 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1754
1780
  device_define_property(this, "cachedPhysicalDisplayId", void 0);
1755
1781
  device_define_property(this, "scrcpyAdapter", null);
1756
1782
  device_define_property(this, "appNameMapping", {});
1783
+ device_define_property(this, "scalingRatio", 1);
1784
+ device_define_property(this, "takeScreenshotFailCount", 0);
1757
1785
  device_define_property(this, "interfaceType", 'android');
1758
1786
  device_define_property(this, "uri", void 0);
1759
1787
  device_define_property(this, "options", void 0);
@@ -1763,6 +1791,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1763
1791
  this.customActions = options?.customActions;
1764
1792
  }
1765
1793
  }
1794
+ device_define_property(AndroidDevice, "TAKE_SCREENSHOT_FAIL_THRESHOLD", 3);
1766
1795
  const runAdbShellParamSchema = core_namespaceObject.z.object({
1767
1796
  command: core_namespaceObject.z.string().describe('ADB shell command to execute')
1768
1797
  });
@@ -1775,6 +1804,9 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1775
1804
  description: 'Execute ADB shell command on Android device',
1776
1805
  interfaceAlias: 'runAdbShell',
1777
1806
  paramSchema: runAdbShellParamSchema,
1807
+ sample: {
1808
+ command: 'dumpsys window displays | grep -E "mCurrentFocus"'
1809
+ },
1778
1810
  call: async (param)=>{
1779
1811
  if (!param.command || '' === param.command.trim()) throw new Error('RunAdbShell requires a non-empty command parameter');
1780
1812
  const adb = await device.getAdb();
@@ -1786,6 +1818,9 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1786
1818
  description: 'Launch an Android app or URL',
1787
1819
  interfaceAlias: 'launch',
1788
1820
  paramSchema: launchParamSchema,
1821
+ sample: {
1822
+ uri: 'com.example.app'
1823
+ },
1789
1824
  call: async (param)=>{
1790
1825
  if (!param.uri || '' === param.uri.trim()) throw new Error('Launch requires a non-empty uri parameter');
1791
1826
  await device.launch(param.uri);
@@ -1934,7 +1969,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1934
1969
  const tools = new AndroidMidsceneTools();
1935
1970
  (0, cli_namespaceObject.runToolsCLI)(tools, 'midscene-android', {
1936
1971
  stripPrefix: 'android_',
1937
- version: "1.6.6"
1972
+ version: "1.6.8"
1938
1973
  }).catch((e)=>{
1939
1974
  if (!(e instanceof cli_namespaceObject.CLIError)) console.error(e);
1940
1975
  process.exit(e instanceof cli_namespaceObject.CLIError ? e.exitCode : 1);