@aiscene/android 1.6.7 → 1.6.9

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,66 +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
- const inputPromise = adb.shell(command);
954
- const timeoutPromise = new Promise((_, reject)=>setTimeout(()=>reject(new Error('YADB timeout')), 1000));
955
- await Promise.race([
956
- inputPromise,
957
- timeoutPromise
958
- ]);
959
- debugDevice(`YADB input completed: "${keyboardContent}"`);
960
- } catch (error) {
961
- const isAccessibilityConflict = error?.cause?.stderr?.includes('UiAutomationService') || error?.cause?.stderr?.includes('already registered') || error?.message?.includes('UiAutomationService');
962
- if (isAccessibilityConflict) {
963
- debugDevice("YADB failed due to AccessibilityService conflict (likely Appium running), falling back to clipboard method");
964
- await this.inputViaClipboard(keyboardContent);
965
- } else 'YADB timeout' === error.message ? debugDevice(`YADB timed out after 2s, assuming input succeeded: "${keyboardContent}"`) : debugDevice(`YADB execution may have completed despite error: ${error}`);
966
- }
967
- }
968
- async inputViaClipboard(text) {
969
- const adb = await this.getAdb();
970
- try {
971
- debugDevice(`Inputting via clipboard: "${text}"`);
972
- const escapedText = text.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
973
- const setClipboardCmd = `
974
- content insert --uri content://settings/system --bind name:s:clipboard_text --bind value:s:"${escapedText}"
975
- `;
976
- await adb.shell(setClipboardCmd);
977
- await (0, core_utils_namespaceObject.sleep)(100);
978
- await adb.shell('input keyevent KEYCODE_PASTE');
979
- await (0, core_utils_namespaceObject.sleep)(100);
980
- debugDevice(`Clipboard input completed via content provider: "${text}"`);
981
- } catch (error1) {
982
- debugDevice(`Content provider clipboard failed, trying clipper app: ${error1}`);
983
- try {
984
- const base64Text = Buffer.from(text, 'utf-8').toString('base64');
985
- await adb.shell(`am broadcast -a clipper.set -e text "${base64Text}"`);
986
- await (0, core_utils_namespaceObject.sleep)(100);
987
- await adb.shell('input keyevent KEYCODE_PASTE');
988
- await (0, core_utils_namespaceObject.sleep)(100);
989
- debugDevice(`Clipboard input completed via clipper: "${text}"`);
990
- } catch (error2) {
991
- debugDevice(`All clipboard methods failed: ${error2}`);
992
- const isPureAscii = /^[\x00-\x7F]*$/.test(text);
993
- if (isPureAscii) {
994
- debugDevice(`Using ADB inputText for ASCII text: "${text}"`);
995
- await adb.inputText(text);
996
- } else await this.inputCharByChar(text);
997
- }
998
- }
999
- }
1000
- async inputCharByChar(text) {
1001
- const adb = await this.getAdb();
1002
- debugDevice(`Inputting character by character (slow method): "${text}"`);
1003
- const chars = Array.from(text);
1004
- for (const char of chars){
1005
- if (' ' === char) await adb.shell('input keyevent KEYCODE_SPACE');
1006
- else await adb.shell(`input text "${char}"`);
1007
- await (0, core_utils_namespaceObject.sleep)(50);
1008
- }
1009
- 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}'`);
1010
994
  }
1011
995
  async getElementsInfo() {
1012
996
  return [];
@@ -1019,6 +1003,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1019
1003
  }
1020
1004
  async getScreenSize() {
1021
1005
  const shouldCache = !(this.options?.alwaysRefreshScreenInfo ?? false);
1006
+ debugDevice(`getScreenSize: alwaysRefreshScreenInfo=${this.options?.alwaysRefreshScreenInfo}, shouldCache=${shouldCache}, hasCachedSize=${!!this.cachedScreenSize}`);
1022
1007
  if (shouldCache && this.cachedScreenSize) return this.cachedScreenSize;
1023
1008
  const adb = await this.getAdb();
1024
1009
  if ('number' == typeof this.options?.displayId) try {
@@ -1152,6 +1137,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1152
1137
  }
1153
1138
  async getDisplayOrientation() {
1154
1139
  const shouldCache = !(this.options?.alwaysRefreshScreenInfo ?? false);
1140
+ debugDevice(`getDisplayOrientation: alwaysRefreshScreenInfo=${this.options?.alwaysRefreshScreenInfo}, shouldCache=${shouldCache}, hasCachedOrientation=${null !== this.cachedOrientation}`);
1155
1141
  if (shouldCache && null !== this.cachedOrientation) return this.cachedOrientation;
1156
1142
  const adb = await this.getAdb();
1157
1143
  let orientation = 0;
@@ -1177,6 +1163,15 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1177
1163
  if (shouldCache) this.cachedOrientation = orientation;
1178
1164
  return orientation;
1179
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
+ }
1180
1175
  async size() {
1181
1176
  const deviceInfo = await this.getDevicePhysicalInfo();
1182
1177
  const adapter = this.getScrcpyAdapter();
@@ -1203,7 +1198,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1203
1198
  height: logicalHeight
1204
1199
  };
1205
1200
  }
1206
- async cacheFeatureForPoint(center, options) {
1201
+ async cacheFeatureForPoint(center) {
1207
1202
  const { width, height } = await this.size();
1208
1203
  debugDevice('cacheFeatureForPoint: center=[%s,%s], screen=[%s,%s]', center[0], center[1], width, height);
1209
1204
  return {
@@ -1283,14 +1278,23 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1283
1278
  const androidScreenshotPath = `/data/local/tmp/ms_${screenshotId}.png`;
1284
1279
  const useShellScreencap = 'number' == typeof this.options?.displayId;
1285
1280
  try {
1286
- if (useShellScreencap) throw new Error('Using shell screencap for displayId');
1287
- debugDevice('Taking screenshot via adb.takeScreenshot');
1288
- screenshotBuffer = await adb.takeScreenshot(null);
1289
- debugDevice('adb.takeScreenshot completed');
1290
- if (!screenshotBuffer) throw new Error('Failed to capture screenshot: screenshotBuffer is null');
1291
- if (!(0, img_namespaceObject.isValidPNGImageBuffer)(screenshotBuffer)) {
1292
- debugDevice('Invalid image buffer detected: not a valid image format');
1293
- 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');
1294
1298
  }
1295
1299
  const validScreenshotBufferSize = this.options?.minScreenshotBufferSize ?? 10240;
1296
1300
  if (validScreenshotBufferSize > 0 && screenshotBuffer.length < validScreenshotBufferSize) {
@@ -1319,12 +1323,21 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1319
1323
  screenshotBuffer = await external_node_fs_default().promises.readFile(screenshotPath);
1320
1324
  const validScreenshotBufferSize = this.options?.minScreenshotBufferSize ?? 10240;
1321
1325
  if (!screenshotBuffer || validScreenshotBufferSize > 0 && screenshotBuffer.length < validScreenshotBufferSize) throw new Error(`Fallback screenshot validation failed: buffer size ${screenshotBuffer?.length || 0} bytes (minimum: ${validScreenshotBufferSize})`);
1322
- 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');
1323
1327
  debugDevice(`Fallback screenshot validated successfully: ${screenshotBuffer.length} bytes`);
1324
1328
  } finally{
1325
- Promise.resolve().then(()=>adb.shell(`rm ${androidScreenshotPath}`)).catch((error)=>{
1326
- 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);
1327
1339
  });
1340
+ child.unref();
1328
1341
  }
1329
1342
  }
1330
1343
  if (!screenshotBuffer) throw new Error('Failed to capture screenshot: all methods failed');
@@ -1487,24 +1500,32 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1487
1500
  if (!this.yadbPushed) {
1488
1501
  const adb = await this.getAdb();
1489
1502
  const androidPkgJson = (0, external_node_module_.createRequire)(__rslib_import_meta_url__).resolve('@aiscene/android/package.json');
1490
- const yadbDir = external_node_path_default().join(external_node_path_default().dirname(androidPkgJson), 'bin');
1491
- 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');
1492
1505
  this.yadbPushed = true;
1493
1506
  }
1494
1507
  }
1495
1508
  shouldUseYadbForText(text) {
1496
1509
  const hasNonAscii = /[\x80-\uFFFF]/.test(text);
1497
1510
  const hasFormatSpecifiers = /%[a-zA-Z]/.test(text);
1498
- return hasNonAscii || hasFormatSpecifiers;
1511
+ const hasShellSpecialChars = /[\\`$]/.test(text);
1512
+ const hasBothQuotes = text.includes('"') && text.includes("'");
1513
+ return hasNonAscii || hasFormatSpecifiers || hasShellSpecialChars || hasBothQuotes;
1499
1514
  }
1500
1515
  async keyboardType(text, options) {
1501
1516
  if (!text) return;
1502
1517
  const adb = await this.getAdb();
1503
- const shouldUseYadb = this.shouldUseYadbForText(text);
1504
1518
  const IME_STRATEGY = (this.options?.imeStrategy || env_namespaceObject.globalConfigManager.getEnvConfigValue(env_namespaceObject.MIDSCENE_ANDROID_IME_STRATEGY)) ?? IME_STRATEGY_YADB_FOR_NON_ASCII;
1505
1519
  const shouldAutoDismissKeyboard = options?.autoDismissKeyboard ?? this.options?.autoDismissKeyboard ?? true;
1506
- if (IME_STRATEGY === IME_STRATEGY_ALWAYS_YADB || IME_STRATEGY === IME_STRATEGY_YADB_FOR_NON_ASCII && shouldUseYadb) await this.execYadb(text);
1507
- 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
+ }
1508
1529
  if (true === shouldAutoDismissKeyboard) await this.hideKeyboard(options);
1509
1530
  }
1510
1531
  normalizeKeyName(key) {
@@ -1552,12 +1573,12 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1552
1573
  }
1553
1574
  async mouseClick(x, y) {
1554
1575
  const adb = await this.getAdb();
1555
- const { x: adjustedX, y: adjustedY } = this.adjustCoordinates(x, y);
1576
+ const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
1556
1577
  await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedX} ${adjustedY} ${adjustedX} ${adjustedY} 150`);
1557
1578
  }
1558
1579
  async mouseDoubleClick(x, y) {
1559
1580
  const adb = await this.getAdb();
1560
- const { x: adjustedX, y: adjustedY } = this.adjustCoordinates(x, y);
1581
+ const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
1561
1582
  const tapCommand = `input${this.getDisplayArg()} tap ${adjustedX} ${adjustedY}`;
1562
1583
  await adb.shell(tapCommand);
1563
1584
  await (0, core_utils_namespaceObject.sleep)(50);
@@ -1568,8 +1589,8 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1568
1589
  }
1569
1590
  async mouseDrag(from, to, duration) {
1570
1591
  const adb = await this.getAdb();
1571
- const { x: fromX, y: fromY } = this.adjustCoordinates(from.x, from.y);
1572
- 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);
1573
1594
  const swipeDuration = duration ?? defaultNormalScrollDuration;
1574
1595
  await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${swipeDuration}`);
1575
1596
  }
@@ -1579,16 +1600,16 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1579
1600
  const n = 4;
1580
1601
  const startX = Math.round(deltaX < 0 ? width / n * (n - 1) : width / n);
1581
1602
  const startY = Math.round(deltaY < 0 ? height / n * (n - 1) : height / n);
1582
- const maxNegativeDeltaX = startX;
1583
- const maxPositiveDeltaX = Math.round(width / n * (n - 1));
1584
- const maxNegativeDeltaY = startY;
1585
- 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;
1586
1607
  deltaX = Math.max(-maxNegativeDeltaX, Math.min(deltaX, maxPositiveDeltaX));
1587
1608
  deltaY = Math.max(-maxNegativeDeltaY, Math.min(deltaY, maxPositiveDeltaY));
1588
1609
  const endX = Math.round(startX - deltaX);
1589
1610
  const endY = Math.round(startY - deltaY);
1590
- const { x: adjustedStartX, y: adjustedStartY } = this.adjustCoordinates(startX, startY);
1591
- 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);
1592
1613
  const adb = await this.getAdb();
1593
1614
  const swipeDuration = duration ?? defaultNormalScrollDuration;
1594
1615
  await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedStartX} ${adjustedStartY} ${adjustedEndX} ${adjustedEndY} ${swipeDuration}`);
@@ -1599,6 +1620,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1599
1620
  this.cachedPhysicalDisplayId = void 0;
1600
1621
  this.cachedScreenSize = null;
1601
1622
  this.cachedOrientation = null;
1623
+ this.scalingRatio = 1;
1602
1624
  if (this.scrcpyAdapter) {
1603
1625
  await this.scrcpyAdapter.disconnect();
1604
1626
  this.scrcpyAdapter = null;
@@ -1638,7 +1660,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1638
1660
  }
1639
1661
  async longPress(x, y, duration = 2000) {
1640
1662
  const adb = await this.getAdb();
1641
- const { x: adjustedX, y: adjustedY } = this.adjustCoordinates(x, y);
1663
+ const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
1642
1664
  await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedX} ${adjustedY} ${adjustedX} ${adjustedY} ${duration}`);
1643
1665
  }
1644
1666
  async pullDown(startPoint, distance, duration = 800) {
@@ -1660,8 +1682,8 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1660
1682
  }
1661
1683
  async pullDrag(from, to, duration) {
1662
1684
  const adb = await this.getAdb();
1663
- const { x: fromX, y: fromY } = this.adjustCoordinates(from.x, from.y);
1664
- 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);
1665
1687
  await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${duration}`);
1666
1688
  }
1667
1689
  async pullUp(startPoint, distance, duration = 600) {
@@ -1748,7 +1770,6 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1748
1770
  device_define_property(this, "yadbPushed", false);
1749
1771
  device_define_property(this, "devicePixelRatio", 1);
1750
1772
  device_define_property(this, "devicePixelRatioInitialized", false);
1751
- device_define_property(this, "scalingRatio", 1);
1752
1773
  device_define_property(this, "adb", null);
1753
1774
  device_define_property(this, "connectingAdb", null);
1754
1775
  device_define_property(this, "destroyed", false);
@@ -1759,6 +1780,8 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1759
1780
  device_define_property(this, "cachedPhysicalDisplayId", void 0);
1760
1781
  device_define_property(this, "scrcpyAdapter", null);
1761
1782
  device_define_property(this, "appNameMapping", {});
1783
+ device_define_property(this, "scalingRatio", 1);
1784
+ device_define_property(this, "takeScreenshotFailCount", 0);
1762
1785
  device_define_property(this, "interfaceType", 'android');
1763
1786
  device_define_property(this, "uri", void 0);
1764
1787
  device_define_property(this, "options", void 0);
@@ -1768,6 +1791,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1768
1791
  this.customActions = options?.customActions;
1769
1792
  }
1770
1793
  }
1794
+ device_define_property(AndroidDevice, "TAKE_SCREENSHOT_FAIL_THRESHOLD", 3);
1771
1795
  const runAdbShellParamSchema = core_namespaceObject.z.object({
1772
1796
  command: core_namespaceObject.z.string().describe('ADB shell command to execute')
1773
1797
  });
@@ -1780,6 +1804,9 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1780
1804
  description: 'Execute ADB shell command on Android device',
1781
1805
  interfaceAlias: 'runAdbShell',
1782
1806
  paramSchema: runAdbShellParamSchema,
1807
+ sample: {
1808
+ command: 'dumpsys window displays | grep -E "mCurrentFocus"'
1809
+ },
1783
1810
  call: async (param)=>{
1784
1811
  if (!param.command || '' === param.command.trim()) throw new Error('RunAdbShell requires a non-empty command parameter');
1785
1812
  const adb = await device.getAdb();
@@ -1791,6 +1818,9 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1791
1818
  description: 'Launch an Android app or URL',
1792
1819
  interfaceAlias: 'launch',
1793
1820
  paramSchema: launchParamSchema,
1821
+ sample: {
1822
+ uri: 'com.example.app'
1823
+ },
1794
1824
  call: async (param)=>{
1795
1825
  if (!param.uri || '' === param.uri.trim()) throw new Error('Launch requires a non-empty uri parameter');
1796
1826
  await device.launch(param.uri);
@@ -1939,7 +1969,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1939
1969
  const tools = new AndroidMidsceneTools();
1940
1970
  (0, cli_namespaceObject.runToolsCLI)(tools, 'midscene-android', {
1941
1971
  stripPrefix: 'android_',
1942
- version: "1.6.7"
1972
+ version: "1.6.9"
1943
1973
  }).catch((e)=>{
1944
1974
  if (!(e instanceof cli_namespaceObject.CLIError)) console.error(e);
1945
1975
  process.exit(e instanceof cli_namespaceObject.CLIError ? e.exitCode : 1);