@midscene/android 1.8.0 → 1.8.1

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.
@@ -8,7 +8,7 @@ import { mergeAndNormalizeAppNameMapping, normalizeForComparison, repeat } from
8
8
  import node_assert from "node:assert";
9
9
  import { execFile } from "node:child_process";
10
10
  import { getMidsceneLocationSchema, z } from "@midscene/core";
11
- import { defineAction, defineActionClearInput, defineActionCursorMove, defineActionDoubleClick, defineActionDragAndDrop, defineActionKeyboardPress, defineActionLongPress, defineActionPinch, defineActionScroll, defineActionSwipe, defineActionTap, normalizeMobileSwipeParam, normalizePinchParam } from "@midscene/core/device";
11
+ import { createDefaultMobileActions, defineAction } from "@midscene/core/device";
12
12
  import { getTmpFile, sleep } from "@midscene/core/utils";
13
13
  import { MIDSCENE_ADB_PATH, MIDSCENE_ADB_REMOTE_HOST, MIDSCENE_ADB_REMOTE_PORT, MIDSCENE_ANDROID_IME_STRATEGY, globalConfigManager } from "@midscene/shared/env";
14
14
  import { createImgBase64ByFormat, validateScreenshotBuffer } from "@midscene/shared/img";
@@ -684,103 +684,30 @@ function escapeForShell(text) {
684
684
  }
685
685
  class AndroidDevice {
686
686
  actionSpace() {
687
- const defaultActions = [
688
- defineActionTap(async (param)=>{
689
- const element = param.locate;
690
- node_assert(element, 'Element not found, cannot tap');
691
- await this.mouseClick(element.center[0], element.center[1]);
692
- }),
693
- defineActionDoubleClick(async (param)=>{
694
- const element = param.locate;
695
- node_assert(element, 'Element not found, cannot double click');
696
- await this.mouseDoubleClick(element.center[0], element.center[1]);
697
- }),
698
- defineAction({
699
- name: 'Input',
700
- description: 'Input text into the input field',
701
- interfaceAlias: 'aiInput',
702
- paramSchema: z.object({
703
- value: z.string().describe('The text to input. Provide the final content for replace/append modes, or an empty string when using clear mode to remove existing text.'),
704
- autoDismissKeyboard: z.boolean().optional().describe('If true, the keyboard will be dismissed after the input is completed. Do not set it unless the user asks you to do so.'),
705
- mode: z.preprocess((val)=>'append' === val ? 'typeOnly' : val, z["enum"]([
706
- 'replace',
707
- 'clear',
708
- 'typeOnly'
709
- ]).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.')),
710
- locate: getMidsceneLocationSchema().describe('The input field to be filled').optional()
711
- }),
712
- sample: {
713
- value: 'test@example.com',
714
- locate: {
715
- prompt: 'the email input field'
716
- }
687
+ const mobileActionContext = {
688
+ input: this.inputPrimitives,
689
+ size: ()=>this.size(),
690
+ sleep: async (timeMs)=>{
691
+ await sleep(timeMs);
692
+ },
693
+ getDefaultAutoDismissKeyboard: ()=>this.options?.autoDismissKeyboard,
694
+ systemActions: {
695
+ backButton: {
696
+ name: 'AndroidBackButton',
697
+ description: 'Trigger the system "back" operation on Android devices'
717
698
  },
718
- call: async (param)=>{
719
- const element = param.locate;
720
- if ('typeOnly' !== param.mode) await this.clearInput(element);
721
- if ('clear' === param.mode) return;
722
- if (!param || !param.value) return;
723
- const autoDismissKeyboard = param.autoDismissKeyboard ?? this.options?.autoDismissKeyboard;
724
- await this.keyboardType(param.value, {
725
- autoDismissKeyboard
726
- });
727
- }
728
- }),
729
- defineActionScroll(async (param)=>{
730
- const element = param.locate;
731
- const startingPoint = element ? {
732
- left: element.center[0],
733
- top: element.center[1]
734
- } : void 0;
735
- const scrollToEventName = param?.scrollType;
736
- if ('scrollToTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
737
- else if ('scrollToBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
738
- else if ('scrollToRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
739
- else if ('scrollToLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
740
- else if ('singleAction' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
741
- else {
742
- if (param?.direction !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance || void 0, startingPoint);
743
- else if ('left' === param.direction) await this.scrollLeft(param.distance || void 0, startingPoint);
744
- else if ('right' === param.direction) await this.scrollRight(param.distance || void 0, startingPoint);
745
- else throw new Error(`Unknown scroll direction: ${param.direction}`);
746
- else await this.scrollDown(param?.distance || void 0, startingPoint);
747
- await sleep(500);
748
- }
749
- }),
750
- defineActionDragAndDrop(async (param)=>{
751
- const from = param.from;
752
- const to = param.to;
753
- node_assert(from, 'missing "from" param for drag and drop');
754
- node_assert(to, 'missing "to" param for drag and drop');
755
- await this.mouseDrag({
756
- x: from.center[0],
757
- y: from.center[1]
758
- }, {
759
- x: to.center[0],
760
- y: to.center[1]
761
- });
762
- }),
763
- defineActionSwipe(async (param)=>{
764
- const { startPoint, endPoint, duration, repeatCount } = normalizeMobileSwipeParam(param, await this.size());
765
- for(let i = 0; i < repeatCount; i++)await this.mouseDrag(startPoint, endPoint, duration);
766
- }),
767
- defineActionKeyboardPress(async (param)=>{
768
- await this.keyboardPress(param.keyName);
769
- }),
770
- defineActionCursorMove(async (param)=>{
771
- const arrowKey = 'left' === param.direction ? 'ArrowLeft' : 'ArrowRight';
772
- const times = param.times ?? 1;
773
- for(let i = 0; i < times; i++){
774
- await this.keyboardPress(arrowKey);
775
- await sleep(100);
699
+ homeButton: {
700
+ name: 'AndroidHomeButton',
701
+ description: 'Trigger the system "home" operation on Android devices'
702
+ },
703
+ recentAppsButton: {
704
+ name: 'AndroidRecentAppsButton',
705
+ description: 'Trigger the system "recent apps" operation on Android devices'
776
706
  }
777
- }),
778
- defineActionLongPress(async (param)=>{
779
- const element = param.locate;
780
- if (!element) throw new Error('LongPress requires an element to be located');
781
- const [x, y] = element.center;
782
- await this.longPress(x, y, param?.duration);
783
- }),
707
+ }
708
+ };
709
+ const defaultActions = [
710
+ ...createDefaultMobileActions(mobileActionContext),
784
711
  defineAction({
785
712
  name: 'PullGesture',
786
713
  description: 'Trigger pull down to refresh or pull up actions',
@@ -810,19 +737,6 @@ class AndroidDevice {
810
737
  else if ('up' === param.direction) await this.pullUp(startPoint, param.distance, param.duration);
811
738
  else throw new Error(`Unknown pull direction: ${param.direction}`);
812
739
  }
813
- }),
814
- defineActionPinch(async (param)=>{
815
- const { centerX, centerY, startDistance, endDistance, duration } = normalizePinchParam(param, await this.size());
816
- const { x: adjCenterX, y: adjCenterY } = await this.adjustCoordinates(centerX, centerY);
817
- const ratio = 0 !== adjCenterX && 0 !== centerX ? adjCenterX / centerX : 1;
818
- const adjStartDist = Math.round(startDistance * ratio);
819
- const adjEndDist = Math.round(endDistance * ratio);
820
- await this.ensureYadb();
821
- const adb = await this.getAdb();
822
- 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}`);
823
- }),
824
- defineActionClearInput(async (param)=>{
825
- await this.clearInput(param.locate);
826
740
  })
827
741
  ];
828
742
  const platformSpecificActions = Object.values(createPlatformActions(this));
@@ -833,6 +747,27 @@ class AndroidDevice {
833
747
  ...customActions
834
748
  ];
835
749
  }
750
+ async performActionScroll(param) {
751
+ const element = param.locate;
752
+ const startingPoint = element ? {
753
+ left: element.center[0],
754
+ top: element.center[1]
755
+ } : void 0;
756
+ const scrollToEventName = param?.scrollType;
757
+ if ('scrollToTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
758
+ else if ('scrollToBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
759
+ else if ('scrollToRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
760
+ else if ('scrollToLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
761
+ else if ('singleAction' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
762
+ else {
763
+ if (param?.direction !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance || void 0, startingPoint);
764
+ else if ('left' === param.direction) await this.scrollLeft(param.distance || void 0, startingPoint);
765
+ else if ('right' === param.direction) await this.scrollRight(param.distance || void 0, startingPoint);
766
+ else throw new Error(`Unknown scroll direction: ${param.direction}`);
767
+ else await this.scrollDown(param?.distance || void 0, startingPoint);
768
+ await sleep(500);
769
+ }
770
+ }
836
771
  describe() {
837
772
  return this.description || `DeviceId: ${this.deviceId}`;
838
773
  }
@@ -1320,14 +1255,20 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1320
1255
  return result;
1321
1256
  }
1322
1257
  async clearInput(element) {
1323
- if (element) await this.mouseClick(element.center[0], element.center[1]);
1258
+ if (element) await this.tapPoint({
1259
+ x: element.center[0],
1260
+ y: element.center[1]
1261
+ });
1324
1262
  await this.ensureYadb();
1325
1263
  const adb = await this.getAdb();
1326
1264
  const IME_STRATEGY = (this.options?.imeStrategy || globalConfigManager.getEnvConfigValue(MIDSCENE_ANDROID_IME_STRATEGY)) ?? IME_STRATEGY_YADB_FOR_NON_ASCII;
1327
1265
  if (IME_STRATEGY === IME_STRATEGY_YADB_FOR_NON_ASCII) await adb.clearTextField(100);
1328
1266
  else await adb.shell(`app_process${this.getDisplayArg()} -Djava.class.path=/data/local/tmp/yadb /data/local/tmp com.ysbing.yadb.Main -keyboardClear`);
1329
1267
  if (await adb.isSoftKeyboardPresent()) return;
1330
- if (element) await this.mouseClick(element.center[0], element.center[1]);
1268
+ if (element) await this.tapPoint({
1269
+ x: element.center[0],
1270
+ y: element.center[1]
1271
+ });
1331
1272
  }
1332
1273
  async forceScreenshot(path) {
1333
1274
  await this.ensureYadb();
@@ -1348,7 +1289,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1348
1289
  x: start.x,
1349
1290
  y: Math.round(height)
1350
1291
  };
1351
- await repeat(defaultScrollUntilTimes, ()=>this.mouseDrag(start, end, defaultFastScrollDuration));
1292
+ await repeat(defaultScrollUntilTimes, ()=>this.dragPoint(start, end, defaultFastScrollDuration));
1352
1293
  await sleep(1000);
1353
1294
  return;
1354
1295
  }
@@ -1365,7 +1306,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1365
1306
  x: start.x,
1366
1307
  y: 0
1367
1308
  };
1368
- await repeat(defaultScrollUntilTimes, ()=>this.mouseDrag(start, end, defaultFastScrollDuration));
1309
+ await repeat(defaultScrollUntilTimes, ()=>this.dragPoint(start, end, defaultFastScrollDuration));
1369
1310
  await sleep(1000);
1370
1311
  return;
1371
1312
  }
@@ -1383,7 +1324,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1383
1324
  x: Math.round(width),
1384
1325
  y: start.y
1385
1326
  };
1386
- await repeat(defaultScrollUntilTimes, ()=>this.mouseDrag(start, end, defaultFastScrollDuration));
1327
+ await repeat(defaultScrollUntilTimes, ()=>this.dragPoint(start, end, defaultFastScrollDuration));
1387
1328
  await sleep(1000);
1388
1329
  return;
1389
1330
  }
@@ -1400,7 +1341,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1400
1341
  x: 0,
1401
1342
  y: start.y
1402
1343
  };
1403
- await repeat(defaultScrollUntilTimes, ()=>this.mouseDrag(start, end, defaultFastScrollDuration));
1344
+ await repeat(defaultScrollUntilTimes, ()=>this.dragPoint(start, end, defaultFastScrollDuration));
1404
1345
  await sleep(1000);
1405
1346
  return;
1406
1347
  }
@@ -1418,7 +1359,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1418
1359
  };
1419
1360
  const end = this.calculateScrollEndPoint(start, 0, scrollDistance, 0, height);
1420
1361
  if (hasExplicitDistance) this.warnScrollDistanceClamped('up', scrollDistance, Math.abs(end.y - start.y));
1421
- await this.mouseDrag(start, end);
1362
+ await this.dragPoint(start, end);
1422
1363
  return;
1423
1364
  }
1424
1365
  await this.scroll(0, -scrollDistance, void 0, hasExplicitDistance, 'up');
@@ -1434,7 +1375,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1434
1375
  };
1435
1376
  const end = this.calculateScrollEndPoint(start, 0, -scrollDistance, 0, height);
1436
1377
  if (hasExplicitDistance) this.warnScrollDistanceClamped('down', scrollDistance, Math.abs(end.y - start.y));
1437
- await this.mouseDrag(start, end);
1378
+ await this.dragPoint(start, end);
1438
1379
  return;
1439
1380
  }
1440
1381
  await this.scroll(0, scrollDistance, void 0, hasExplicitDistance, 'down');
@@ -1450,7 +1391,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1450
1391
  };
1451
1392
  const end = this.calculateScrollEndPoint(start, scrollDistance, 0, width, 0);
1452
1393
  if (hasExplicitDistance) this.warnScrollDistanceClamped('left', scrollDistance, Math.abs(end.x - start.x));
1453
- await this.mouseDrag(start, end);
1394
+ await this.dragPoint(start, end);
1454
1395
  return;
1455
1396
  }
1456
1397
  await this.scroll(-scrollDistance, 0, void 0, hasExplicitDistance, 'left');
@@ -1466,7 +1407,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1466
1407
  };
1467
1408
  const end = this.calculateScrollEndPoint(start, -scrollDistance, 0, width, 0);
1468
1409
  if (hasExplicitDistance) this.warnScrollDistanceClamped('right', scrollDistance, Math.abs(end.x - start.x));
1469
- await this.mouseDrag(start, end);
1410
+ await this.dragPoint(start, end);
1470
1411
  return;
1471
1412
  }
1472
1413
  await this.scroll(scrollDistance, 0, void 0, hasExplicitDistance, 'right');
@@ -1487,7 +1428,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1487
1428
  const hasBothQuotes = text.includes('"') && text.includes("'");
1488
1429
  return hasNonAscii || hasFormatSpecifiers || hasShellSpecialChars || hasBothQuotes;
1489
1430
  }
1490
- async keyboardType(text, options) {
1431
+ async typeText(text, options) {
1491
1432
  if (!text) return;
1492
1433
  const adb = await this.getAdb();
1493
1434
  const IME_STRATEGY = (this.options?.imeStrategy || globalConfigManager.getEnvConfigValue(MIDSCENE_ANDROID_IME_STRATEGY)) ?? IME_STRATEGY_YADB_FOR_NON_ASCII;
@@ -1524,7 +1465,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1524
1465
  const lowerKey = key.toLowerCase();
1525
1466
  return keyMap[lowerKey] || key;
1526
1467
  }
1527
- async keyboardPress(key) {
1468
+ async pressKey(key) {
1528
1469
  const keyCodeMap = {
1529
1470
  Enter: 66,
1530
1471
  Backspace: 67,
@@ -1546,14 +1487,14 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1546
1487
  if (asciiCode >= 65 && asciiCode <= 90) await adb.keyevent(asciiCode - 36);
1547
1488
  }
1548
1489
  }
1549
- async mouseClick(x, y) {
1490
+ async tapPoint(point) {
1550
1491
  const adb = await this.getAdb();
1551
- const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
1492
+ const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(point.x, point.y);
1552
1493
  await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedX} ${adjustedY} ${adjustedX} ${adjustedY} 150`);
1553
1494
  }
1554
- async mouseDoubleClick(x, y) {
1495
+ async doubleTapPoint(point) {
1555
1496
  const adb = await this.getAdb();
1556
- const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
1497
+ const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(point.x, point.y);
1557
1498
  const tapCommand = `input${this.getDisplayArg()} tap ${adjustedX} ${adjustedY}`;
1558
1499
  await adb.shell(tapCommand);
1559
1500
  await sleep(50);
@@ -1562,12 +1503,14 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1562
1503
  async mouseMove() {
1563
1504
  return Promise.resolve();
1564
1505
  }
1565
- async mouseDrag(from, to, duration) {
1506
+ async dragPoint(from, to, duration) {
1507
+ await this.swipePoint(from, to, duration ?? defaultNormalScrollDuration);
1508
+ }
1509
+ async swipePoint(from, to, duration) {
1566
1510
  const adb = await this.getAdb();
1567
1511
  const { x: fromX, y: fromY } = await this.adjustCoordinates(from.x, from.y);
1568
1512
  const { x: toX, y: toY } = await this.adjustCoordinates(to.x, to.y);
1569
- const swipeDuration = duration ?? defaultNormalScrollDuration;
1570
- await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${swipeDuration}`);
1513
+ await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${duration}`);
1571
1514
  }
1572
1515
  async scroll(deltaX, deltaY, duration, warnOnClamp = false, direction) {
1573
1516
  if (0 === deltaX && 0 === deltaY) throw new Error('Scroll distance cannot be zero in both directions');
@@ -1590,11 +1533,14 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1590
1533
  }
1591
1534
  const endX = Math.round(startX - deltaX);
1592
1535
  const endY = Math.round(startY - deltaY);
1593
- const { x: adjustedStartX, y: adjustedStartY } = await this.adjustCoordinates(startX, startY);
1594
- const { x: adjustedEndX, y: adjustedEndY } = await this.adjustCoordinates(endX, endY);
1595
- const adb = await this.getAdb();
1596
1536
  const swipeDuration = duration ?? defaultNormalScrollDuration;
1597
- await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedStartX} ${adjustedStartY} ${adjustedEndX} ${adjustedEndY} ${swipeDuration}`);
1537
+ await this.swipePoint({
1538
+ x: startX,
1539
+ y: startY
1540
+ }, {
1541
+ x: endX,
1542
+ y: endY
1543
+ }, swipeDuration);
1598
1544
  }
1599
1545
  async destroy() {
1600
1546
  if (this.destroyed) return;
@@ -1642,9 +1588,9 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1642
1588
  const adb = await this.getAdb();
1643
1589
  await adb.shell(`input${this.getDisplayArg()} keyevent 187`);
1644
1590
  }
1645
- async longPress(x, y, duration = 2000) {
1591
+ async longPressPoint(point, duration = 2000) {
1646
1592
  const adb = await this.getAdb();
1647
- const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
1593
+ const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(point.x, point.y);
1648
1594
  await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedX} ${adjustedY} ${adjustedX} ${adjustedY} ${duration}`);
1649
1595
  }
1650
1596
  async pullDown(startPoint, distance, duration = 800) {
@@ -1665,10 +1611,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1665
1611
  await sleep(200);
1666
1612
  }
1667
1613
  async pullDrag(from, to, duration) {
1668
- const adb = await this.getAdb();
1669
- const { x: fromX, y: fromY } = await this.adjustCoordinates(from.x, from.y);
1670
- const { x: toX, y: toY } = await this.adjustCoordinates(to.x, to.y);
1671
- await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${duration}`);
1614
+ await this.swipePoint(from, to, duration);
1672
1615
  }
1673
1616
  async pullUp(startPoint, distance, duration = 600) {
1674
1617
  const { width, height } = await this.size();
@@ -1769,6 +1712,56 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1769
1712
  device_define_property(this, "interfaceType", 'android');
1770
1713
  device_define_property(this, "uri", void 0);
1771
1714
  device_define_property(this, "options", void 0);
1715
+ device_define_property(this, "inputPrimitives", {
1716
+ pointer: {
1717
+ tap: (point)=>this.tapPoint(point),
1718
+ doubleClick: (point)=>this.doubleTapPoint(point),
1719
+ longPress: (point, opts)=>this.longPressPoint(point, opts?.duration),
1720
+ dragAndDrop: (from, to)=>this.dragPoint(from, to)
1721
+ },
1722
+ keyboard: {
1723
+ keyboardPress: (keyName)=>this.pressKey(keyName),
1724
+ typeText: async (value, opts)=>{
1725
+ const target = opts?.target;
1726
+ if (target && opts?.replace !== false) await this.clearInput(target);
1727
+ else if (target) await this.tapPoint({
1728
+ x: target.center[0],
1729
+ y: target.center[1]
1730
+ });
1731
+ if (opts?.focusOnly) return;
1732
+ await this.typeText(value, opts);
1733
+ },
1734
+ clearInput: (target)=>this.clearInput(target),
1735
+ cursorMove: async (direction, times = 1)=>{
1736
+ const arrowKey = 'left' === direction ? 'ArrowLeft' : 'ArrowRight';
1737
+ for(let i = 0; i < times; i++)await this.pressKey(arrowKey);
1738
+ }
1739
+ },
1740
+ touch: {
1741
+ swipe: async (start, end, opts)=>{
1742
+ const duration = opts?.duration ?? 300;
1743
+ const repeatCount = opts?.repeat ?? 1;
1744
+ for(let i = 0; i < repeatCount; i++)await this.dragPoint(start, end, duration);
1745
+ },
1746
+ pinch: async (center, opts)=>{
1747
+ const { x: adjCenterX, y: adjCenterY } = await this.adjustCoordinates(Math.round(center.x), Math.round(center.y));
1748
+ const ratio = 0 !== adjCenterX && 0 !== center.x ? adjCenterX / center.x : 1;
1749
+ const adjStartDist = Math.round(opts.startDistance * ratio);
1750
+ const adjEndDist = Math.round(opts.endDistance * ratio);
1751
+ await this.ensureYadb();
1752
+ const adb = await this.getAdb();
1753
+ 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} ${opts.duration}`);
1754
+ }
1755
+ },
1756
+ scroll: {
1757
+ scroll: (param)=>this.performActionScroll(param)
1758
+ },
1759
+ system: {
1760
+ backButton: ()=>this.back(),
1761
+ homeButton: ()=>this.home(),
1762
+ recentAppsButton: ()=>this.recentApps()
1763
+ }
1764
+ });
1772
1765
  node_assert(deviceId, 'deviceId is required for AndroidDevice');
1773
1766
  this.deviceId = deviceId;
1774
1767
  this.options = options;
@@ -1819,37 +1812,10 @@ const createPlatformActions = (device)=>({
1819
1812
  description: 'Terminate (force-stop) an Android app by package name',
1820
1813
  interfaceAlias: 'terminate',
1821
1814
  paramSchema: terminateParamSchema,
1822
- delayBeforeRunner: 0,
1823
- delayAfterRunner: 0,
1824
1815
  call: async (param)=>{
1825
1816
  if (!param.uri || '' === param.uri.trim()) throw new Error('Terminate requires a non-empty uri parameter');
1826
1817
  await device.terminate(param.uri);
1827
1818
  }
1828
- }),
1829
- AndroidBackButton: defineAction({
1830
- name: 'AndroidBackButton',
1831
- description: 'Trigger the system "back" operation on Android devices',
1832
- delayBeforeRunner: 0,
1833
- delayAfterRunner: 0,
1834
- call: async ()=>{
1835
- await device.back();
1836
- }
1837
- }),
1838
- AndroidHomeButton: defineAction({
1839
- name: 'AndroidHomeButton',
1840
- description: 'Trigger the system "home" operation on Android devices',
1841
- delayBeforeRunner: 0,
1842
- delayAfterRunner: 0,
1843
- call: async ()=>{
1844
- await device.home();
1845
- }
1846
- }),
1847
- AndroidRecentAppsButton: defineAction({
1848
- name: 'AndroidRecentAppsButton',
1849
- description: 'Trigger the system "recent apps" operation on Android devices',
1850
- call: async ()=>{
1851
- await device.recentApps();
1852
- }
1853
1819
  })
1854
1820
  });
1855
1821
  const debugUtils = (0, logger_.getDebug)('android:utils');
@@ -2020,7 +1986,7 @@ class AndroidMCPServer extends BaseMCPServer {
2020
1986
  constructor(toolsManager){
2021
1987
  super({
2022
1988
  name: '@midscene/android-mcp',
2023
- version: "1.8.0",
1989
+ version: "1.8.1",
2024
1990
  description: 'Control the Android device using natural language commands'
2025
1991
  }, toolsManager);
2026
1992
  }