@midscene/android 1.6.4 → 1.7.1-beta-20260408073050.0
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/bin/midscene-ime.apk +0 -0
- package/dist/es/cli.mjs +137 -25
- package/dist/es/index.mjs +136 -24
- package/dist/es/mcp-server.mjs +137 -25
- package/dist/lib/cli.js +137 -25
- package/dist/lib/index.js +136 -24
- package/dist/lib/mcp-server.js +137 -25
- package/dist/types/index.d.ts +12 -0
- package/dist/types/mcp-server.d.ts +12 -0
- package/package.json +5 -5
package/dist/es/mcp-server.mjs
CHANGED
|
@@ -674,6 +674,18 @@ const defaultFastScrollDuration = 100;
|
|
|
674
674
|
const defaultNormalScrollDuration = 1000;
|
|
675
675
|
const IME_STRATEGY_ALWAYS_YADB = 'always-yadb';
|
|
676
676
|
const IME_STRATEGY_YADB_FOR_NON_ASCII = 'yadb-for-non-ascii';
|
|
677
|
+
const MIDSCENE_IME_PACKAGE = 'com.midscene.ime';
|
|
678
|
+
const MIDSCENE_IME_ID = `${MIDSCENE_IME_PACKAGE}/.MidsceneIME`;
|
|
679
|
+
const MIDSCENE_IME_DISMISS_ACTION = `${MIDSCENE_IME_PACKAGE}.DISMISS`;
|
|
680
|
+
const AUTO_DISMISS_KEYBOARD_SCHEMA = z.union([
|
|
681
|
+
z.boolean(),
|
|
682
|
+
z["enum"]([
|
|
683
|
+
'back',
|
|
684
|
+
'esc',
|
|
685
|
+
'midscene-ime',
|
|
686
|
+
'midscene-ime-auto-install'
|
|
687
|
+
])
|
|
688
|
+
]).optional().describe("Dismiss the keyboard after input. Use `true` for the default ESC-based behavior, `false` to leave the keyboard open, `'back'`/`'esc'` for explicit key-event strategies, or `'midscene-ime'` / `'midscene-ime-auto-install'` for the zero-key-event helper IME flow.");
|
|
677
689
|
const debugDevice = (0, logger_.getDebug)('android:device');
|
|
678
690
|
function escapeForShell(text) {
|
|
679
691
|
return text.replace(/'/g, "'\\''").replace(/\n/g, '\\n');
|
|
@@ -697,7 +709,7 @@ class AndroidDevice {
|
|
|
697
709
|
interfaceAlias: 'aiInput',
|
|
698
710
|
paramSchema: z.object({
|
|
699
711
|
value: z.string().describe('The text to input. Provide the final content for replace/append modes, or an empty string when using clear mode to remove existing text.'),
|
|
700
|
-
autoDismissKeyboard:
|
|
712
|
+
autoDismissKeyboard: AUTO_DISMISS_KEYBOARD_SCHEMA,
|
|
701
713
|
mode: z.preprocess((val)=>'append' === val ? 'typeOnly' : val, z["enum"]([
|
|
702
714
|
'replace',
|
|
703
715
|
'clear',
|
|
@@ -943,6 +955,9 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
943
955
|
setAppNameMapping(mapping) {
|
|
944
956
|
this.appNameMapping = mapping;
|
|
945
957
|
}
|
|
958
|
+
setInstallApprovalHandler(handler) {
|
|
959
|
+
this.installApprovalHandler = handler;
|
|
960
|
+
}
|
|
946
961
|
resolvePackageName(appName) {
|
|
947
962
|
const normalizedAppName = normalizeForComparison(appName);
|
|
948
963
|
return this.appNameMapping[normalizedAppName];
|
|
@@ -1481,7 +1496,8 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1481
1496
|
if (!text) return;
|
|
1482
1497
|
const adb = await this.getAdb();
|
|
1483
1498
|
const IME_STRATEGY = (this.options?.imeStrategy || globalConfigManager.getEnvConfigValue(MIDSCENE_ANDROID_IME_STRATEGY)) ?? IME_STRATEGY_YADB_FOR_NON_ASCII;
|
|
1484
|
-
const
|
|
1499
|
+
const autoDismiss = options?.autoDismissKeyboard ?? this.options?.autoDismissKeyboard ?? 'esc';
|
|
1500
|
+
const shouldAutoDismissKeyboard = false !== autoDismiss;
|
|
1485
1501
|
const useYadb = IME_STRATEGY === IME_STRATEGY_ALWAYS_YADB || IME_STRATEGY === IME_STRATEGY_YADB_FOR_NON_ASCII && this.shouldUseYadbForText(text);
|
|
1486
1502
|
if (useYadb) await this.execYadb(escapeForShell(text), {
|
|
1487
1503
|
overwrite: options?.overwrite
|
|
@@ -1599,6 +1615,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1599
1615
|
}
|
|
1600
1616
|
this.connectingAdb = null;
|
|
1601
1617
|
this.yadbPushed = false;
|
|
1618
|
+
this.midsceneImeInstalled = false;
|
|
1602
1619
|
}
|
|
1603
1620
|
async getTimestamp() {
|
|
1604
1621
|
const adb = await this.getAdb();
|
|
@@ -1698,43 +1715,135 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1698
1715
|
return null;
|
|
1699
1716
|
}
|
|
1700
1717
|
}
|
|
1718
|
+
resolveAutoDismissKeyboard(options) {
|
|
1719
|
+
const raw = options?.autoDismissKeyboard ?? this.options?.autoDismissKeyboard;
|
|
1720
|
+
if (true === raw) ;
|
|
1721
|
+
else if (false === raw) return false;
|
|
1722
|
+
else if (void 0 !== raw) return raw;
|
|
1723
|
+
const legacy = options?.keyboardDismissStrategy ?? this.options?.keyboardDismissStrategy;
|
|
1724
|
+
if ('back-first' === legacy) return 'back';
|
|
1725
|
+
return 'esc';
|
|
1726
|
+
}
|
|
1727
|
+
async waitForKeyboardHidden(adb, timeoutMs) {
|
|
1728
|
+
const startTime = Date.now();
|
|
1729
|
+
const intervalMs = 100;
|
|
1730
|
+
while(Date.now() - startTime < timeoutMs){
|
|
1731
|
+
await sleep(intervalMs);
|
|
1732
|
+
const currentStatus = await adb.isSoftKeyboardPresent();
|
|
1733
|
+
const isStillShown = 'boolean' == typeof currentStatus ? currentStatus : currentStatus?.isKeyboardShown;
|
|
1734
|
+
if (!isStillShown) return true;
|
|
1735
|
+
}
|
|
1736
|
+
return false;
|
|
1737
|
+
}
|
|
1738
|
+
async ensureMidsceneImeInstalled(adb, autoApprove = false) {
|
|
1739
|
+
if (this.midsceneImeInstalled) return;
|
|
1740
|
+
try {
|
|
1741
|
+
const output = await adb.shell(`pm list packages ${MIDSCENE_IME_PACKAGE}`);
|
|
1742
|
+
if (output.includes(MIDSCENE_IME_PACKAGE)) {
|
|
1743
|
+
await adb.shell(`ime enable ${MIDSCENE_IME_ID}`);
|
|
1744
|
+
this.midsceneImeInstalled = true;
|
|
1745
|
+
return;
|
|
1746
|
+
}
|
|
1747
|
+
} catch {}
|
|
1748
|
+
const androidPkgJson = (0, external_node_module_.createRequire)(import.meta.url).resolve('@midscene/android/package.json');
|
|
1749
|
+
const apkPath = external_node_path_["default"].join(external_node_path_["default"].dirname(androidPkgJson), 'bin', 'midscene-ime.apk');
|
|
1750
|
+
if (!external_node_fs_["default"].existsSync(apkPath)) throw new Error(`MidsceneIME APK not found at ${apkPath}. Run the @midscene/android prepack/build step to generate bin/midscene-ime.apk before using the MidsceneIME keyboard-dismiss strategy.`);
|
|
1751
|
+
try {
|
|
1752
|
+
await adb.install(apkPath);
|
|
1753
|
+
await adb.shell(`ime enable ${MIDSCENE_IME_ID}`);
|
|
1754
|
+
this.midsceneImeInstalled = true;
|
|
1755
|
+
return;
|
|
1756
|
+
} catch (e) {
|
|
1757
|
+
const errorMsg = String(e);
|
|
1758
|
+
if (!errorMsg.includes('INSTALL_FAILED_USER_RESTRICTED')) throw e;
|
|
1759
|
+
if (!autoApprove || !this.installApprovalHandler) throw new Error(`APK installation was blocked by the device (INSTALL_FAILED_USER_RESTRICTED). On some ROMs (e.g. MIUI), enable "Install via USB" in Developer Options, or use autoDismissKeyboard: 'midscene-ime-auto-install' with an agent to auto-approve. Original error: ${e}`);
|
|
1760
|
+
debugDevice('Install blocked by device, retrying with approval handler...');
|
|
1761
|
+
}
|
|
1762
|
+
await adb.push(apkPath, '/data/local/tmp/midscene-ime.apk');
|
|
1763
|
+
const installPromise = adb.shell('pm install -r -t /data/local/tmp/midscene-ime.apk');
|
|
1764
|
+
await sleep(2000);
|
|
1765
|
+
try {
|
|
1766
|
+
await this.installApprovalHandler();
|
|
1767
|
+
} catch (approvalError) {
|
|
1768
|
+
debugDevice(`Install approval handler failed: ${approvalError}`);
|
|
1769
|
+
}
|
|
1770
|
+
try {
|
|
1771
|
+
const result = await installPromise;
|
|
1772
|
+
if (result.includes('Success')) {
|
|
1773
|
+
await adb.shell(`ime enable ${MIDSCENE_IME_ID}`);
|
|
1774
|
+
this.midsceneImeInstalled = true;
|
|
1775
|
+
debugDevice('MidsceneIME installed via auto-approval');
|
|
1776
|
+
return;
|
|
1777
|
+
}
|
|
1778
|
+
throw new Error(`Install failed: ${result}`);
|
|
1779
|
+
} catch (e) {
|
|
1780
|
+
throw new Error(`Failed to install MidsceneIME even with auto-approval. Please manually enable "Install via USB" in Developer Options. Original error: ${e}`);
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
async hideKeyboardViaMidsceneIme(adb, timeoutMs) {
|
|
1784
|
+
const originalIme = await adb.shell('settings get secure default_input_method');
|
|
1785
|
+
const trimmedOriginalIme = originalIme.trim();
|
|
1786
|
+
try {
|
|
1787
|
+
await adb.shell(`ime set ${MIDSCENE_IME_ID}`);
|
|
1788
|
+
await sleep(500);
|
|
1789
|
+
await adb.shell(`am broadcast -a ${MIDSCENE_IME_DISMISS_ACTION}`);
|
|
1790
|
+
await sleep(500);
|
|
1791
|
+
if (trimmedOriginalIme && trimmedOriginalIme !== MIDSCENE_IME_ID) await adb.shell(`ime set ${trimmedOriginalIme}`);
|
|
1792
|
+
if (await this.waitForKeyboardHidden(adb, timeoutMs)) {
|
|
1793
|
+
debugDevice('Keyboard hidden successfully with MidsceneIME');
|
|
1794
|
+
return true;
|
|
1795
|
+
}
|
|
1796
|
+
debugDevice('MidsceneIME did not dismiss keyboard');
|
|
1797
|
+
return false;
|
|
1798
|
+
} catch (e) {
|
|
1799
|
+
if (trimmedOriginalIme && trimmedOriginalIme !== MIDSCENE_IME_ID) try {
|
|
1800
|
+
await adb.shell(`ime set ${trimmedOriginalIme}`);
|
|
1801
|
+
} catch {}
|
|
1802
|
+
throw e;
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1701
1805
|
async hideKeyboard(options, timeoutMs = 1000) {
|
|
1702
1806
|
const adb = await this.getAdb();
|
|
1703
|
-
const
|
|
1807
|
+
const strategy = this.resolveAutoDismissKeyboard(options);
|
|
1808
|
+
if (false === strategy) return false;
|
|
1704
1809
|
const keyboardStatus = await adb.isSoftKeyboardPresent();
|
|
1705
1810
|
const isKeyboardShown = 'boolean' == typeof keyboardStatus ? keyboardStatus : keyboardStatus?.isKeyboardShown;
|
|
1706
1811
|
if (!isKeyboardShown) {
|
|
1707
1812
|
debugDevice('Keyboard has no UI; no closing necessary');
|
|
1708
1813
|
return false;
|
|
1709
1814
|
}
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
const intervalMs = 100;
|
|
1721
|
-
while(Date.now() - startTime < timeoutMs){
|
|
1722
|
-
await sleep(intervalMs);
|
|
1723
|
-
const currentStatus = await adb.isSoftKeyboardPresent();
|
|
1724
|
-
const isStillShown = 'boolean' == typeof currentStatus ? currentStatus : currentStatus?.isKeyboardShown;
|
|
1725
|
-
if (!isStillShown) {
|
|
1726
|
-
debugDevice(`Keyboard hidden successfully with keycode ${keyCode}`);
|
|
1727
|
-
return true;
|
|
1728
|
-
}
|
|
1815
|
+
if ('midscene-ime' === strategy || 'midscene-ime-auto-install' === strategy) {
|
|
1816
|
+
const autoApprove = 'midscene-ime-auto-install' === strategy;
|
|
1817
|
+
try {
|
|
1818
|
+
await this.ensureMidsceneImeInstalled(adb, autoApprove);
|
|
1819
|
+
const hidden = await this.hideKeyboardViaMidsceneIme(adb, timeoutMs);
|
|
1820
|
+
if (!hidden) console.warn('Warning: MidsceneIME was invoked but the software keyboard is still visible');
|
|
1821
|
+
return hidden;
|
|
1822
|
+
} catch (e) {
|
|
1823
|
+
const installHint = autoApprove ? 'Failed to use MidsceneIME auto-install for keyboard dismissal. MidsceneIME will not fall back to ESC/BACK because that can clear user input. Please enable USB installs on the device or preinstall the helper APK manually: adb install <path-to-midscene-ime.apk>.' : "Failed to use MidsceneIME for keyboard dismissal. Please install the APK manually: adb install <path-to-midscene-ime.apk>, or use autoDismissKeyboard: 'midscene-ime-auto-install' with an AndroidAgent to auto-approve installation prompts.";
|
|
1824
|
+
throw new Error(`${installHint} Original error: ${e}`);
|
|
1729
1825
|
}
|
|
1730
|
-
debugDevice(`Keyboard still shown after keycode ${keyCode}, trying next key`);
|
|
1731
1826
|
}
|
|
1732
|
-
|
|
1827
|
+
const keyCode = 'back' === strategy ? 4 : 111;
|
|
1828
|
+
await adb.keyevent(keyCode);
|
|
1829
|
+
if (await this.waitForKeyboardHidden(adb, timeoutMs)) {
|
|
1830
|
+
debugDevice(`Keyboard hidden successfully with keycode ${keyCode} (${strategy})`);
|
|
1831
|
+
return true;
|
|
1832
|
+
}
|
|
1833
|
+
const fallbackKeyCode = 'back' === strategy ? 111 : 4;
|
|
1834
|
+
await adb.keyevent(fallbackKeyCode);
|
|
1835
|
+
if (await this.waitForKeyboardHidden(adb, timeoutMs)) {
|
|
1836
|
+
debugDevice(`Keyboard hidden successfully with fallback keycode ${fallbackKeyCode}`);
|
|
1837
|
+
return true;
|
|
1838
|
+
}
|
|
1839
|
+
console.warn('Warning: Failed to hide the software keyboard after trying all strategies');
|
|
1733
1840
|
return false;
|
|
1734
1841
|
}
|
|
1735
1842
|
constructor(deviceId, options){
|
|
1736
1843
|
device_define_property(this, "deviceId", void 0);
|
|
1737
1844
|
device_define_property(this, "yadbPushed", false);
|
|
1845
|
+
device_define_property(this, "midsceneImeInstalled", false);
|
|
1846
|
+
device_define_property(this, "installApprovalHandler", void 0);
|
|
1738
1847
|
device_define_property(this, "devicePixelRatio", 1);
|
|
1739
1848
|
device_define_property(this, "devicePixelRatioInitialized", false);
|
|
1740
1849
|
device_define_property(this, "adb", null);
|
|
@@ -1882,6 +1991,9 @@ class AndroidAgent extends Agent {
|
|
|
1882
1991
|
super(device, opts), agent_define_property(this, "back", void 0), agent_define_property(this, "home", void 0), agent_define_property(this, "recentApps", void 0), agent_define_property(this, "appNameMapping", void 0);
|
|
1883
1992
|
this.appNameMapping = mergeAndNormalizeAppNameMapping(defaultAppNameMapping, opts?.appNameMapping);
|
|
1884
1993
|
device.setAppNameMapping(this.appNameMapping);
|
|
1994
|
+
device.setInstallApprovalHandler(async ()=>{
|
|
1995
|
+
await this.aiAct('A system dialog is asking to approve USB installation. Click the "Continue Install", "Allow", "Install" or similar approval button.');
|
|
1996
|
+
});
|
|
1885
1997
|
this.back = this.createActionWrapper('AndroidBackButton');
|
|
1886
1998
|
this.home = this.createActionWrapper('AndroidHomeButton');
|
|
1887
1999
|
this.recentApps = this.createActionWrapper('AndroidRecentAppsButton');
|
|
@@ -1959,7 +2071,7 @@ class AndroidMCPServer extends BaseMCPServer {
|
|
|
1959
2071
|
constructor(toolsManager){
|
|
1960
2072
|
super({
|
|
1961
2073
|
name: '@midscene/android-mcp',
|
|
1962
|
-
version: "1.
|
|
2074
|
+
version: "1.7.1-beta-20260408073050.0",
|
|
1963
2075
|
description: 'Control the Android device using natural language commands'
|
|
1964
2076
|
}, toolsManager);
|
|
1965
2077
|
}
|
package/dist/lib/cli.js
CHANGED
|
@@ -690,6 +690,18 @@ var __webpack_exports__ = {};
|
|
|
690
690
|
const defaultNormalScrollDuration = 1000;
|
|
691
691
|
const IME_STRATEGY_ALWAYS_YADB = 'always-yadb';
|
|
692
692
|
const IME_STRATEGY_YADB_FOR_NON_ASCII = 'yadb-for-non-ascii';
|
|
693
|
+
const MIDSCENE_IME_PACKAGE = 'com.midscene.ime';
|
|
694
|
+
const MIDSCENE_IME_ID = `${MIDSCENE_IME_PACKAGE}/.MidsceneIME`;
|
|
695
|
+
const MIDSCENE_IME_DISMISS_ACTION = `${MIDSCENE_IME_PACKAGE}.DISMISS`;
|
|
696
|
+
const AUTO_DISMISS_KEYBOARD_SCHEMA = core_namespaceObject.z.union([
|
|
697
|
+
core_namespaceObject.z.boolean(),
|
|
698
|
+
core_namespaceObject.z["enum"]([
|
|
699
|
+
'back',
|
|
700
|
+
'esc',
|
|
701
|
+
'midscene-ime',
|
|
702
|
+
'midscene-ime-auto-install'
|
|
703
|
+
])
|
|
704
|
+
]).optional().describe("Dismiss the keyboard after input. Use `true` for the default ESC-based behavior, `false` to leave the keyboard open, `'back'`/`'esc'` for explicit key-event strategies, or `'midscene-ime'` / `'midscene-ime-auto-install'` for the zero-key-event helper IME flow.");
|
|
693
705
|
const debugDevice = (0, logger_.getDebug)('android:device');
|
|
694
706
|
function escapeForShell(text) {
|
|
695
707
|
return text.replace(/'/g, "'\\''").replace(/\n/g, '\\n');
|
|
@@ -713,7 +725,7 @@ var __webpack_exports__ = {};
|
|
|
713
725
|
interfaceAlias: 'aiInput',
|
|
714
726
|
paramSchema: core_namespaceObject.z.object({
|
|
715
727
|
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.'),
|
|
716
|
-
autoDismissKeyboard:
|
|
728
|
+
autoDismissKeyboard: AUTO_DISMISS_KEYBOARD_SCHEMA,
|
|
717
729
|
mode: core_namespaceObject.z.preprocess((val)=>'append' === val ? 'typeOnly' : val, core_namespaceObject.z["enum"]([
|
|
718
730
|
'replace',
|
|
719
731
|
'clear',
|
|
@@ -959,6 +971,9 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
959
971
|
setAppNameMapping(mapping) {
|
|
960
972
|
this.appNameMapping = mapping;
|
|
961
973
|
}
|
|
974
|
+
setInstallApprovalHandler(handler) {
|
|
975
|
+
this.installApprovalHandler = handler;
|
|
976
|
+
}
|
|
962
977
|
resolvePackageName(appName) {
|
|
963
978
|
const normalizedAppName = (0, utils_namespaceObject.normalizeForComparison)(appName);
|
|
964
979
|
return this.appNameMapping[normalizedAppName];
|
|
@@ -1497,7 +1512,8 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1497
1512
|
if (!text) return;
|
|
1498
1513
|
const adb = await this.getAdb();
|
|
1499
1514
|
const IME_STRATEGY = (this.options?.imeStrategy || env_namespaceObject.globalConfigManager.getEnvConfigValue(env_namespaceObject.MIDSCENE_ANDROID_IME_STRATEGY)) ?? IME_STRATEGY_YADB_FOR_NON_ASCII;
|
|
1500
|
-
const
|
|
1515
|
+
const autoDismiss = options?.autoDismissKeyboard ?? this.options?.autoDismissKeyboard ?? 'esc';
|
|
1516
|
+
const shouldAutoDismissKeyboard = false !== autoDismiss;
|
|
1501
1517
|
const useYadb = IME_STRATEGY === IME_STRATEGY_ALWAYS_YADB || IME_STRATEGY === IME_STRATEGY_YADB_FOR_NON_ASCII && this.shouldUseYadbForText(text);
|
|
1502
1518
|
if (useYadb) await this.execYadb(escapeForShell(text), {
|
|
1503
1519
|
overwrite: options?.overwrite
|
|
@@ -1615,6 +1631,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1615
1631
|
}
|
|
1616
1632
|
this.connectingAdb = null;
|
|
1617
1633
|
this.yadbPushed = false;
|
|
1634
|
+
this.midsceneImeInstalled = false;
|
|
1618
1635
|
}
|
|
1619
1636
|
async getTimestamp() {
|
|
1620
1637
|
const adb = await this.getAdb();
|
|
@@ -1714,43 +1731,135 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1714
1731
|
return null;
|
|
1715
1732
|
}
|
|
1716
1733
|
}
|
|
1734
|
+
resolveAutoDismissKeyboard(options) {
|
|
1735
|
+
const raw = options?.autoDismissKeyboard ?? this.options?.autoDismissKeyboard;
|
|
1736
|
+
if (true === raw) ;
|
|
1737
|
+
else if (false === raw) return false;
|
|
1738
|
+
else if (void 0 !== raw) return raw;
|
|
1739
|
+
const legacy = options?.keyboardDismissStrategy ?? this.options?.keyboardDismissStrategy;
|
|
1740
|
+
if ('back-first' === legacy) return 'back';
|
|
1741
|
+
return 'esc';
|
|
1742
|
+
}
|
|
1743
|
+
async waitForKeyboardHidden(adb, timeoutMs) {
|
|
1744
|
+
const startTime = Date.now();
|
|
1745
|
+
const intervalMs = 100;
|
|
1746
|
+
while(Date.now() - startTime < timeoutMs){
|
|
1747
|
+
await (0, core_utils_namespaceObject.sleep)(intervalMs);
|
|
1748
|
+
const currentStatus = await adb.isSoftKeyboardPresent();
|
|
1749
|
+
const isStillShown = 'boolean' == typeof currentStatus ? currentStatus : currentStatus?.isKeyboardShown;
|
|
1750
|
+
if (!isStillShown) return true;
|
|
1751
|
+
}
|
|
1752
|
+
return false;
|
|
1753
|
+
}
|
|
1754
|
+
async ensureMidsceneImeInstalled(adb, autoApprove = false) {
|
|
1755
|
+
if (this.midsceneImeInstalled) return;
|
|
1756
|
+
try {
|
|
1757
|
+
const output = await adb.shell(`pm list packages ${MIDSCENE_IME_PACKAGE}`);
|
|
1758
|
+
if (output.includes(MIDSCENE_IME_PACKAGE)) {
|
|
1759
|
+
await adb.shell(`ime enable ${MIDSCENE_IME_ID}`);
|
|
1760
|
+
this.midsceneImeInstalled = true;
|
|
1761
|
+
return;
|
|
1762
|
+
}
|
|
1763
|
+
} catch {}
|
|
1764
|
+
const androidPkgJson = (0, external_node_module_.createRequire)(__rslib_import_meta_url__).resolve('@midscene/android/package.json');
|
|
1765
|
+
const apkPath = external_node_path_default().join(external_node_path_default().dirname(androidPkgJson), 'bin', 'midscene-ime.apk');
|
|
1766
|
+
if (!external_node_fs_default().existsSync(apkPath)) throw new Error(`MidsceneIME APK not found at ${apkPath}. Run the @midscene/android prepack/build step to generate bin/midscene-ime.apk before using the MidsceneIME keyboard-dismiss strategy.`);
|
|
1767
|
+
try {
|
|
1768
|
+
await adb.install(apkPath);
|
|
1769
|
+
await adb.shell(`ime enable ${MIDSCENE_IME_ID}`);
|
|
1770
|
+
this.midsceneImeInstalled = true;
|
|
1771
|
+
return;
|
|
1772
|
+
} catch (e) {
|
|
1773
|
+
const errorMsg = String(e);
|
|
1774
|
+
if (!errorMsg.includes('INSTALL_FAILED_USER_RESTRICTED')) throw e;
|
|
1775
|
+
if (!autoApprove || !this.installApprovalHandler) throw new Error(`APK installation was blocked by the device (INSTALL_FAILED_USER_RESTRICTED). On some ROMs (e.g. MIUI), enable "Install via USB" in Developer Options, or use autoDismissKeyboard: 'midscene-ime-auto-install' with an agent to auto-approve. Original error: ${e}`);
|
|
1776
|
+
debugDevice('Install blocked by device, retrying with approval handler...');
|
|
1777
|
+
}
|
|
1778
|
+
await adb.push(apkPath, '/data/local/tmp/midscene-ime.apk');
|
|
1779
|
+
const installPromise = adb.shell('pm install -r -t /data/local/tmp/midscene-ime.apk');
|
|
1780
|
+
await (0, core_utils_namespaceObject.sleep)(2000);
|
|
1781
|
+
try {
|
|
1782
|
+
await this.installApprovalHandler();
|
|
1783
|
+
} catch (approvalError) {
|
|
1784
|
+
debugDevice(`Install approval handler failed: ${approvalError}`);
|
|
1785
|
+
}
|
|
1786
|
+
try {
|
|
1787
|
+
const result = await installPromise;
|
|
1788
|
+
if (result.includes('Success')) {
|
|
1789
|
+
await adb.shell(`ime enable ${MIDSCENE_IME_ID}`);
|
|
1790
|
+
this.midsceneImeInstalled = true;
|
|
1791
|
+
debugDevice('MidsceneIME installed via auto-approval');
|
|
1792
|
+
return;
|
|
1793
|
+
}
|
|
1794
|
+
throw new Error(`Install failed: ${result}`);
|
|
1795
|
+
} catch (e) {
|
|
1796
|
+
throw new Error(`Failed to install MidsceneIME even with auto-approval. Please manually enable "Install via USB" in Developer Options. Original error: ${e}`);
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
async hideKeyboardViaMidsceneIme(adb, timeoutMs) {
|
|
1800
|
+
const originalIme = await adb.shell('settings get secure default_input_method');
|
|
1801
|
+
const trimmedOriginalIme = originalIme.trim();
|
|
1802
|
+
try {
|
|
1803
|
+
await adb.shell(`ime set ${MIDSCENE_IME_ID}`);
|
|
1804
|
+
await (0, core_utils_namespaceObject.sleep)(500);
|
|
1805
|
+
await adb.shell(`am broadcast -a ${MIDSCENE_IME_DISMISS_ACTION}`);
|
|
1806
|
+
await (0, core_utils_namespaceObject.sleep)(500);
|
|
1807
|
+
if (trimmedOriginalIme && trimmedOriginalIme !== MIDSCENE_IME_ID) await adb.shell(`ime set ${trimmedOriginalIme}`);
|
|
1808
|
+
if (await this.waitForKeyboardHidden(adb, timeoutMs)) {
|
|
1809
|
+
debugDevice('Keyboard hidden successfully with MidsceneIME');
|
|
1810
|
+
return true;
|
|
1811
|
+
}
|
|
1812
|
+
debugDevice('MidsceneIME did not dismiss keyboard');
|
|
1813
|
+
return false;
|
|
1814
|
+
} catch (e) {
|
|
1815
|
+
if (trimmedOriginalIme && trimmedOriginalIme !== MIDSCENE_IME_ID) try {
|
|
1816
|
+
await adb.shell(`ime set ${trimmedOriginalIme}`);
|
|
1817
|
+
} catch {}
|
|
1818
|
+
throw e;
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1717
1821
|
async hideKeyboard(options, timeoutMs = 1000) {
|
|
1718
1822
|
const adb = await this.getAdb();
|
|
1719
|
-
const
|
|
1823
|
+
const strategy = this.resolveAutoDismissKeyboard(options);
|
|
1824
|
+
if (false === strategy) return false;
|
|
1720
1825
|
const keyboardStatus = await adb.isSoftKeyboardPresent();
|
|
1721
1826
|
const isKeyboardShown = 'boolean' == typeof keyboardStatus ? keyboardStatus : keyboardStatus?.isKeyboardShown;
|
|
1722
1827
|
if (!isKeyboardShown) {
|
|
1723
1828
|
debugDevice('Keyboard has no UI; no closing necessary');
|
|
1724
1829
|
return false;
|
|
1725
1830
|
}
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
const intervalMs = 100;
|
|
1737
|
-
while(Date.now() - startTime < timeoutMs){
|
|
1738
|
-
await (0, core_utils_namespaceObject.sleep)(intervalMs);
|
|
1739
|
-
const currentStatus = await adb.isSoftKeyboardPresent();
|
|
1740
|
-
const isStillShown = 'boolean' == typeof currentStatus ? currentStatus : currentStatus?.isKeyboardShown;
|
|
1741
|
-
if (!isStillShown) {
|
|
1742
|
-
debugDevice(`Keyboard hidden successfully with keycode ${keyCode}`);
|
|
1743
|
-
return true;
|
|
1744
|
-
}
|
|
1831
|
+
if ('midscene-ime' === strategy || 'midscene-ime-auto-install' === strategy) {
|
|
1832
|
+
const autoApprove = 'midscene-ime-auto-install' === strategy;
|
|
1833
|
+
try {
|
|
1834
|
+
await this.ensureMidsceneImeInstalled(adb, autoApprove);
|
|
1835
|
+
const hidden = await this.hideKeyboardViaMidsceneIme(adb, timeoutMs);
|
|
1836
|
+
if (!hidden) console.warn('Warning: MidsceneIME was invoked but the software keyboard is still visible');
|
|
1837
|
+
return hidden;
|
|
1838
|
+
} catch (e) {
|
|
1839
|
+
const installHint = autoApprove ? 'Failed to use MidsceneIME auto-install for keyboard dismissal. MidsceneIME will not fall back to ESC/BACK because that can clear user input. Please enable USB installs on the device or preinstall the helper APK manually: adb install <path-to-midscene-ime.apk>.' : "Failed to use MidsceneIME for keyboard dismissal. Please install the APK manually: adb install <path-to-midscene-ime.apk>, or use autoDismissKeyboard: 'midscene-ime-auto-install' with an AndroidAgent to auto-approve installation prompts.";
|
|
1840
|
+
throw new Error(`${installHint} Original error: ${e}`);
|
|
1745
1841
|
}
|
|
1746
|
-
debugDevice(`Keyboard still shown after keycode ${keyCode}, trying next key`);
|
|
1747
1842
|
}
|
|
1748
|
-
|
|
1843
|
+
const keyCode = 'back' === strategy ? 4 : 111;
|
|
1844
|
+
await adb.keyevent(keyCode);
|
|
1845
|
+
if (await this.waitForKeyboardHidden(adb, timeoutMs)) {
|
|
1846
|
+
debugDevice(`Keyboard hidden successfully with keycode ${keyCode} (${strategy})`);
|
|
1847
|
+
return true;
|
|
1848
|
+
}
|
|
1849
|
+
const fallbackKeyCode = 'back' === strategy ? 111 : 4;
|
|
1850
|
+
await adb.keyevent(fallbackKeyCode);
|
|
1851
|
+
if (await this.waitForKeyboardHidden(adb, timeoutMs)) {
|
|
1852
|
+
debugDevice(`Keyboard hidden successfully with fallback keycode ${fallbackKeyCode}`);
|
|
1853
|
+
return true;
|
|
1854
|
+
}
|
|
1855
|
+
console.warn('Warning: Failed to hide the software keyboard after trying all strategies');
|
|
1749
1856
|
return false;
|
|
1750
1857
|
}
|
|
1751
1858
|
constructor(deviceId, options){
|
|
1752
1859
|
device_define_property(this, "deviceId", void 0);
|
|
1753
1860
|
device_define_property(this, "yadbPushed", false);
|
|
1861
|
+
device_define_property(this, "midsceneImeInstalled", false);
|
|
1862
|
+
device_define_property(this, "installApprovalHandler", void 0);
|
|
1754
1863
|
device_define_property(this, "devicePixelRatio", 1);
|
|
1755
1864
|
device_define_property(this, "devicePixelRatioInitialized", false);
|
|
1756
1865
|
device_define_property(this, "adb", null);
|
|
@@ -1898,6 +2007,9 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1898
2007
|
super(device, opts), agent_define_property(this, "back", void 0), agent_define_property(this, "home", void 0), agent_define_property(this, "recentApps", void 0), agent_define_property(this, "appNameMapping", void 0);
|
|
1899
2008
|
this.appNameMapping = (0, utils_namespaceObject.mergeAndNormalizeAppNameMapping)(defaultAppNameMapping, opts?.appNameMapping);
|
|
1900
2009
|
device.setAppNameMapping(this.appNameMapping);
|
|
2010
|
+
device.setInstallApprovalHandler(async ()=>{
|
|
2011
|
+
await this.aiAct('A system dialog is asking to approve USB installation. Click the "Continue Install", "Allow", "Install" or similar approval button.');
|
|
2012
|
+
});
|
|
1901
2013
|
this.back = this.createActionWrapper('AndroidBackButton');
|
|
1902
2014
|
this.home = this.createActionWrapper('AndroidHomeButton');
|
|
1903
2015
|
this.recentApps = this.createActionWrapper('AndroidRecentAppsButton');
|
|
@@ -1971,7 +2083,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1971
2083
|
const tools = new AndroidMidsceneTools();
|
|
1972
2084
|
(0, cli_namespaceObject.runToolsCLI)(tools, 'midscene-android', {
|
|
1973
2085
|
stripPrefix: 'android_',
|
|
1974
|
-
version: "1.
|
|
2086
|
+
version: "1.7.1-beta-20260408073050.0",
|
|
1975
2087
|
extraCommands: (0, core_namespaceObject.createReportCliCommands)()
|
|
1976
2088
|
}).catch((e)=>{
|
|
1977
2089
|
if (!(e instanceof cli_namespaceObject.CLIError)) console.error(e);
|