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