@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.
@@ -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: z.boolean().optional().describe('If true, the keyboard will be dismissed after the input is completed. Do not set it unless the user asks you to do so.'),
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 shouldAutoDismissKeyboard = options?.autoDismissKeyboard ?? this.options?.autoDismissKeyboard ?? true;
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 keyboardDismissStrategy = options?.keyboardDismissStrategy ?? this.options?.keyboardDismissStrategy ?? 'esc-first';
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
- const keyCodes = 'back-first' === keyboardDismissStrategy ? [
1711
- 4,
1712
- 111
1713
- ] : [
1714
- 111,
1715
- 4
1716
- ];
1717
- for (const keyCode of keyCodes){
1718
- await adb.keyevent(keyCode);
1719
- const startTime = Date.now();
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
- console.warn('Warning: Failed to hide the software keyboard after trying both ESC and BACK keys');
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.6.4",
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: core_namespaceObject.z.boolean().optional().describe('If true, the keyboard will be dismissed after the input is completed. Do not set it unless the user asks you to do so.'),
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 shouldAutoDismissKeyboard = options?.autoDismissKeyboard ?? this.options?.autoDismissKeyboard ?? true;
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 keyboardDismissStrategy = options?.keyboardDismissStrategy ?? this.options?.keyboardDismissStrategy ?? 'esc-first';
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
- const keyCodes = 'back-first' === keyboardDismissStrategy ? [
1727
- 4,
1728
- 111
1729
- ] : [
1730
- 111,
1731
- 4
1732
- ];
1733
- for (const keyCode of keyCodes){
1734
- await adb.keyevent(keyCode);
1735
- const startTime = Date.now();
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
- console.warn('Warning: Failed to hide the software keyboard after trying both ESC and BACK keys');
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.6.4",
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);