@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/LICENSE +21 -0
- package/dist/es/cli.mjs +125 -95
- package/dist/es/index.mjs +124 -94
- package/dist/es/mcp-server.mjs +125 -95
- package/dist/lib/cli.js +123 -93
- package/dist/lib/index.js +122 -92
- package/dist/lib/mcp-server.js +123 -93
- 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,66 +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
|
-
const inputPromise = adb.shell(command);
|
|
875
|
-
const timeoutPromise = new Promise((_, reject)=>setTimeout(()=>reject(new Error('YADB timeout')), 1000));
|
|
876
|
-
await Promise.race([
|
|
877
|
-
inputPromise,
|
|
878
|
-
timeoutPromise
|
|
879
|
-
]);
|
|
880
|
-
debugDevice(`YADB input completed: "${keyboardContent}"`);
|
|
881
|
-
} catch (error) {
|
|
882
|
-
const isAccessibilityConflict = error?.cause?.stderr?.includes('UiAutomationService') || error?.cause?.stderr?.includes('already registered') || error?.message?.includes('UiAutomationService');
|
|
883
|
-
if (isAccessibilityConflict) {
|
|
884
|
-
debugDevice("YADB failed due to AccessibilityService conflict (likely Appium running), falling back to clipboard method");
|
|
885
|
-
await this.inputViaClipboard(keyboardContent);
|
|
886
|
-
} else 'YADB timeout' === error.message ? debugDevice(`YADB timed out after 2s, assuming input succeeded: "${keyboardContent}"`) : debugDevice(`YADB execution may have completed despite error: ${error}`);
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
async inputViaClipboard(text) {
|
|
890
|
-
const adb = await this.getAdb();
|
|
891
|
-
try {
|
|
892
|
-
debugDevice(`Inputting via clipboard: "${text}"`);
|
|
893
|
-
const escapedText = text.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
894
|
-
const setClipboardCmd = `
|
|
895
|
-
content insert --uri content://settings/system --bind name:s:clipboard_text --bind value:s:"${escapedText}"
|
|
896
|
-
`;
|
|
897
|
-
await adb.shell(setClipboardCmd);
|
|
898
|
-
await (0, utils_namespaceObject.sleep)(100);
|
|
899
|
-
await adb.shell('input keyevent KEYCODE_PASTE');
|
|
900
|
-
await (0, utils_namespaceObject.sleep)(100);
|
|
901
|
-
debugDevice(`Clipboard input completed via content provider: "${text}"`);
|
|
902
|
-
} catch (error1) {
|
|
903
|
-
debugDevice(`Content provider clipboard failed, trying clipper app: ${error1}`);
|
|
904
|
-
try {
|
|
905
|
-
const base64Text = Buffer.from(text, 'utf-8').toString('base64');
|
|
906
|
-
await adb.shell(`am broadcast -a clipper.set -e text "${base64Text}"`);
|
|
907
|
-
await (0, utils_namespaceObject.sleep)(100);
|
|
908
|
-
await adb.shell('input keyevent KEYCODE_PASTE');
|
|
909
|
-
await (0, utils_namespaceObject.sleep)(100);
|
|
910
|
-
debugDevice(`Clipboard input completed via clipper: "${text}"`);
|
|
911
|
-
} catch (error2) {
|
|
912
|
-
debugDevice(`All clipboard methods failed: ${error2}`);
|
|
913
|
-
const isPureAscii = /^[\x00-\x7F]*$/.test(text);
|
|
914
|
-
if (isPureAscii) {
|
|
915
|
-
debugDevice(`Using ADB inputText for ASCII text: "${text}"`);
|
|
916
|
-
await adb.inputText(text);
|
|
917
|
-
} else await this.inputCharByChar(text);
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
async inputCharByChar(text) {
|
|
922
|
-
const adb = await this.getAdb();
|
|
923
|
-
debugDevice(`Inputting character by character (slow method): "${text}"`);
|
|
924
|
-
const chars = Array.from(text);
|
|
925
|
-
for (const char of chars){
|
|
926
|
-
if (' ' === char) await adb.shell('input keyevent KEYCODE_SPACE');
|
|
927
|
-
else await adb.shell(`input text "${char}"`);
|
|
928
|
-
await (0, utils_namespaceObject.sleep)(50);
|
|
929
|
-
}
|
|
930
|
-
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}'`);
|
|
931
915
|
}
|
|
932
916
|
async getElementsInfo() {
|
|
933
917
|
return [];
|
|
@@ -940,6 +924,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
940
924
|
}
|
|
941
925
|
async getScreenSize() {
|
|
942
926
|
const shouldCache = !(this.options?.alwaysRefreshScreenInfo ?? false);
|
|
927
|
+
debugDevice(`getScreenSize: alwaysRefreshScreenInfo=${this.options?.alwaysRefreshScreenInfo}, shouldCache=${shouldCache}, hasCachedSize=${!!this.cachedScreenSize}`);
|
|
943
928
|
if (shouldCache && this.cachedScreenSize) return this.cachedScreenSize;
|
|
944
929
|
const adb = await this.getAdb();
|
|
945
930
|
if ('number' == typeof this.options?.displayId) try {
|
|
@@ -1073,6 +1058,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1073
1058
|
}
|
|
1074
1059
|
async getDisplayOrientation() {
|
|
1075
1060
|
const shouldCache = !(this.options?.alwaysRefreshScreenInfo ?? false);
|
|
1061
|
+
debugDevice(`getDisplayOrientation: alwaysRefreshScreenInfo=${this.options?.alwaysRefreshScreenInfo}, shouldCache=${shouldCache}, hasCachedOrientation=${null !== this.cachedOrientation}`);
|
|
1076
1062
|
if (shouldCache && null !== this.cachedOrientation) return this.cachedOrientation;
|
|
1077
1063
|
const adb = await this.getAdb();
|
|
1078
1064
|
let orientation = 0;
|
|
@@ -1098,6 +1084,15 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1098
1084
|
if (shouldCache) this.cachedOrientation = orientation;
|
|
1099
1085
|
return orientation;
|
|
1100
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
|
+
}
|
|
1101
1096
|
async size() {
|
|
1102
1097
|
const deviceInfo = await this.getDevicePhysicalInfo();
|
|
1103
1098
|
const adapter = this.getScrcpyAdapter();
|
|
@@ -1124,7 +1119,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1124
1119
|
height: logicalHeight
|
|
1125
1120
|
};
|
|
1126
1121
|
}
|
|
1127
|
-
async cacheFeatureForPoint(center
|
|
1122
|
+
async cacheFeatureForPoint(center) {
|
|
1128
1123
|
const { width, height } = await this.size();
|
|
1129
1124
|
debugDevice('cacheFeatureForPoint: center=[%s,%s], screen=[%s,%s]', center[0], center[1], width, height);
|
|
1130
1125
|
return {
|
|
@@ -1204,14 +1199,23 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1204
1199
|
const androidScreenshotPath = `/data/local/tmp/ms_${screenshotId}.png`;
|
|
1205
1200
|
const useShellScreencap = 'number' == typeof this.options?.displayId;
|
|
1206
1201
|
try {
|
|
1207
|
-
if (useShellScreencap
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
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');
|
|
1215
1219
|
}
|
|
1216
1220
|
const validScreenshotBufferSize = this.options?.minScreenshotBufferSize ?? 10240;
|
|
1217
1221
|
if (validScreenshotBufferSize > 0 && screenshotBuffer.length < validScreenshotBufferSize) {
|
|
@@ -1240,12 +1244,21 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1240
1244
|
screenshotBuffer = await external_node_fs_default().promises.readFile(screenshotPath);
|
|
1241
1245
|
const validScreenshotBufferSize = this.options?.minScreenshotBufferSize ?? 10240;
|
|
1242
1246
|
if (!screenshotBuffer || validScreenshotBufferSize > 0 && screenshotBuffer.length < validScreenshotBufferSize) throw new Error(`Fallback screenshot validation failed: buffer size ${screenshotBuffer?.length || 0} bytes (minimum: ${validScreenshotBufferSize})`);
|
|
1243
|
-
if (!(0, img_namespaceObject.
|
|
1247
|
+
if (!(0, img_namespaceObject.isValidImageBuffer)(screenshotBuffer)) throw new Error('Fallback screenshot buffer has invalid PNG format');
|
|
1244
1248
|
debugDevice(`Fallback screenshot validated successfully: ${screenshotBuffer.length} bytes`);
|
|
1245
1249
|
} finally{
|
|
1246
|
-
|
|
1247
|
-
|
|
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);
|
|
1248
1260
|
});
|
|
1261
|
+
child.unref();
|
|
1249
1262
|
}
|
|
1250
1263
|
}
|
|
1251
1264
|
if (!screenshotBuffer) throw new Error('Failed to capture screenshot: all methods failed');
|
|
@@ -1408,24 +1421,32 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1408
1421
|
if (!this.yadbPushed) {
|
|
1409
1422
|
const adb = await this.getAdb();
|
|
1410
1423
|
const androidPkgJson = (0, external_node_module_.createRequire)(__rslib_import_meta_url__).resolve('@aiscene/android/package.json');
|
|
1411
|
-
const
|
|
1412
|
-
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');
|
|
1413
1426
|
this.yadbPushed = true;
|
|
1414
1427
|
}
|
|
1415
1428
|
}
|
|
1416
1429
|
shouldUseYadbForText(text) {
|
|
1417
1430
|
const hasNonAscii = /[\x80-\uFFFF]/.test(text);
|
|
1418
1431
|
const hasFormatSpecifiers = /%[a-zA-Z]/.test(text);
|
|
1419
|
-
|
|
1432
|
+
const hasShellSpecialChars = /[\\`$]/.test(text);
|
|
1433
|
+
const hasBothQuotes = text.includes('"') && text.includes("'");
|
|
1434
|
+
return hasNonAscii || hasFormatSpecifiers || hasShellSpecialChars || hasBothQuotes;
|
|
1420
1435
|
}
|
|
1421
1436
|
async keyboardType(text, options) {
|
|
1422
1437
|
if (!text) return;
|
|
1423
1438
|
const adb = await this.getAdb();
|
|
1424
|
-
const shouldUseYadb = this.shouldUseYadbForText(text);
|
|
1425
1439
|
const IME_STRATEGY = (this.options?.imeStrategy || env_namespaceObject.globalConfigManager.getEnvConfigValue(env_namespaceObject.MIDSCENE_ANDROID_IME_STRATEGY)) ?? IME_STRATEGY_YADB_FOR_NON_ASCII;
|
|
1426
1440
|
const shouldAutoDismissKeyboard = options?.autoDismissKeyboard ?? this.options?.autoDismissKeyboard ?? true;
|
|
1427
|
-
|
|
1428
|
-
|
|
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
|
+
}
|
|
1429
1450
|
if (true === shouldAutoDismissKeyboard) await this.hideKeyboard(options);
|
|
1430
1451
|
}
|
|
1431
1452
|
normalizeKeyName(key) {
|
|
@@ -1473,12 +1494,12 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1473
1494
|
}
|
|
1474
1495
|
async mouseClick(x, y) {
|
|
1475
1496
|
const adb = await this.getAdb();
|
|
1476
|
-
const { x: adjustedX, y: adjustedY } = this.adjustCoordinates(x, y);
|
|
1497
|
+
const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
|
|
1477
1498
|
await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedX} ${adjustedY} ${adjustedX} ${adjustedY} 150`);
|
|
1478
1499
|
}
|
|
1479
1500
|
async mouseDoubleClick(x, y) {
|
|
1480
1501
|
const adb = await this.getAdb();
|
|
1481
|
-
const { x: adjustedX, y: adjustedY } = this.adjustCoordinates(x, y);
|
|
1502
|
+
const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
|
|
1482
1503
|
const tapCommand = `input${this.getDisplayArg()} tap ${adjustedX} ${adjustedY}`;
|
|
1483
1504
|
await adb.shell(tapCommand);
|
|
1484
1505
|
await (0, utils_namespaceObject.sleep)(50);
|
|
@@ -1489,8 +1510,8 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1489
1510
|
}
|
|
1490
1511
|
async mouseDrag(from, to, duration) {
|
|
1491
1512
|
const adb = await this.getAdb();
|
|
1492
|
-
const { x: fromX, y: fromY } = this.adjustCoordinates(from.x, from.y);
|
|
1493
|
-
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);
|
|
1494
1515
|
const swipeDuration = duration ?? defaultNormalScrollDuration;
|
|
1495
1516
|
await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${swipeDuration}`);
|
|
1496
1517
|
}
|
|
@@ -1500,16 +1521,16 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1500
1521
|
const n = 4;
|
|
1501
1522
|
const startX = Math.round(deltaX < 0 ? width / n * (n - 1) : width / n);
|
|
1502
1523
|
const startY = Math.round(deltaY < 0 ? height / n * (n - 1) : height / n);
|
|
1503
|
-
const
|
|
1504
|
-
const
|
|
1505
|
-
const
|
|
1506
|
-
const
|
|
1524
|
+
const maxPositiveDeltaX = startX;
|
|
1525
|
+
const maxNegativeDeltaX = width - startX;
|
|
1526
|
+
const maxPositiveDeltaY = startY;
|
|
1527
|
+
const maxNegativeDeltaY = height - startY;
|
|
1507
1528
|
deltaX = Math.max(-maxNegativeDeltaX, Math.min(deltaX, maxPositiveDeltaX));
|
|
1508
1529
|
deltaY = Math.max(-maxNegativeDeltaY, Math.min(deltaY, maxPositiveDeltaY));
|
|
1509
1530
|
const endX = Math.round(startX - deltaX);
|
|
1510
1531
|
const endY = Math.round(startY - deltaY);
|
|
1511
|
-
const { x: adjustedStartX, y: adjustedStartY } = this.adjustCoordinates(startX, startY);
|
|
1512
|
-
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);
|
|
1513
1534
|
const adb = await this.getAdb();
|
|
1514
1535
|
const swipeDuration = duration ?? defaultNormalScrollDuration;
|
|
1515
1536
|
await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedStartX} ${adjustedStartY} ${adjustedEndX} ${adjustedEndY} ${swipeDuration}`);
|
|
@@ -1520,6 +1541,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1520
1541
|
this.cachedPhysicalDisplayId = void 0;
|
|
1521
1542
|
this.cachedScreenSize = null;
|
|
1522
1543
|
this.cachedOrientation = null;
|
|
1544
|
+
this.scalingRatio = 1;
|
|
1523
1545
|
if (this.scrcpyAdapter) {
|
|
1524
1546
|
await this.scrcpyAdapter.disconnect();
|
|
1525
1547
|
this.scrcpyAdapter = null;
|
|
@@ -1559,7 +1581,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1559
1581
|
}
|
|
1560
1582
|
async longPress(x, y, duration = 2000) {
|
|
1561
1583
|
const adb = await this.getAdb();
|
|
1562
|
-
const { x: adjustedX, y: adjustedY } = this.adjustCoordinates(x, y);
|
|
1584
|
+
const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
|
|
1563
1585
|
await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedX} ${adjustedY} ${adjustedX} ${adjustedY} ${duration}`);
|
|
1564
1586
|
}
|
|
1565
1587
|
async pullDown(startPoint, distance, duration = 800) {
|
|
@@ -1581,8 +1603,8 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1581
1603
|
}
|
|
1582
1604
|
async pullDrag(from, to, duration) {
|
|
1583
1605
|
const adb = await this.getAdb();
|
|
1584
|
-
const { x: fromX, y: fromY } = this.adjustCoordinates(from.x, from.y);
|
|
1585
|
-
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);
|
|
1586
1608
|
await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${duration}`);
|
|
1587
1609
|
}
|
|
1588
1610
|
async pullUp(startPoint, distance, duration = 600) {
|
|
@@ -1669,7 +1691,6 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1669
1691
|
device_define_property(this, "yadbPushed", false);
|
|
1670
1692
|
device_define_property(this, "devicePixelRatio", 1);
|
|
1671
1693
|
device_define_property(this, "devicePixelRatioInitialized", false);
|
|
1672
|
-
device_define_property(this, "scalingRatio", 1);
|
|
1673
1694
|
device_define_property(this, "adb", null);
|
|
1674
1695
|
device_define_property(this, "connectingAdb", null);
|
|
1675
1696
|
device_define_property(this, "destroyed", false);
|
|
@@ -1680,6 +1701,8 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1680
1701
|
device_define_property(this, "cachedPhysicalDisplayId", void 0);
|
|
1681
1702
|
device_define_property(this, "scrcpyAdapter", null);
|
|
1682
1703
|
device_define_property(this, "appNameMapping", {});
|
|
1704
|
+
device_define_property(this, "scalingRatio", 1);
|
|
1705
|
+
device_define_property(this, "takeScreenshotFailCount", 0);
|
|
1683
1706
|
device_define_property(this, "interfaceType", 'android');
|
|
1684
1707
|
device_define_property(this, "uri", void 0);
|
|
1685
1708
|
device_define_property(this, "options", void 0);
|
|
@@ -1689,6 +1712,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1689
1712
|
this.customActions = options?.customActions;
|
|
1690
1713
|
}
|
|
1691
1714
|
}
|
|
1715
|
+
device_define_property(AndroidDevice, "TAKE_SCREENSHOT_FAIL_THRESHOLD", 3);
|
|
1692
1716
|
const runAdbShellParamSchema = core_namespaceObject.z.object({
|
|
1693
1717
|
command: core_namespaceObject.z.string().describe('ADB shell command to execute')
|
|
1694
1718
|
});
|
|
@@ -1701,6 +1725,9 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1701
1725
|
description: 'Execute ADB shell command on Android device',
|
|
1702
1726
|
interfaceAlias: 'runAdbShell',
|
|
1703
1727
|
paramSchema: runAdbShellParamSchema,
|
|
1728
|
+
sample: {
|
|
1729
|
+
command: 'dumpsys window displays | grep -E "mCurrentFocus"'
|
|
1730
|
+
},
|
|
1704
1731
|
call: async (param)=>{
|
|
1705
1732
|
if (!param.command || '' === param.command.trim()) throw new Error('RunAdbShell requires a non-empty command parameter');
|
|
1706
1733
|
const adb = await device.getAdb();
|
|
@@ -1712,6 +1739,9 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1712
1739
|
description: 'Launch an Android app or URL',
|
|
1713
1740
|
interfaceAlias: 'launch',
|
|
1714
1741
|
paramSchema: launchParamSchema,
|
|
1742
|
+
sample: {
|
|
1743
|
+
uri: 'com.example.app'
|
|
1744
|
+
},
|
|
1715
1745
|
call: async (param)=>{
|
|
1716
1746
|
if (!param.uri || '' === param.uri.trim()) throw new Error('Launch requires a non-empty uri parameter');
|
|
1717
1747
|
await device.launch(param.uri);
|