@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/cli.js
CHANGED
|
@@ -550,6 +550,7 @@ var __webpack_exports__ = {};
|
|
|
550
550
|
};
|
|
551
551
|
const external_node_assert_namespaceObject = require("node:assert");
|
|
552
552
|
var external_node_assert_default = /*#__PURE__*/ __webpack_require__.n(external_node_assert_namespaceObject);
|
|
553
|
+
const external_node_child_process_namespaceObject = require("node:child_process");
|
|
553
554
|
var external_node_fs_ = __webpack_require__("node:fs");
|
|
554
555
|
var external_node_fs_default = /*#__PURE__*/ __webpack_require__.n(external_node_fs_);
|
|
555
556
|
var external_node_module_ = __webpack_require__("node:module");
|
|
@@ -690,6 +691,9 @@ var __webpack_exports__ = {};
|
|
|
690
691
|
const IME_STRATEGY_ALWAYS_YADB = 'always-yadb';
|
|
691
692
|
const IME_STRATEGY_YADB_FOR_NON_ASCII = 'yadb-for-non-ascii';
|
|
692
693
|
const debugDevice = (0, logger_.getDebug)('android:device');
|
|
694
|
+
function escapeForShell(text) {
|
|
695
|
+
return text.replace(/'/g, "'\\''").replace(/\n/g, '\\n');
|
|
696
|
+
}
|
|
693
697
|
class AndroidDevice {
|
|
694
698
|
actionSpace() {
|
|
695
699
|
const defaultActions = [
|
|
@@ -717,6 +721,12 @@ var __webpack_exports__ = {};
|
|
|
717
721
|
]).default('replace').optional().describe('Input mode: "replace" (default) - clear the field and input the value; "typeOnly" - type the value directly without clearing the field first; "clear" - clear the field without inputting new text.')),
|
|
718
722
|
locate: (0, core_namespaceObject.getMidsceneLocationSchema)().describe('The input field to be filled').optional()
|
|
719
723
|
}),
|
|
724
|
+
sample: {
|
|
725
|
+
value: 'test@example.com',
|
|
726
|
+
locate: {
|
|
727
|
+
prompt: 'the email input field'
|
|
728
|
+
}
|
|
729
|
+
},
|
|
720
730
|
call: async (param)=>{
|
|
721
731
|
const element = param.locate;
|
|
722
732
|
if ('typeOnly' !== param.mode) await this.clearInput(element);
|
|
@@ -762,9 +772,21 @@ var __webpack_exports__ = {};
|
|
|
762
772
|
y: to.center[1]
|
|
763
773
|
});
|
|
764
774
|
}),
|
|
775
|
+
(0, device_namespaceObject.defineActionSwipe)(async (param)=>{
|
|
776
|
+
const { startPoint, endPoint, duration, repeatCount } = (0, device_namespaceObject.normalizeMobileSwipeParam)(param, await this.size());
|
|
777
|
+
for(let i = 0; i < repeatCount; i++)await this.mouseDrag(startPoint, endPoint, duration);
|
|
778
|
+
}),
|
|
765
779
|
(0, device_namespaceObject.defineActionKeyboardPress)(async (param)=>{
|
|
766
780
|
await this.keyboardPress(param.keyName);
|
|
767
781
|
}),
|
|
782
|
+
(0, device_namespaceObject.defineActionCursorMove)(async (param)=>{
|
|
783
|
+
const arrowKey = 'left' === param.direction ? 'ArrowLeft' : 'ArrowRight';
|
|
784
|
+
const times = param.times ?? 1;
|
|
785
|
+
for(let i = 0; i < times; i++){
|
|
786
|
+
await this.keyboardPress(arrowKey);
|
|
787
|
+
await (0, core_utils_namespaceObject.sleep)(100);
|
|
788
|
+
}
|
|
789
|
+
}),
|
|
768
790
|
(0, device_namespaceObject.defineAction)({
|
|
769
791
|
name: 'LongPress',
|
|
770
792
|
description: 'Trigger a long press on the screen at specified element',
|
|
@@ -772,6 +794,11 @@ var __webpack_exports__ = {};
|
|
|
772
794
|
duration: core_namespaceObject.z.number().optional().describe('The duration of the long press in milliseconds'),
|
|
773
795
|
locate: (0, core_namespaceObject.getMidsceneLocationSchema)().describe('The element to be long pressed')
|
|
774
796
|
}),
|
|
797
|
+
sample: {
|
|
798
|
+
locate: {
|
|
799
|
+
prompt: 'the message bubble'
|
|
800
|
+
}
|
|
801
|
+
},
|
|
775
802
|
call: async (param)=>{
|
|
776
803
|
const element = param.locate;
|
|
777
804
|
if (!element) throw new Error('LongPress requires an element to be located');
|
|
@@ -791,6 +818,12 @@ var __webpack_exports__ = {};
|
|
|
791
818
|
duration: core_namespaceObject.z.number().optional().describe('The duration of the pull (in milliseconds)'),
|
|
792
819
|
locate: (0, core_namespaceObject.getMidsceneLocationSchema)().optional().describe('The element to start the pull from (optional)')
|
|
793
820
|
}),
|
|
821
|
+
sample: {
|
|
822
|
+
direction: 'down',
|
|
823
|
+
locate: {
|
|
824
|
+
prompt: 'the center of the content list area'
|
|
825
|
+
}
|
|
826
|
+
},
|
|
794
827
|
call: async (param)=>{
|
|
795
828
|
const element = param.locate;
|
|
796
829
|
const startPoint = element ? {
|
|
@@ -803,6 +836,16 @@ var __webpack_exports__ = {};
|
|
|
803
836
|
else throw new Error(`Unknown pull direction: ${param.direction}`);
|
|
804
837
|
}
|
|
805
838
|
}),
|
|
839
|
+
(0, device_namespaceObject.defineActionPinch)(async (param)=>{
|
|
840
|
+
const { centerX, centerY, startDistance, endDistance, duration } = (0, device_namespaceObject.normalizePinchParam)(param, await this.size());
|
|
841
|
+
const { x: adjCenterX, y: adjCenterY } = await this.adjustCoordinates(centerX, centerY);
|
|
842
|
+
const ratio = 0 !== adjCenterX && 0 !== centerX ? adjCenterX / centerX : 1;
|
|
843
|
+
const adjStartDist = Math.round(startDistance * ratio);
|
|
844
|
+
const adjEndDist = Math.round(endDistance * ratio);
|
|
845
|
+
await this.ensureYadb();
|
|
846
|
+
const adb = await this.getAdb();
|
|
847
|
+
await adb.shell(`app_process${this.getDisplayArg()} -Djava.class.path=/data/local/tmp/yadb /data/local/tmp com.ysbing.yadb.Main -pinch ${adjCenterX} ${adjCenterY} ${adjStartDist} ${adjEndDist} ${duration}`);
|
|
848
|
+
}),
|
|
806
849
|
(0, device_namespaceObject.defineActionClearInput)(async (param)=>{
|
|
807
850
|
await this.clearInput(param.locate);
|
|
808
851
|
})
|
|
@@ -947,66 +990,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
947
990
|
async execYadb(keyboardContent) {
|
|
948
991
|
await this.ensureYadb();
|
|
949
992
|
const adb = await this.getAdb();
|
|
950
|
-
|
|
951
|
-
const command = `app_process${this.getDisplayArg()} -Djava.class.path=/data/local/tmp/yadb /data/local/tmp com.ysbing.yadb.Main -keyboard "${keyboardContent}"`;
|
|
952
|
-
debugDevice(`Executing YADB input: "${keyboardContent}"`);
|
|
953
|
-
const inputPromise = adb.shell(command);
|
|
954
|
-
const timeoutPromise = new Promise((_, reject)=>setTimeout(()=>reject(new Error('YADB timeout')), 1000));
|
|
955
|
-
await Promise.race([
|
|
956
|
-
inputPromise,
|
|
957
|
-
timeoutPromise
|
|
958
|
-
]);
|
|
959
|
-
debugDevice(`YADB input completed: "${keyboardContent}"`);
|
|
960
|
-
} catch (error) {
|
|
961
|
-
const isAccessibilityConflict = error?.cause?.stderr?.includes('UiAutomationService') || error?.cause?.stderr?.includes('already registered') || error?.message?.includes('UiAutomationService');
|
|
962
|
-
if (isAccessibilityConflict) {
|
|
963
|
-
debugDevice("YADB failed due to AccessibilityService conflict (likely Appium running), falling back to clipboard method");
|
|
964
|
-
await this.inputViaClipboard(keyboardContent);
|
|
965
|
-
} else 'YADB timeout' === error.message ? debugDevice(`YADB timed out after 2s, assuming input succeeded: "${keyboardContent}"`) : debugDevice(`YADB execution may have completed despite error: ${error}`);
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
async inputViaClipboard(text) {
|
|
969
|
-
const adb = await this.getAdb();
|
|
970
|
-
try {
|
|
971
|
-
debugDevice(`Inputting via clipboard: "${text}"`);
|
|
972
|
-
const escapedText = text.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
973
|
-
const setClipboardCmd = `
|
|
974
|
-
content insert --uri content://settings/system --bind name:s:clipboard_text --bind value:s:"${escapedText}"
|
|
975
|
-
`;
|
|
976
|
-
await adb.shell(setClipboardCmd);
|
|
977
|
-
await (0, core_utils_namespaceObject.sleep)(100);
|
|
978
|
-
await adb.shell('input keyevent KEYCODE_PASTE');
|
|
979
|
-
await (0, core_utils_namespaceObject.sleep)(100);
|
|
980
|
-
debugDevice(`Clipboard input completed via content provider: "${text}"`);
|
|
981
|
-
} catch (error1) {
|
|
982
|
-
debugDevice(`Content provider clipboard failed, trying clipper app: ${error1}`);
|
|
983
|
-
try {
|
|
984
|
-
const base64Text = Buffer.from(text, 'utf-8').toString('base64');
|
|
985
|
-
await adb.shell(`am broadcast -a clipper.set -e text "${base64Text}"`);
|
|
986
|
-
await (0, core_utils_namespaceObject.sleep)(100);
|
|
987
|
-
await adb.shell('input keyevent KEYCODE_PASTE');
|
|
988
|
-
await (0, core_utils_namespaceObject.sleep)(100);
|
|
989
|
-
debugDevice(`Clipboard input completed via clipper: "${text}"`);
|
|
990
|
-
} catch (error2) {
|
|
991
|
-
debugDevice(`All clipboard methods failed: ${error2}`);
|
|
992
|
-
const isPureAscii = /^[\x00-\x7F]*$/.test(text);
|
|
993
|
-
if (isPureAscii) {
|
|
994
|
-
debugDevice(`Using ADB inputText for ASCII text: "${text}"`);
|
|
995
|
-
await adb.inputText(text);
|
|
996
|
-
} else await this.inputCharByChar(text);
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
async inputCharByChar(text) {
|
|
1001
|
-
const adb = await this.getAdb();
|
|
1002
|
-
debugDevice(`Inputting character by character (slow method): "${text}"`);
|
|
1003
|
-
const chars = Array.from(text);
|
|
1004
|
-
for (const char of chars){
|
|
1005
|
-
if (' ' === char) await adb.shell('input keyevent KEYCODE_SPACE');
|
|
1006
|
-
else await adb.shell(`input text "${char}"`);
|
|
1007
|
-
await (0, core_utils_namespaceObject.sleep)(50);
|
|
1008
|
-
}
|
|
1009
|
-
debugDevice("Character-by-character input completed");
|
|
993
|
+
await adb.shell(`app_process${this.getDisplayArg()} -Djava.class.path=/data/local/tmp/yadb /data/local/tmp com.ysbing.yadb.Main -keyboard '${keyboardContent}'`);
|
|
1010
994
|
}
|
|
1011
995
|
async getElementsInfo() {
|
|
1012
996
|
return [];
|
|
@@ -1019,6 +1003,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1019
1003
|
}
|
|
1020
1004
|
async getScreenSize() {
|
|
1021
1005
|
const shouldCache = !(this.options?.alwaysRefreshScreenInfo ?? false);
|
|
1006
|
+
debugDevice(`getScreenSize: alwaysRefreshScreenInfo=${this.options?.alwaysRefreshScreenInfo}, shouldCache=${shouldCache}, hasCachedSize=${!!this.cachedScreenSize}`);
|
|
1022
1007
|
if (shouldCache && this.cachedScreenSize) return this.cachedScreenSize;
|
|
1023
1008
|
const adb = await this.getAdb();
|
|
1024
1009
|
if ('number' == typeof this.options?.displayId) try {
|
|
@@ -1152,6 +1137,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1152
1137
|
}
|
|
1153
1138
|
async getDisplayOrientation() {
|
|
1154
1139
|
const shouldCache = !(this.options?.alwaysRefreshScreenInfo ?? false);
|
|
1140
|
+
debugDevice(`getDisplayOrientation: alwaysRefreshScreenInfo=${this.options?.alwaysRefreshScreenInfo}, shouldCache=${shouldCache}, hasCachedOrientation=${null !== this.cachedOrientation}`);
|
|
1155
1141
|
if (shouldCache && null !== this.cachedOrientation) return this.cachedOrientation;
|
|
1156
1142
|
const adb = await this.getAdb();
|
|
1157
1143
|
let orientation = 0;
|
|
@@ -1177,6 +1163,15 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1177
1163
|
if (shouldCache) this.cachedOrientation = orientation;
|
|
1178
1164
|
return orientation;
|
|
1179
1165
|
}
|
|
1166
|
+
async getOrientedPhysicalSize() {
|
|
1167
|
+
const info = await this.getDevicePhysicalInfo();
|
|
1168
|
+
const isLandscape = 1 === info.orientation || 3 === info.orientation;
|
|
1169
|
+
const shouldSwap = true !== info.isCurrentOrientation && isLandscape;
|
|
1170
|
+
return {
|
|
1171
|
+
width: shouldSwap ? info.physicalHeight : info.physicalWidth,
|
|
1172
|
+
height: shouldSwap ? info.physicalWidth : info.physicalHeight
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1180
1175
|
async size() {
|
|
1181
1176
|
const deviceInfo = await this.getDevicePhysicalInfo();
|
|
1182
1177
|
const adapter = this.getScrcpyAdapter();
|
|
@@ -1203,7 +1198,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1203
1198
|
height: logicalHeight
|
|
1204
1199
|
};
|
|
1205
1200
|
}
|
|
1206
|
-
async cacheFeatureForPoint(center
|
|
1201
|
+
async cacheFeatureForPoint(center) {
|
|
1207
1202
|
const { width, height } = await this.size();
|
|
1208
1203
|
debugDevice('cacheFeatureForPoint: center=[%s,%s], screen=[%s,%s]', center[0], center[1], width, height);
|
|
1209
1204
|
return {
|
|
@@ -1283,14 +1278,23 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1283
1278
|
const androidScreenshotPath = `/data/local/tmp/ms_${screenshotId}.png`;
|
|
1284
1279
|
const useShellScreencap = 'number' == typeof this.options?.displayId;
|
|
1285
1280
|
try {
|
|
1286
|
-
if (useShellScreencap
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1281
|
+
if (!useShellScreencap && this.takeScreenshotFailCount < AndroidDevice.TAKE_SCREENSHOT_FAIL_THRESHOLD) {
|
|
1282
|
+
debugDevice('Taking screenshot via adb.takeScreenshot');
|
|
1283
|
+
screenshotBuffer = await adb.takeScreenshot(null);
|
|
1284
|
+
debugDevice('adb.takeScreenshot completed');
|
|
1285
|
+
if (!screenshotBuffer) {
|
|
1286
|
+
this.takeScreenshotFailCount++;
|
|
1287
|
+
throw new Error('Failed to capture screenshot: screenshotBuffer is null');
|
|
1288
|
+
}
|
|
1289
|
+
if (!(0, img_namespaceObject.isValidImageBuffer)(screenshotBuffer)) {
|
|
1290
|
+
debugDevice('Invalid image buffer detected: not a valid image format');
|
|
1291
|
+
this.takeScreenshotFailCount++;
|
|
1292
|
+
throw new Error('Screenshot buffer has invalid format: could not find valid image signature');
|
|
1293
|
+
}
|
|
1294
|
+
this.takeScreenshotFailCount = 0;
|
|
1295
|
+
} else {
|
|
1296
|
+
if (this.takeScreenshotFailCount >= AndroidDevice.TAKE_SCREENSHOT_FAIL_THRESHOLD) debugDevice('Skipping takeScreenshot (failed %d consecutive times), using shell screencap directly', this.takeScreenshotFailCount);
|
|
1297
|
+
throw new Error('Using shell screencap directly');
|
|
1294
1298
|
}
|
|
1295
1299
|
const validScreenshotBufferSize = this.options?.minScreenshotBufferSize ?? 10240;
|
|
1296
1300
|
if (validScreenshotBufferSize > 0 && screenshotBuffer.length < validScreenshotBufferSize) {
|
|
@@ -1319,12 +1323,21 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1319
1323
|
screenshotBuffer = await external_node_fs_default().promises.readFile(screenshotPath);
|
|
1320
1324
|
const validScreenshotBufferSize = this.options?.minScreenshotBufferSize ?? 10240;
|
|
1321
1325
|
if (!screenshotBuffer || validScreenshotBufferSize > 0 && screenshotBuffer.length < validScreenshotBufferSize) throw new Error(`Fallback screenshot validation failed: buffer size ${screenshotBuffer?.length || 0} bytes (minimum: ${validScreenshotBufferSize})`);
|
|
1322
|
-
if (!(0, img_namespaceObject.
|
|
1326
|
+
if (!(0, img_namespaceObject.isValidImageBuffer)(screenshotBuffer)) throw new Error('Fallback screenshot buffer has invalid PNG format');
|
|
1323
1327
|
debugDevice(`Fallback screenshot validated successfully: ${screenshotBuffer.length} bytes`);
|
|
1324
1328
|
} finally{
|
|
1325
|
-
|
|
1326
|
-
|
|
1329
|
+
const adbPath = adb.executable?.path ?? 'adb';
|
|
1330
|
+
const child = (0, external_node_child_process_namespaceObject.execFile)(adbPath, [
|
|
1331
|
+
'-s',
|
|
1332
|
+
this.deviceId,
|
|
1333
|
+
'shell',
|
|
1334
|
+
`rm ${androidScreenshotPath}`
|
|
1335
|
+
], {
|
|
1336
|
+
timeout: 3000
|
|
1337
|
+
}, (err)=>{
|
|
1338
|
+
if (err) debugDevice('Failed to delete remote screenshot: %s', err.message);
|
|
1327
1339
|
});
|
|
1340
|
+
child.unref();
|
|
1328
1341
|
}
|
|
1329
1342
|
}
|
|
1330
1343
|
if (!screenshotBuffer) throw new Error('Failed to capture screenshot: all methods failed');
|
|
@@ -1487,24 +1500,32 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1487
1500
|
if (!this.yadbPushed) {
|
|
1488
1501
|
const adb = await this.getAdb();
|
|
1489
1502
|
const androidPkgJson = (0, external_node_module_.createRequire)(__rslib_import_meta_url__).resolve('@aiscene/android/package.json');
|
|
1490
|
-
const
|
|
1491
|
-
await adb.push(
|
|
1503
|
+
const yadbBin = external_node_path_default().join(external_node_path_default().dirname(androidPkgJson), 'bin', 'yadb');
|
|
1504
|
+
await adb.push(yadbBin, '/data/local/tmp');
|
|
1492
1505
|
this.yadbPushed = true;
|
|
1493
1506
|
}
|
|
1494
1507
|
}
|
|
1495
1508
|
shouldUseYadbForText(text) {
|
|
1496
1509
|
const hasNonAscii = /[\x80-\uFFFF]/.test(text);
|
|
1497
1510
|
const hasFormatSpecifiers = /%[a-zA-Z]/.test(text);
|
|
1498
|
-
|
|
1511
|
+
const hasShellSpecialChars = /[\\`$]/.test(text);
|
|
1512
|
+
const hasBothQuotes = text.includes('"') && text.includes("'");
|
|
1513
|
+
return hasNonAscii || hasFormatSpecifiers || hasShellSpecialChars || hasBothQuotes;
|
|
1499
1514
|
}
|
|
1500
1515
|
async keyboardType(text, options) {
|
|
1501
1516
|
if (!text) return;
|
|
1502
1517
|
const adb = await this.getAdb();
|
|
1503
|
-
const shouldUseYadb = this.shouldUseYadbForText(text);
|
|
1504
1518
|
const IME_STRATEGY = (this.options?.imeStrategy || env_namespaceObject.globalConfigManager.getEnvConfigValue(env_namespaceObject.MIDSCENE_ANDROID_IME_STRATEGY)) ?? IME_STRATEGY_YADB_FOR_NON_ASCII;
|
|
1505
1519
|
const shouldAutoDismissKeyboard = options?.autoDismissKeyboard ?? this.options?.autoDismissKeyboard ?? true;
|
|
1506
|
-
|
|
1507
|
-
|
|
1520
|
+
const useYadb = IME_STRATEGY === IME_STRATEGY_ALWAYS_YADB || IME_STRATEGY === IME_STRATEGY_YADB_FOR_NON_ASCII && this.shouldUseYadbForText(text);
|
|
1521
|
+
if (useYadb) await this.execYadb(escapeForShell(text));
|
|
1522
|
+
else {
|
|
1523
|
+
const segments = text.split('\n');
|
|
1524
|
+
for(let i = 0; i < segments.length; i++){
|
|
1525
|
+
if (segments[i].length > 0) await adb.inputText(segments[i]);
|
|
1526
|
+
if (i < segments.length - 1) await adb.keyevent(66);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1508
1529
|
if (true === shouldAutoDismissKeyboard) await this.hideKeyboard(options);
|
|
1509
1530
|
}
|
|
1510
1531
|
normalizeKeyName(key) {
|
|
@@ -1552,12 +1573,12 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1552
1573
|
}
|
|
1553
1574
|
async mouseClick(x, y) {
|
|
1554
1575
|
const adb = await this.getAdb();
|
|
1555
|
-
const { x: adjustedX, y: adjustedY } = this.adjustCoordinates(x, y);
|
|
1576
|
+
const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
|
|
1556
1577
|
await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedX} ${adjustedY} ${adjustedX} ${adjustedY} 150`);
|
|
1557
1578
|
}
|
|
1558
1579
|
async mouseDoubleClick(x, y) {
|
|
1559
1580
|
const adb = await this.getAdb();
|
|
1560
|
-
const { x: adjustedX, y: adjustedY } = this.adjustCoordinates(x, y);
|
|
1581
|
+
const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
|
|
1561
1582
|
const tapCommand = `input${this.getDisplayArg()} tap ${adjustedX} ${adjustedY}`;
|
|
1562
1583
|
await adb.shell(tapCommand);
|
|
1563
1584
|
await (0, core_utils_namespaceObject.sleep)(50);
|
|
@@ -1568,8 +1589,8 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1568
1589
|
}
|
|
1569
1590
|
async mouseDrag(from, to, duration) {
|
|
1570
1591
|
const adb = await this.getAdb();
|
|
1571
|
-
const { x: fromX, y: fromY } = this.adjustCoordinates(from.x, from.y);
|
|
1572
|
-
const { x: toX, y: toY } = this.adjustCoordinates(to.x, to.y);
|
|
1592
|
+
const { x: fromX, y: fromY } = await this.adjustCoordinates(from.x, from.y);
|
|
1593
|
+
const { x: toX, y: toY } = await this.adjustCoordinates(to.x, to.y);
|
|
1573
1594
|
const swipeDuration = duration ?? defaultNormalScrollDuration;
|
|
1574
1595
|
await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${swipeDuration}`);
|
|
1575
1596
|
}
|
|
@@ -1579,16 +1600,16 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1579
1600
|
const n = 4;
|
|
1580
1601
|
const startX = Math.round(deltaX < 0 ? width / n * (n - 1) : width / n);
|
|
1581
1602
|
const startY = Math.round(deltaY < 0 ? height / n * (n - 1) : height / n);
|
|
1582
|
-
const
|
|
1583
|
-
const
|
|
1584
|
-
const
|
|
1585
|
-
const
|
|
1603
|
+
const maxPositiveDeltaX = startX;
|
|
1604
|
+
const maxNegativeDeltaX = width - startX;
|
|
1605
|
+
const maxPositiveDeltaY = startY;
|
|
1606
|
+
const maxNegativeDeltaY = height - startY;
|
|
1586
1607
|
deltaX = Math.max(-maxNegativeDeltaX, Math.min(deltaX, maxPositiveDeltaX));
|
|
1587
1608
|
deltaY = Math.max(-maxNegativeDeltaY, Math.min(deltaY, maxPositiveDeltaY));
|
|
1588
1609
|
const endX = Math.round(startX - deltaX);
|
|
1589
1610
|
const endY = Math.round(startY - deltaY);
|
|
1590
|
-
const { x: adjustedStartX, y: adjustedStartY } = this.adjustCoordinates(startX, startY);
|
|
1591
|
-
const { x: adjustedEndX, y: adjustedEndY } = this.adjustCoordinates(endX, endY);
|
|
1611
|
+
const { x: adjustedStartX, y: adjustedStartY } = await this.adjustCoordinates(startX, startY);
|
|
1612
|
+
const { x: adjustedEndX, y: adjustedEndY } = await this.adjustCoordinates(endX, endY);
|
|
1592
1613
|
const adb = await this.getAdb();
|
|
1593
1614
|
const swipeDuration = duration ?? defaultNormalScrollDuration;
|
|
1594
1615
|
await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedStartX} ${adjustedStartY} ${adjustedEndX} ${adjustedEndY} ${swipeDuration}`);
|
|
@@ -1599,6 +1620,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1599
1620
|
this.cachedPhysicalDisplayId = void 0;
|
|
1600
1621
|
this.cachedScreenSize = null;
|
|
1601
1622
|
this.cachedOrientation = null;
|
|
1623
|
+
this.scalingRatio = 1;
|
|
1602
1624
|
if (this.scrcpyAdapter) {
|
|
1603
1625
|
await this.scrcpyAdapter.disconnect();
|
|
1604
1626
|
this.scrcpyAdapter = null;
|
|
@@ -1638,7 +1660,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1638
1660
|
}
|
|
1639
1661
|
async longPress(x, y, duration = 2000) {
|
|
1640
1662
|
const adb = await this.getAdb();
|
|
1641
|
-
const { x: adjustedX, y: adjustedY } = this.adjustCoordinates(x, y);
|
|
1663
|
+
const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
|
|
1642
1664
|
await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedX} ${adjustedY} ${adjustedX} ${adjustedY} ${duration}`);
|
|
1643
1665
|
}
|
|
1644
1666
|
async pullDown(startPoint, distance, duration = 800) {
|
|
@@ -1660,8 +1682,8 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1660
1682
|
}
|
|
1661
1683
|
async pullDrag(from, to, duration) {
|
|
1662
1684
|
const adb = await this.getAdb();
|
|
1663
|
-
const { x: fromX, y: fromY } = this.adjustCoordinates(from.x, from.y);
|
|
1664
|
-
const { x: toX, y: toY } = this.adjustCoordinates(to.x, to.y);
|
|
1685
|
+
const { x: fromX, y: fromY } = await this.adjustCoordinates(from.x, from.y);
|
|
1686
|
+
const { x: toX, y: toY } = await this.adjustCoordinates(to.x, to.y);
|
|
1665
1687
|
await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${duration}`);
|
|
1666
1688
|
}
|
|
1667
1689
|
async pullUp(startPoint, distance, duration = 600) {
|
|
@@ -1748,7 +1770,6 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1748
1770
|
device_define_property(this, "yadbPushed", false);
|
|
1749
1771
|
device_define_property(this, "devicePixelRatio", 1);
|
|
1750
1772
|
device_define_property(this, "devicePixelRatioInitialized", false);
|
|
1751
|
-
device_define_property(this, "scalingRatio", 1);
|
|
1752
1773
|
device_define_property(this, "adb", null);
|
|
1753
1774
|
device_define_property(this, "connectingAdb", null);
|
|
1754
1775
|
device_define_property(this, "destroyed", false);
|
|
@@ -1759,6 +1780,8 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1759
1780
|
device_define_property(this, "cachedPhysicalDisplayId", void 0);
|
|
1760
1781
|
device_define_property(this, "scrcpyAdapter", null);
|
|
1761
1782
|
device_define_property(this, "appNameMapping", {});
|
|
1783
|
+
device_define_property(this, "scalingRatio", 1);
|
|
1784
|
+
device_define_property(this, "takeScreenshotFailCount", 0);
|
|
1762
1785
|
device_define_property(this, "interfaceType", 'android');
|
|
1763
1786
|
device_define_property(this, "uri", void 0);
|
|
1764
1787
|
device_define_property(this, "options", void 0);
|
|
@@ -1768,6 +1791,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1768
1791
|
this.customActions = options?.customActions;
|
|
1769
1792
|
}
|
|
1770
1793
|
}
|
|
1794
|
+
device_define_property(AndroidDevice, "TAKE_SCREENSHOT_FAIL_THRESHOLD", 3);
|
|
1771
1795
|
const runAdbShellParamSchema = core_namespaceObject.z.object({
|
|
1772
1796
|
command: core_namespaceObject.z.string().describe('ADB shell command to execute')
|
|
1773
1797
|
});
|
|
@@ -1780,6 +1804,9 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1780
1804
|
description: 'Execute ADB shell command on Android device',
|
|
1781
1805
|
interfaceAlias: 'runAdbShell',
|
|
1782
1806
|
paramSchema: runAdbShellParamSchema,
|
|
1807
|
+
sample: {
|
|
1808
|
+
command: 'dumpsys window displays | grep -E "mCurrentFocus"'
|
|
1809
|
+
},
|
|
1783
1810
|
call: async (param)=>{
|
|
1784
1811
|
if (!param.command || '' === param.command.trim()) throw new Error('RunAdbShell requires a non-empty command parameter');
|
|
1785
1812
|
const adb = await device.getAdb();
|
|
@@ -1791,6 +1818,9 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1791
1818
|
description: 'Launch an Android app or URL',
|
|
1792
1819
|
interfaceAlias: 'launch',
|
|
1793
1820
|
paramSchema: launchParamSchema,
|
|
1821
|
+
sample: {
|
|
1822
|
+
uri: 'com.example.app'
|
|
1823
|
+
},
|
|
1794
1824
|
call: async (param)=>{
|
|
1795
1825
|
if (!param.uri || '' === param.uri.trim()) throw new Error('Launch requires a non-empty uri parameter');
|
|
1796
1826
|
await device.launch(param.uri);
|
|
@@ -1939,7 +1969,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1939
1969
|
const tools = new AndroidMidsceneTools();
|
|
1940
1970
|
(0, cli_namespaceObject.runToolsCLI)(tools, 'midscene-android', {
|
|
1941
1971
|
stripPrefix: 'android_',
|
|
1942
|
-
version: "1.6.
|
|
1972
|
+
version: "1.6.9"
|
|
1943
1973
|
}).catch((e)=>{
|
|
1944
1974
|
if (!(e instanceof cli_namespaceObject.CLIError)) console.error(e);
|
|
1945
1975
|
process.exit(e instanceof cli_namespaceObject.CLIError ? e.exitCode : 1);
|