@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.
@@ -714,103 +714,30 @@ var __webpack_exports__ = {};
714
714
  }
715
715
  class AndroidDevice {
716
716
  actionSpace() {
717
- const defaultActions = [
718
- (0, device_namespaceObject.defineActionTap)(async (param)=>{
719
- const element = param.locate;
720
- external_node_assert_default()(element, 'Element not found, cannot tap');
721
- await this.mouseClick(element.center[0], element.center[1]);
722
- }),
723
- (0, device_namespaceObject.defineActionDoubleClick)(async (param)=>{
724
- const element = param.locate;
725
- external_node_assert_default()(element, 'Element not found, cannot double click');
726
- await this.mouseDoubleClick(element.center[0], element.center[1]);
727
- }),
728
- (0, device_namespaceObject.defineAction)({
729
- name: 'Input',
730
- description: 'Input text into the input field',
731
- interfaceAlias: 'aiInput',
732
- paramSchema: core_namespaceObject.z.object({
733
- value: core_namespaceObject.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.'),
734
- autoDismissKeyboard: core_namespaceObject.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.'),
735
- mode: core_namespaceObject.z.preprocess((val)=>'append' === val ? 'typeOnly' : val, core_namespaceObject.z["enum"]([
736
- 'replace',
737
- 'clear',
738
- 'typeOnly'
739
- ]).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.')),
740
- locate: (0, core_namespaceObject.getMidsceneLocationSchema)().describe('The input field to be filled').optional()
741
- }),
742
- sample: {
743
- value: 'test@example.com',
744
- locate: {
745
- prompt: 'the email input field'
746
- }
717
+ const mobileActionContext = {
718
+ input: this.inputPrimitives,
719
+ size: ()=>this.size(),
720
+ sleep: async (timeMs)=>{
721
+ await (0, core_utils_namespaceObject.sleep)(timeMs);
722
+ },
723
+ getDefaultAutoDismissKeyboard: ()=>this.options?.autoDismissKeyboard,
724
+ systemActions: {
725
+ backButton: {
726
+ name: 'AndroidBackButton',
727
+ description: 'Trigger the system "back" operation on Android devices'
747
728
  },
748
- call: async (param)=>{
749
- const element = param.locate;
750
- if ('typeOnly' !== param.mode) await this.clearInput(element);
751
- if ('clear' === param.mode) return;
752
- if (!param || !param.value) return;
753
- const autoDismissKeyboard = param.autoDismissKeyboard ?? this.options?.autoDismissKeyboard;
754
- await this.keyboardType(param.value, {
755
- autoDismissKeyboard
756
- });
757
- }
758
- }),
759
- (0, device_namespaceObject.defineActionScroll)(async (param)=>{
760
- const element = param.locate;
761
- const startingPoint = element ? {
762
- left: element.center[0],
763
- top: element.center[1]
764
- } : void 0;
765
- const scrollToEventName = param?.scrollType;
766
- if ('scrollToTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
767
- else if ('scrollToBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
768
- else if ('scrollToRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
769
- else if ('scrollToLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
770
- else if ('singleAction' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
771
- else {
772
- if (param?.direction !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance || void 0, startingPoint);
773
- else if ('left' === param.direction) await this.scrollLeft(param.distance || void 0, startingPoint);
774
- else if ('right' === param.direction) await this.scrollRight(param.distance || void 0, startingPoint);
775
- else throw new Error(`Unknown scroll direction: ${param.direction}`);
776
- else await this.scrollDown(param?.distance || void 0, startingPoint);
777
- await (0, core_utils_namespaceObject.sleep)(500);
778
- }
779
- }),
780
- (0, device_namespaceObject.defineActionDragAndDrop)(async (param)=>{
781
- const from = param.from;
782
- const to = param.to;
783
- external_node_assert_default()(from, 'missing "from" param for drag and drop');
784
- external_node_assert_default()(to, 'missing "to" param for drag and drop');
785
- await this.mouseDrag({
786
- x: from.center[0],
787
- y: from.center[1]
788
- }, {
789
- x: to.center[0],
790
- y: to.center[1]
791
- });
792
- }),
793
- (0, device_namespaceObject.defineActionSwipe)(async (param)=>{
794
- const { startPoint, endPoint, duration, repeatCount } = (0, device_namespaceObject.normalizeMobileSwipeParam)(param, await this.size());
795
- for(let i = 0; i < repeatCount; i++)await this.mouseDrag(startPoint, endPoint, duration);
796
- }),
797
- (0, device_namespaceObject.defineActionKeyboardPress)(async (param)=>{
798
- await this.keyboardPress(param.keyName);
799
- }),
800
- (0, device_namespaceObject.defineActionCursorMove)(async (param)=>{
801
- const arrowKey = 'left' === param.direction ? 'ArrowLeft' : 'ArrowRight';
802
- const times = param.times ?? 1;
803
- for(let i = 0; i < times; i++){
804
- await this.keyboardPress(arrowKey);
805
- await (0, core_utils_namespaceObject.sleep)(100);
729
+ homeButton: {
730
+ name: 'AndroidHomeButton',
731
+ description: 'Trigger the system "home" operation on Android devices'
732
+ },
733
+ recentAppsButton: {
734
+ name: 'AndroidRecentAppsButton',
735
+ description: 'Trigger the system "recent apps" operation on Android devices'
806
736
  }
807
- }),
808
- (0, device_namespaceObject.defineActionLongPress)(async (param)=>{
809
- const element = param.locate;
810
- if (!element) throw new Error('LongPress requires an element to be located');
811
- const [x, y] = element.center;
812
- await this.longPress(x, y, param?.duration);
813
- }),
737
+ }
738
+ };
739
+ const defaultActions = [
740
+ ...(0, device_namespaceObject.createDefaultMobileActions)(mobileActionContext),
814
741
  (0, device_namespaceObject.defineAction)({
815
742
  name: 'PullGesture',
816
743
  description: 'Trigger pull down to refresh or pull up actions',
@@ -840,19 +767,6 @@ var __webpack_exports__ = {};
840
767
  else if ('up' === param.direction) await this.pullUp(startPoint, param.distance, param.duration);
841
768
  else throw new Error(`Unknown pull direction: ${param.direction}`);
842
769
  }
843
- }),
844
- (0, device_namespaceObject.defineActionPinch)(async (param)=>{
845
- const { centerX, centerY, startDistance, endDistance, duration } = (0, device_namespaceObject.normalizePinchParam)(param, await this.size());
846
- const { x: adjCenterX, y: adjCenterY } = await this.adjustCoordinates(centerX, centerY);
847
- const ratio = 0 !== adjCenterX && 0 !== centerX ? adjCenterX / centerX : 1;
848
- const adjStartDist = Math.round(startDistance * ratio);
849
- const adjEndDist = Math.round(endDistance * ratio);
850
- await this.ensureYadb();
851
- const adb = await this.getAdb();
852
- 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}`);
853
- }),
854
- (0, device_namespaceObject.defineActionClearInput)(async (param)=>{
855
- await this.clearInput(param.locate);
856
770
  })
857
771
  ];
858
772
  const platformSpecificActions = Object.values(createPlatformActions(this));
@@ -863,6 +777,27 @@ var __webpack_exports__ = {};
863
777
  ...customActions
864
778
  ];
865
779
  }
780
+ async performActionScroll(param) {
781
+ const element = param.locate;
782
+ const startingPoint = element ? {
783
+ left: element.center[0],
784
+ top: element.center[1]
785
+ } : void 0;
786
+ const scrollToEventName = param?.scrollType;
787
+ if ('scrollToTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
788
+ else if ('scrollToBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
789
+ else if ('scrollToRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
790
+ else if ('scrollToLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
791
+ else if ('singleAction' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
792
+ else {
793
+ if (param?.direction !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance || void 0, startingPoint);
794
+ else if ('left' === param.direction) await this.scrollLeft(param.distance || void 0, startingPoint);
795
+ else if ('right' === param.direction) await this.scrollRight(param.distance || void 0, startingPoint);
796
+ else throw new Error(`Unknown scroll direction: ${param.direction}`);
797
+ else await this.scrollDown(param?.distance || void 0, startingPoint);
798
+ await (0, core_utils_namespaceObject.sleep)(500);
799
+ }
800
+ }
866
801
  describe() {
867
802
  return this.description || `DeviceId: ${this.deviceId}`;
868
803
  }
@@ -1350,14 +1285,20 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1350
1285
  return result;
1351
1286
  }
1352
1287
  async clearInput(element) {
1353
- if (element) await this.mouseClick(element.center[0], element.center[1]);
1288
+ if (element) await this.tapPoint({
1289
+ x: element.center[0],
1290
+ y: element.center[1]
1291
+ });
1354
1292
  await this.ensureYadb();
1355
1293
  const adb = await this.getAdb();
1356
1294
  const IME_STRATEGY = (this.options?.imeStrategy || env_namespaceObject.globalConfigManager.getEnvConfigValue(env_namespaceObject.MIDSCENE_ANDROID_IME_STRATEGY)) ?? IME_STRATEGY_YADB_FOR_NON_ASCII;
1357
1295
  if (IME_STRATEGY === IME_STRATEGY_YADB_FOR_NON_ASCII) await adb.clearTextField(100);
1358
1296
  else await adb.shell(`app_process${this.getDisplayArg()} -Djava.class.path=/data/local/tmp/yadb /data/local/tmp com.ysbing.yadb.Main -keyboardClear`);
1359
1297
  if (await adb.isSoftKeyboardPresent()) return;
1360
- if (element) await this.mouseClick(element.center[0], element.center[1]);
1298
+ if (element) await this.tapPoint({
1299
+ x: element.center[0],
1300
+ y: element.center[1]
1301
+ });
1361
1302
  }
1362
1303
  async forceScreenshot(path) {
1363
1304
  await this.ensureYadb();
@@ -1378,7 +1319,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1378
1319
  x: start.x,
1379
1320
  y: Math.round(height)
1380
1321
  };
1381
- await (0, utils_namespaceObject.repeat)(defaultScrollUntilTimes, ()=>this.mouseDrag(start, end, defaultFastScrollDuration));
1322
+ await (0, utils_namespaceObject.repeat)(defaultScrollUntilTimes, ()=>this.dragPoint(start, end, defaultFastScrollDuration));
1382
1323
  await (0, core_utils_namespaceObject.sleep)(1000);
1383
1324
  return;
1384
1325
  }
@@ -1395,7 +1336,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1395
1336
  x: start.x,
1396
1337
  y: 0
1397
1338
  };
1398
- await (0, utils_namespaceObject.repeat)(defaultScrollUntilTimes, ()=>this.mouseDrag(start, end, defaultFastScrollDuration));
1339
+ await (0, utils_namespaceObject.repeat)(defaultScrollUntilTimes, ()=>this.dragPoint(start, end, defaultFastScrollDuration));
1399
1340
  await (0, core_utils_namespaceObject.sleep)(1000);
1400
1341
  return;
1401
1342
  }
@@ -1413,7 +1354,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1413
1354
  x: Math.round(width),
1414
1355
  y: start.y
1415
1356
  };
1416
- await (0, utils_namespaceObject.repeat)(defaultScrollUntilTimes, ()=>this.mouseDrag(start, end, defaultFastScrollDuration));
1357
+ await (0, utils_namespaceObject.repeat)(defaultScrollUntilTimes, ()=>this.dragPoint(start, end, defaultFastScrollDuration));
1417
1358
  await (0, core_utils_namespaceObject.sleep)(1000);
1418
1359
  return;
1419
1360
  }
@@ -1430,7 +1371,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1430
1371
  x: 0,
1431
1372
  y: start.y
1432
1373
  };
1433
- await (0, utils_namespaceObject.repeat)(defaultScrollUntilTimes, ()=>this.mouseDrag(start, end, defaultFastScrollDuration));
1374
+ await (0, utils_namespaceObject.repeat)(defaultScrollUntilTimes, ()=>this.dragPoint(start, end, defaultFastScrollDuration));
1434
1375
  await (0, core_utils_namespaceObject.sleep)(1000);
1435
1376
  return;
1436
1377
  }
@@ -1448,7 +1389,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1448
1389
  };
1449
1390
  const end = this.calculateScrollEndPoint(start, 0, scrollDistance, 0, height);
1450
1391
  if (hasExplicitDistance) this.warnScrollDistanceClamped('up', scrollDistance, Math.abs(end.y - start.y));
1451
- await this.mouseDrag(start, end);
1392
+ await this.dragPoint(start, end);
1452
1393
  return;
1453
1394
  }
1454
1395
  await this.scroll(0, -scrollDistance, void 0, hasExplicitDistance, 'up');
@@ -1464,7 +1405,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1464
1405
  };
1465
1406
  const end = this.calculateScrollEndPoint(start, 0, -scrollDistance, 0, height);
1466
1407
  if (hasExplicitDistance) this.warnScrollDistanceClamped('down', scrollDistance, Math.abs(end.y - start.y));
1467
- await this.mouseDrag(start, end);
1408
+ await this.dragPoint(start, end);
1468
1409
  return;
1469
1410
  }
1470
1411
  await this.scroll(0, scrollDistance, void 0, hasExplicitDistance, 'down');
@@ -1480,7 +1421,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1480
1421
  };
1481
1422
  const end = this.calculateScrollEndPoint(start, scrollDistance, 0, width, 0);
1482
1423
  if (hasExplicitDistance) this.warnScrollDistanceClamped('left', scrollDistance, Math.abs(end.x - start.x));
1483
- await this.mouseDrag(start, end);
1424
+ await this.dragPoint(start, end);
1484
1425
  return;
1485
1426
  }
1486
1427
  await this.scroll(-scrollDistance, 0, void 0, hasExplicitDistance, 'left');
@@ -1496,7 +1437,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1496
1437
  };
1497
1438
  const end = this.calculateScrollEndPoint(start, -scrollDistance, 0, width, 0);
1498
1439
  if (hasExplicitDistance) this.warnScrollDistanceClamped('right', scrollDistance, Math.abs(end.x - start.x));
1499
- await this.mouseDrag(start, end);
1440
+ await this.dragPoint(start, end);
1500
1441
  return;
1501
1442
  }
1502
1443
  await this.scroll(scrollDistance, 0, void 0, hasExplicitDistance, 'right');
@@ -1517,7 +1458,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1517
1458
  const hasBothQuotes = text.includes('"') && text.includes("'");
1518
1459
  return hasNonAscii || hasFormatSpecifiers || hasShellSpecialChars || hasBothQuotes;
1519
1460
  }
1520
- async keyboardType(text, options) {
1461
+ async typeText(text, options) {
1521
1462
  if (!text) return;
1522
1463
  const adb = await this.getAdb();
1523
1464
  const IME_STRATEGY = (this.options?.imeStrategy || env_namespaceObject.globalConfigManager.getEnvConfigValue(env_namespaceObject.MIDSCENE_ANDROID_IME_STRATEGY)) ?? IME_STRATEGY_YADB_FOR_NON_ASCII;
@@ -1554,7 +1495,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1554
1495
  const lowerKey = key.toLowerCase();
1555
1496
  return keyMap[lowerKey] || key;
1556
1497
  }
1557
- async keyboardPress(key) {
1498
+ async pressKey(key) {
1558
1499
  const keyCodeMap = {
1559
1500
  Enter: 66,
1560
1501
  Backspace: 67,
@@ -1576,14 +1517,14 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1576
1517
  if (asciiCode >= 65 && asciiCode <= 90) await adb.keyevent(asciiCode - 36);
1577
1518
  }
1578
1519
  }
1579
- async mouseClick(x, y) {
1520
+ async tapPoint(point) {
1580
1521
  const adb = await this.getAdb();
1581
- const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
1522
+ const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(point.x, point.y);
1582
1523
  await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedX} ${adjustedY} ${adjustedX} ${adjustedY} 150`);
1583
1524
  }
1584
- async mouseDoubleClick(x, y) {
1525
+ async doubleTapPoint(point) {
1585
1526
  const adb = await this.getAdb();
1586
- const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
1527
+ const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(point.x, point.y);
1587
1528
  const tapCommand = `input${this.getDisplayArg()} tap ${adjustedX} ${adjustedY}`;
1588
1529
  await adb.shell(tapCommand);
1589
1530
  await (0, core_utils_namespaceObject.sleep)(50);
@@ -1592,12 +1533,14 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1592
1533
  async mouseMove() {
1593
1534
  return Promise.resolve();
1594
1535
  }
1595
- async mouseDrag(from, to, duration) {
1536
+ async dragPoint(from, to, duration) {
1537
+ await this.swipePoint(from, to, duration ?? defaultNormalScrollDuration);
1538
+ }
1539
+ async swipePoint(from, to, duration) {
1596
1540
  const adb = await this.getAdb();
1597
1541
  const { x: fromX, y: fromY } = await this.adjustCoordinates(from.x, from.y);
1598
1542
  const { x: toX, y: toY } = await this.adjustCoordinates(to.x, to.y);
1599
- const swipeDuration = duration ?? defaultNormalScrollDuration;
1600
- await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${swipeDuration}`);
1543
+ await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${duration}`);
1601
1544
  }
1602
1545
  async scroll(deltaX, deltaY, duration, warnOnClamp = false, direction) {
1603
1546
  if (0 === deltaX && 0 === deltaY) throw new Error('Scroll distance cannot be zero in both directions');
@@ -1620,11 +1563,14 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1620
1563
  }
1621
1564
  const endX = Math.round(startX - deltaX);
1622
1565
  const endY = Math.round(startY - deltaY);
1623
- const { x: adjustedStartX, y: adjustedStartY } = await this.adjustCoordinates(startX, startY);
1624
- const { x: adjustedEndX, y: adjustedEndY } = await this.adjustCoordinates(endX, endY);
1625
- const adb = await this.getAdb();
1626
1566
  const swipeDuration = duration ?? defaultNormalScrollDuration;
1627
- await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedStartX} ${adjustedStartY} ${adjustedEndX} ${adjustedEndY} ${swipeDuration}`);
1567
+ await this.swipePoint({
1568
+ x: startX,
1569
+ y: startY
1570
+ }, {
1571
+ x: endX,
1572
+ y: endY
1573
+ }, swipeDuration);
1628
1574
  }
1629
1575
  async destroy() {
1630
1576
  if (this.destroyed) return;
@@ -1672,9 +1618,9 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1672
1618
  const adb = await this.getAdb();
1673
1619
  await adb.shell(`input${this.getDisplayArg()} keyevent 187`);
1674
1620
  }
1675
- async longPress(x, y, duration = 2000) {
1621
+ async longPressPoint(point, duration = 2000) {
1676
1622
  const adb = await this.getAdb();
1677
- const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
1623
+ const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(point.x, point.y);
1678
1624
  await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedX} ${adjustedY} ${adjustedX} ${adjustedY} ${duration}`);
1679
1625
  }
1680
1626
  async pullDown(startPoint, distance, duration = 800) {
@@ -1695,10 +1641,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1695
1641
  await (0, core_utils_namespaceObject.sleep)(200);
1696
1642
  }
1697
1643
  async pullDrag(from, to, duration) {
1698
- const adb = await this.getAdb();
1699
- const { x: fromX, y: fromY } = await this.adjustCoordinates(from.x, from.y);
1700
- const { x: toX, y: toY } = await this.adjustCoordinates(to.x, to.y);
1701
- await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${duration}`);
1644
+ await this.swipePoint(from, to, duration);
1702
1645
  }
1703
1646
  async pullUp(startPoint, distance, duration = 600) {
1704
1647
  const { width, height } = await this.size();
@@ -1799,6 +1742,56 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1799
1742
  device_define_property(this, "interfaceType", 'android');
1800
1743
  device_define_property(this, "uri", void 0);
1801
1744
  device_define_property(this, "options", void 0);
1745
+ device_define_property(this, "inputPrimitives", {
1746
+ pointer: {
1747
+ tap: (point)=>this.tapPoint(point),
1748
+ doubleClick: (point)=>this.doubleTapPoint(point),
1749
+ longPress: (point, opts)=>this.longPressPoint(point, opts?.duration),
1750
+ dragAndDrop: (from, to)=>this.dragPoint(from, to)
1751
+ },
1752
+ keyboard: {
1753
+ keyboardPress: (keyName)=>this.pressKey(keyName),
1754
+ typeText: async (value, opts)=>{
1755
+ const target = opts?.target;
1756
+ if (target && opts?.replace !== false) await this.clearInput(target);
1757
+ else if (target) await this.tapPoint({
1758
+ x: target.center[0],
1759
+ y: target.center[1]
1760
+ });
1761
+ if (opts?.focusOnly) return;
1762
+ await this.typeText(value, opts);
1763
+ },
1764
+ clearInput: (target)=>this.clearInput(target),
1765
+ cursorMove: async (direction, times = 1)=>{
1766
+ const arrowKey = 'left' === direction ? 'ArrowLeft' : 'ArrowRight';
1767
+ for(let i = 0; i < times; i++)await this.pressKey(arrowKey);
1768
+ }
1769
+ },
1770
+ touch: {
1771
+ swipe: async (start, end, opts)=>{
1772
+ const duration = opts?.duration ?? 300;
1773
+ const repeatCount = opts?.repeat ?? 1;
1774
+ for(let i = 0; i < repeatCount; i++)await this.dragPoint(start, end, duration);
1775
+ },
1776
+ pinch: async (center, opts)=>{
1777
+ const { x: adjCenterX, y: adjCenterY } = await this.adjustCoordinates(Math.round(center.x), Math.round(center.y));
1778
+ const ratio = 0 !== adjCenterX && 0 !== center.x ? adjCenterX / center.x : 1;
1779
+ const adjStartDist = Math.round(opts.startDistance * ratio);
1780
+ const adjEndDist = Math.round(opts.endDistance * ratio);
1781
+ await this.ensureYadb();
1782
+ const adb = await this.getAdb();
1783
+ 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}`);
1784
+ }
1785
+ },
1786
+ scroll: {
1787
+ scroll: (param)=>this.performActionScroll(param)
1788
+ },
1789
+ system: {
1790
+ backButton: ()=>this.back(),
1791
+ homeButton: ()=>this.home(),
1792
+ recentAppsButton: ()=>this.recentApps()
1793
+ }
1794
+ });
1802
1795
  external_node_assert_default()(deviceId, 'deviceId is required for AndroidDevice');
1803
1796
  this.deviceId = deviceId;
1804
1797
  this.options = options;
@@ -1849,37 +1842,10 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1849
1842
  description: 'Terminate (force-stop) an Android app by package name',
1850
1843
  interfaceAlias: 'terminate',
1851
1844
  paramSchema: terminateParamSchema,
1852
- delayBeforeRunner: 0,
1853
- delayAfterRunner: 0,
1854
1845
  call: async (param)=>{
1855
1846
  if (!param.uri || '' === param.uri.trim()) throw new Error('Terminate requires a non-empty uri parameter');
1856
1847
  await device.terminate(param.uri);
1857
1848
  }
1858
- }),
1859
- AndroidBackButton: (0, device_namespaceObject.defineAction)({
1860
- name: 'AndroidBackButton',
1861
- description: 'Trigger the system "back" operation on Android devices',
1862
- delayBeforeRunner: 0,
1863
- delayAfterRunner: 0,
1864
- call: async ()=>{
1865
- await device.back();
1866
- }
1867
- }),
1868
- AndroidHomeButton: (0, device_namespaceObject.defineAction)({
1869
- name: 'AndroidHomeButton',
1870
- description: 'Trigger the system "home" operation on Android devices',
1871
- delayBeforeRunner: 0,
1872
- delayAfterRunner: 0,
1873
- call: async ()=>{
1874
- await device.home();
1875
- }
1876
- }),
1877
- AndroidRecentAppsButton: (0, device_namespaceObject.defineAction)({
1878
- name: 'AndroidRecentAppsButton',
1879
- description: 'Trigger the system "recent apps" operation on Android devices',
1880
- call: async ()=>{
1881
- await device.recentApps();
1882
- }
1883
1849
  })
1884
1850
  });
1885
1851
  const debugUtils = (0, logger_.getDebug)('android:utils');
@@ -2051,7 +2017,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
2051
2017
  constructor(toolsManager){
2052
2018
  super({
2053
2019
  name: '@midscene/android-mcp',
2054
- version: "1.8.0",
2020
+ version: "1.8.1",
2055
2021
  description: 'Control the Android device using natural language commands'
2056
2022
  }, toolsManager);
2057
2023
  }
@@ -13,6 +13,7 @@ import { DeviceAction } from '@midscene/core';
13
13
  import type { ElementInfo } from '@midscene/shared/extractor';
14
14
  import { InitArgSpec } from '@midscene/shared/mcp/base-tools';
15
15
  import { InterfaceType } from '@midscene/core';
16
+ import { MobileInputPrimitives } from '@midscene/core/device';
16
17
  import { overrideAIConfig } from '@midscene/shared/env';
17
18
  import { Point } from '@midscene/core';
18
19
  import { Size } from '@midscene/core';
@@ -95,7 +96,9 @@ export declare class AndroidDevice implements AbstractInterface {
95
96
  interfaceType: InterfaceType;
96
97
  uri: string | undefined;
97
98
  options?: AndroidDeviceOpt;
99
+ readonly inputPrimitives: MobileInputPrimitives;
98
100
  actionSpace(): DeviceAction<any>[];
101
+ private performActionScroll;
99
102
  constructor(deviceId: string, options?: AndroidDeviceOpt);
100
103
  describe(): string;
101
104
  connect(): Promise<ADB>;
@@ -196,19 +199,14 @@ export declare class AndroidDevice implements AbstractInterface {
196
199
  * via escapeForShell + double-quoted shell context.
197
200
  */
198
201
  private shouldUseYadbForText;
199
- keyboardType(text: string, options?: AndroidDeviceInputOpt): Promise<void>;
202
+ private typeText;
200
203
  private normalizeKeyName;
201
- keyboardPress(key: string): Promise<void>;
202
- mouseClick(x: number, y: number): Promise<void>;
203
- mouseDoubleClick(x: number, y: number): Promise<void>;
204
+ private pressKey;
205
+ private tapPoint;
206
+ private doubleTapPoint;
204
207
  mouseMove(): Promise<void>;
205
- mouseDrag(from: {
206
- x: number;
207
- y: number;
208
- }, to: {
209
- x: number;
210
- y: number;
211
- }, duration?: number): Promise<void>;
208
+ private dragPoint;
209
+ private swipePoint;
212
210
  scroll(deltaX: number, deltaY: number, duration?: number, warnOnClamp?: boolean, direction?: ScrollDirection): Promise<void>;
213
211
  destroy(): Promise<void>;
214
212
  /**
@@ -220,7 +218,7 @@ export declare class AndroidDevice implements AbstractInterface {
220
218
  back(): Promise<void>;
221
219
  home(): Promise<void>;
222
220
  recentApps(): Promise<void>;
223
- longPress(x: number, y: number, duration?: number): Promise<void>;
221
+ private longPressPoint;
224
222
  pullDown(startPoint?: Point, distance?: number, duration?: number): Promise<void>;
225
223
  pullDrag(from: {
226
224
  x: number;
@@ -14,6 +14,7 @@ import { InitArgSpec } from '@midscene/shared/mcp/base-tools';
14
14
  import { InterfaceType } from '@midscene/core';
15
15
  import { LaunchMCPServerOptions } from '@midscene/shared/mcp';
16
16
  import { LaunchMCPServerResult } from '@midscene/shared/mcp';
17
+ import { MobileInputPrimitives } from '@midscene/core/device';
17
18
  import { Point } from '@midscene/core';
18
19
  import { Size } from '@midscene/core';
19
20
  import { Tool } from '@midscene/shared/mcp';
@@ -87,7 +88,9 @@ declare class AndroidDevice implements AbstractInterface {
87
88
  interfaceType: InterfaceType;
88
89
  uri: string | undefined;
89
90
  options?: AndroidDeviceOpt;
91
+ readonly inputPrimitives: MobileInputPrimitives;
90
92
  actionSpace(): DeviceAction<any>[];
93
+ private performActionScroll;
91
94
  constructor(deviceId: string, options?: AndroidDeviceOpt);
92
95
  describe(): string;
93
96
  connect(): Promise<ADB>;
@@ -188,19 +191,14 @@ declare class AndroidDevice implements AbstractInterface {
188
191
  * via escapeForShell + double-quoted shell context.
189
192
  */
190
193
  private shouldUseYadbForText;
191
- keyboardType(text: string, options?: AndroidDeviceInputOpt): Promise<void>;
194
+ private typeText;
192
195
  private normalizeKeyName;
193
- keyboardPress(key: string): Promise<void>;
194
- mouseClick(x: number, y: number): Promise<void>;
195
- mouseDoubleClick(x: number, y: number): Promise<void>;
196
+ private pressKey;
197
+ private tapPoint;
198
+ private doubleTapPoint;
196
199
  mouseMove(): Promise<void>;
197
- mouseDrag(from: {
198
- x: number;
199
- y: number;
200
- }, to: {
201
- x: number;
202
- y: number;
203
- }, duration?: number): Promise<void>;
200
+ private dragPoint;
201
+ private swipePoint;
204
202
  scroll(deltaX: number, deltaY: number, duration?: number, warnOnClamp?: boolean, direction?: ScrollDirection): Promise<void>;
205
203
  destroy(): Promise<void>;
206
204
  /**
@@ -212,7 +210,7 @@ declare class AndroidDevice implements AbstractInterface {
212
210
  back(): Promise<void>;
213
211
  home(): Promise<void>;
214
212
  recentApps(): Promise<void>;
215
- longPress(x: number, y: number, duration?: number): Promise<void>;
213
+ private longPressPoint;
216
214
  pullDown(startPoint?: Point, distance?: number, duration?: number): Promise<void>;
217
215
  pullDrag(from: {
218
216
  x: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@midscene/android",
3
- "version": "1.8.0",
3
+ "version": "1.8.1",
4
4
  "description": "Android automation library for Midscene",
5
5
  "keywords": [
6
6
  "Android UI automation",
@@ -41,8 +41,8 @@
41
41
  "@yume-chan/stream-extra": "2.1.0",
42
42
  "appium-adb": "12.12.1",
43
43
  "sharp": "^0.34.3",
44
- "@midscene/core": "1.8.0",
45
- "@midscene/shared": "1.8.0"
44
+ "@midscene/shared": "1.8.1",
45
+ "@midscene/core": "1.8.1"
46
46
  },
47
47
  "optionalDependencies": {
48
48
  "@ffmpeg-installer/ffmpeg": "^1.1.0"
@@ -56,7 +56,7 @@
56
56
  "undici": "^6.0.0",
57
57
  "vitest": "3.0.5",
58
58
  "zod": "^3.25.1",
59
- "@midscene/playground": "1.8.0"
59
+ "@midscene/playground": "1.8.1"
60
60
  },
61
61
  "license": "MIT",
62
62
  "scripts": {