""" Android View InputMethod 组件 — InputMethodManager, EditorInfo, InputConnection, ExtractedText 等 方法签名完全对齐 Android SDK """ import sys from typing import Any, Dict, List, Optional, Tuple from .._core import Stub, AndroidOnlyError, android_context # ═══════════════════════════════════════════════════════════════ # InputMethodManager # ═══════════════════════════════════════════════════════════════ class InputMethodManager(Stub): """InputMethodManager — 输入法管理器""" SHOW_IMPLICIT = 0x0001 SHOW_FORCED = 0x0002 HIDE_IMPLICIT_ONLY = 0x0001 HIDE_NOT_ALWAYS = 0x0002 RESULT_SHOWN = 2 RESULT_HIDDEN = 3 RESULT_UNCHANGED_SHOWN = 4 RESULT_UNCHANGED_HIDDEN = 5 def __init__(self): super().__init__() self._active: bool = False self._current_input_connection: Optional["InputConnection"] = None self._is_accepting_text: bool = True self._fullscreen_mode: bool = False self._cursor_x: int = 0 self._cursor_y: int = 0 self._selection_start: int = -1 self._selection_end: int = -1 self._composing_start: int = -1 self._composing_end: int = -1 def showSoftInput(self, view: Any, flags: int, result_receiver: Any = None) -> bool: self._active = True sys.stderr.write(f"[InputMethodManager] showSoftInput: flags={flags}\n") return True def hideSoftInputFromWindow(self, window_token: Any, flags: int, result_receiver: Any = None) -> bool: self._active = False sys.stderr.write(f"[InputMethodManager] hideSoftInputFromWindow: flags={flags}\n") return True def toggleSoftInput(self, show_flags: int, hide_flags: int): self._active = not self._active sys.stderr.write(f"[InputMethodManager] toggleSoftInput: " f"show={show_flags}, hide={hide_flags}\n") def restartInput(self, view: Any): sys.stderr.write("[InputMethodManager] restartInput\n") def startInput(self, view: Any) -> bool: sys.stderr.write("[InputMethodManager] startInput\n") return True def updateSelection(self, view: Any, sel_start: int, sel_end: int, composing_start: int, composing_end: int): self._selection_start = sel_start self._selection_end = sel_end self._composing_start = composing_start self._composing_end = composing_end def updateCursorAnchorInfo(self, view: Any, cursor_anchor_info: "CursorAnchorInfo"): self._cursor_x = cursor_anchor_info.getInsertionMarkerBaseline() self._cursor_y = cursor_anchor_info.getInsertionMarkerTop() def displayCompletions(self, view: Any, completions: List["CompletionInfo"]): sys.stderr.write(f"[InputMethodManager] displayCompletions: " f"{len(completions) if completions else 0} items\n") def sendKeyChar(self, key_char: str): sys.stderr.write(f"[InputMethodManager] sendKeyChar: '{key_char}'\n") def clearMetaKeyStates(self, view: Any, states: int): sys.stderr.write(f"[InputMethodManager] clearMetaKeyStates: {states}\n") def isAcceptingText(self) -> bool: return self._is_accepting_text def isActive(self) -> bool: return self._active def isFullscreenMode(self) -> bool: return self._fullscreen_mode def setFullscreenMode(self, fullscreen: bool): self._fullscreen_mode = fullscreen def getCurrentInputMethodSubtype(self) -> Optional["InputMethodSubtype"]: return None def getShortcutInputMethodsAndSubtypes(self) -> List[Any]: return [] def showStatusIcon(self, icon: Any): pass def hideStatusIcon(self): pass def registerSuggestionSpansForNotification(self, spans: List[Any], sender: Any): pass def notifySuggestionPicked(self, suggestion: Any): pass def windowDismissed(self, window_token: Any): self._active = False def toString(self) -> str: return f"InputMethodManager(active={self._active})" # ═══════════════════════════════════════════════════════════════ # EditorInfo # ═══════════════════════════════════════════════════════════════ class EditorInfo(Stub): """EditorInfo — 编辑器信息""" TYPE_CLASS_TEXT = 0x0001 TYPE_CLASS_NUMBER = 0x0002 TYPE_CLASS_PHONE = 0x0004 TYPE_CLASS_DATETIME = 0x0008 TYPE_TEXT_VARIATION_NORMAL = 0x0000 TYPE_TEXT_VARIATION_URI = 0x0010 TYPE_TEXT_VARIATION_EMAIL_ADDRESS = 0x0020 TYPE_TEXT_VARIATION_EMAIL_SUBJECT = 0x0030 TYPE_TEXT_VARIATION_SHORT_MESSAGE = 0x0040 TYPE_TEXT_VARIATION_LONG_MESSAGE = 0x0050 TYPE_TEXT_VARIATION_PERSON_NAME = 0x0060 TYPE_TEXT_VARIATION_POSTAL_ADDRESS = 0x0070 TYPE_TEXT_VARIATION_PASSWORD = 0x0080 TYPE_TEXT_VARIATION_VISIBLE_PASSWORD = 0x0090 TYPE_TEXT_VARIATION_WEB_EDIT_TEXT = 0x00A0 TYPE_TEXT_VARIATION_FILTER = 0x00B0 TYPE_TEXT_VARIATION_PHONETIC = 0x00C0 TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS = 0x00D0 TYPE_TEXT_VARIATION_WEB_PASSWORD = 0x00E0 TYPE_NUMBER_VARIATION_NORMAL = 0x0000 TYPE_NUMBER_VARIATION_PASSWORD = 0x0010 TYPE_DATETIME_VARIATION_NORMAL = 0x0000 TYPE_DATETIME_VARIATION_DATE = 0x0010 TYPE_DATETIME_VARIATION_TIME = 0x0020 TYPE_NULL = 0x0000 IME_ACTION_NONE = 0x0001 IME_ACTION_GO = 0x0002 IME_ACTION_SEARCH = 0x0003 IME_ACTION_SEND = 0x0004 IME_ACTION_NEXT = 0x0005 IME_ACTION_DONE = 0x0006 IME_ACTION_PREVIOUS = 0x0007 IME_FLAG_NO_EXTRACT_UI = 0x10000001 IME_FLAG_NO_ACCESSORY_ACTION = 0x20000000 IME_FLAG_NO_ENTER_ACTION = 0x40000000 IME_FLAG_NAVIGATE_NEXT = 0x08000000 IME_FLAG_NAVIGATE_PREVIOUS = 0x04000000 IME_FLAG_NO_FULLSCREEN = 0x02000000 IME_FLAG_FORCE_ASCII = 0x80000000 IME_MASK_ACTION = 0x000000FF def __init__(self): super().__init__() self.inputType: int = EditorInfo.TYPE_CLASS_TEXT self.imeOptions: int = EditorInfo.IME_ACTION_NONE self.actionLabel: Optional[str] = None self.actionId: int = 0 self.initialSelStart: int = -1 self.initialSelEnd: int = -1 self.initialCapsMode: int = 0 self.hintText: Optional[str] = None self.label: Optional[str] = None self.packageName: Optional[str] = None self.fieldId: int = 0 self.fieldName: Optional[str] = None self.extras: Optional[Any] = None def toString(self) -> str: return (f"EditorInfo(inputType={hex(self.inputType)}, " f"imeOptions={hex(self.imeOptions)}, " f"actionLabel={self.actionLabel})") def dump(self, printer: Any, prefix: str): sys.stderr.write(f"{prefix}EditorInfo: inputType={hex(self.inputType)}\n") # ═══════════════════════════════════════════════════════════════ # InputConnection # ═══════════════════════════════════════════════════════════════ class InputConnection(Stub): """InputConnection — 输入连接接口""" def __init__(self): super().__init__() self._text: str = "" self._selection_start: int = 0 self._selection_end: int = 0 self._composing_start: int = -1 self._composing_end: int = -1 self._batch_edit_count: int = 0 def getTextBeforeCursor(self, length: int, flags: int) -> Optional[str]: end = self._selection_start start = max(0, end - length) return self._text[start:end] def getTextAfterCursor(self, length: int, flags: int) -> Optional[str]: start = self._selection_end end = min(len(self._text), start + length) return self._text[start:end] def getExtractedText(self, request: "ExtractedTextRequest", flags: int) -> Optional["ExtractedText"]: result = ExtractedText() result.text = self._text result.startOffset = 0 result.selectionStart = self._selection_start result.selectionEnd = self._selection_end result.flags = 0 return result def commitText(self, text: Any, new_cursor_position: int) -> bool: if isinstance(text, str): committed = text else: committed = str(text) if text else "" before = self._text[:self._selection_start] after = self._text[self._selection_end:] self._text = before + committed + after if new_cursor_position > 0: self._selection_start = len(before) + new_cursor_position - 1 else: self._selection_start = len(before) + len(committed) + new_cursor_position self._selection_end = self._selection_start self._composing_start = -1 self._composing_end = -1 return True def setComposingText(self, text: Any, new_cursor_position: int) -> bool: if self._composing_start < 0: self._composing_start = self._selection_start self._composing_end = self._selection_end before = self._text[:self._composing_start] after = self._text[self._composing_end:] if isinstance(text, str): composing = text else: composing = str(text) if text else "" self._text = before + composing + after self._composing_end = self._composing_start + len(composing) self._selection_start = self._composing_end + new_cursor_position self._selection_end = self._selection_start return True def setSelection(self, start: int, end: int) -> bool: self._selection_start = max(0, min(start, len(self._text))) self._selection_end = max(0, min(end, len(self._text))) return True def deleteSurroundingText(self, before_length: int, after_length: int) -> bool: start = max(0, self._selection_start - before_length) end = min(len(self._text), self._selection_end + after_length) self._text = self._text[:start] + self._text[end:] new_cursor = max(0, self._selection_start - before_length) self._selection_start = new_cursor self._selection_end = new_cursor return True def commitCompletion(self, completion: "CompletionInfo") -> bool: sys.stderr.write(f"[InputConnection] commitCompletion: " f"{completion.getText()}\n") return True def commitCorrection(self, correction: "CorrectionInfo") -> bool: sys.stderr.write(f"[InputConnection] commitCorrection: " f"{correction.getNewText()}\n") return True def getSelectedText(self, flags: int) -> Optional[str]: if self._selection_start == self._selection_end: return "" return self._text[self._selection_start:self._selection_end] def finishComposingText(self) -> bool: self._composing_start = -1 self._composing_end = -1 return True def beginBatchEdit(self) -> bool: self._batch_edit_count += 1 return True def endBatchEdit(self) -> bool: if self._batch_edit_count > 0: self._batch_edit_count -= 1 return True def sendKeyEvent(self, event: Any) -> bool: sys.stderr.write(f"[InputConnection] sendKeyEvent\n") return True def clearMetaKeyStates(self, states: int) -> bool: return True def performEditorAction(self, action_code: int) -> bool: sys.stderr.write(f"[InputConnection] performEditorAction: {action_code}\n") return True def performContextMenuAction(self, id: int) -> bool: sys.stderr.write(f"[InputConnection] performContextMenuAction: {id}\n") return True def commitContent(self, input_content: "InputContentInfo", flags: int, opts: Optional[Any] = None) -> bool: sys.stderr.write("[InputConnection] commitContent\n") return True def requestCursorUpdates(self, cursor_update_mode: int) -> bool: return True def closeConnection(self): pass def reportFullscreenMode(self, enabled: bool): pass def getSurroundingText(self, before_length: int, after_length: int, flags: int) -> Optional["SurroundingText"]: start = max(0, self._selection_start - before_length) end = min(len(self._text), self._selection_end + after_length) return SurroundingText( text=self._text[start:end], selection_start=self._selection_start - start, selection_end=self._selection_end - start, offset=start, ) def takeSnapshot(self) -> Optional[Any]: """获取输入快照""" return None def setComposingRegion(self, start: int, end: int) -> bool: self._composing_start = start self._composing_end = end return True def getCursorCapsMode(self, req_modes: int) -> int: return 0 def getTextBeforeCursor(self, length: int, flags: int) -> Optional[str]: end = self._selection_start start = max(0, end - length) return self._text[start:end] def getTextAfterCursor(self, length: int, flags: int) -> Optional[str]: start = self._selection_end end = min(len(self._text), start + length) return self._text[start:end] # ═══════════════════════════════════════════════════════════════ # ExtractedText # ═══════════════════════════════════════════════════════════════ class ExtractedText(Stub): """ExtractedText — 提取的文本""" FLAG_SINGLE_LINE = 0x0001 FLAG_SELECTING = 0x0002 def __init__(self): super().__init__() self.text: Optional[str] = None self.startOffset: int = 0 self.partialStartOffset: int = -1 self.partialEndOffset: int = -1 self.selectionStart: int = -1 self.selectionEnd: int = -1 self.flags: int = 0 def toString(self) -> str: return (f"ExtractedText(startOffset={self.startOffset}, " f"text={self.text}, " f"selection=({self.selectionStart}, {self.selectionEnd}))") # ═══════════════════════════════════════════════════════════════ # ExtractedTextRequest # ═══════════════════════════════════════════════════════════════ class ExtractedTextRequest(Stub): """ExtractedTextRequest — 提取文本请求""" def __init__(self): super().__init__() self.token: int = 0 self.flags: int = 0 self.hintMaxLines: int = 0 self.hintMaxChars: int = 0 # ═══════════════════════════════════════════════════════════════ # InputBinding # ═══════════════════════════════════════════════════════════════ class InputBinding(Stub): """InputBinding — 输入绑定""" def __init__(self, connection: Optional["InputConnection"] = None, uid: int = -1, pid: int = -1, private_ime_options: str = ""): super().__init__() self._connection: Optional["InputConnection"] = connection self._uid: int = uid self._pid: int = pid self._private_ime_options: str = private_ime_options def getConnection(self) -> Optional["InputConnection"]: return self._connection def getUid(self) -> int: return self._uid def getPid(self) -> int: return self._pid def getPrivateImeOptions(self) -> str: return self._private_ime_options def toString(self) -> str: return (f"InputBinding(uid={self._uid}, pid={self._pid}, " f"connection={self._connection})") # ═══════════════════════════════════════════════════════════════ # InputMethodSubtype # ═══════════════════════════════════════════════════════════════ class InputMethodSubtype(Stub): """InputMethodSubtype — 输入法子类型""" SUBTYPE_ID_NONE = -1 def __init__(self, locale: str = "", mode: str = "", extra_value: str = "", is_auxiliary: bool = False, overrides_ime_theme: bool = False, subtype_id: int = SUBTYPE_ID_NONE): super().__init__() self._locale: str = locale self._mode: str = mode self._extra_value: str = extra_value self._is_auxiliary: bool = is_auxiliary self._overrides_ime_theme: bool = overrides_ime_theme self._subtype_id: int = subtype_id self._display_name_res: int = 0 self._icon_res: int = 0 def getLocale(self) -> str: return self._locale def getMode(self) -> str: return self._mode def getExtraValue(self) -> str: return self._extra_value def getSubtypeId(self) -> int: return self._subtype_id def containsExtraValueKey(self, key: str) -> bool: if not self._extra_value: return False pairs = self._extra_value.split(",") for pair in pairs: kv = pair.split("=", 1) if len(kv) == 2 and kv[0].strip() == key: return True return False def getDisplayName(self, context: Any, package_name: str, app_info: Any) -> str: return self._locale or self._mode def isAuxiliary(self) -> bool: return self._is_auxiliary def overridesImeTheme(self) -> bool: return self._overrides_ime_theme def hashCode(self) -> int: return hash((self._locale, self._mode, self._extra_value)) & 0x7FFFFFFF def equals(self, other: Any) -> bool: if isinstance(other, InputMethodSubtype): return (self._locale == other._locale and self._mode == other._mode and self._extra_value == other._extra_value) return False def toString(self) -> str: return (f"InputMethodSubtype(locale={self._locale}, " f"mode={self._mode})") # ═══════════════════════════════════════════════════════════════ # CursorAnchorInfo # ═══════════════════════════════════════════════════════════════ class CursorAnchorInfo(Stub): """CursorAnchorInfo — 光标锚点信息""" FLAG_HAS_VISIBLE_REGION = 0x01 FLAG_IS_RTL = 0x02 def __init__(self): super().__init__() self._insertion_marker_baseline: float = 0.0 self._insertion_marker_top: float = 0.0 self._insertion_marker_bottom: float = 0.0 self._matrix: Optional[Any] = None self._character_bounds: Dict[int, Tuple[float, float, float, float]] = {} self._character_bounds_flags: Dict[int, int] = {} self._composing_text: Optional[str] = None self._composing_text_start: int = -1 def getInsertionMarkerBaseline(self) -> float: return self._insertion_marker_baseline def setInsertionMarkerBaseline(self, baseline: float): self._insertion_marker_baseline = baseline def getInsertionMarkerTop(self) -> float: return self._insertion_marker_top def setInsertionMarkerTop(self, top: float): self._insertion_marker_top = top def getInsertionMarkerBottom(self) -> float: return self._insertion_marker_bottom def setInsertionMarkerBottom(self, bottom: float): self._insertion_marker_bottom = bottom def getMatrix(self) -> Optional[Any]: return self._matrix def setMatrix(self, matrix: Any): self._matrix = matrix def getCharacterBounds(self, index: int) -> Optional[Tuple[float, float, float, float]]: return self._character_bounds.get(index) def setCharacterBounds(self, index: int, left: float, top: float, right: float, bottom: float, flags: int): self._character_bounds[index] = (left, top, right, bottom) self._character_bounds_flags[index] = flags def getCharacterBoundsFlags(self, index: int) -> int: return self._character_bounds_flags.get(index, 0) def getComposingText(self) -> Optional[str]: return self._composing_text def getComposingTextStart(self) -> int: return self._composing_text_start def toString(self) -> str: return (f"CursorAnchorInfo(baseline={self._insertion_marker_baseline}, " f"top={self._insertion_marker_top})") # ═══════════════════════════════════════════════════════════════ # CorrectionInfo # ═══════════════════════════════════════════════════════════════ class CorrectionInfo(Stub): """CorrectionInfo — 校正信息""" def __init__(self, old_text: str = "", new_text: str = ""): super().__init__() self._old_text: str = old_text self._new_text: str = new_text def getOldText(self) -> str: return self._old_text def getNewText(self) -> str: return self._new_text def toString(self) -> str: return f"CorrectionInfo('{self._old_text}' -> '{self._new_text}')" # ═══════════════════════════════════════════════════════════════ # CompletionInfo # ═══════════════════════════════════════════════════════════════ class CompletionInfo(Stub): """CompletionInfo — 补全信息""" def __init__(self, id: int = 0, position: int = 0, text: str = "", label: str = ""): super().__init__() self._id: int = id self._position: int = position self._text: str = text self._label: str = label def getId(self) -> int: return self._id def getPosition(self) -> int: return self._position def getText(self) -> str: return self._text def getLabel(self) -> str: return self._label def toString(self) -> str: return f"CompletionInfo(id={self._id}, text='{self._text}')" # ═══════════════════════════════════════════════════════════════ # SurroundingText # ═══════════════════════════════════════════════════════════════ class SurroundingText(Stub): """SurroundingText — 周围文本""" def __init__(self, text: str = "", selection_start: int = 0, selection_end: int = 0, offset: int = 0): super().__init__() self._text: str = text self._selection_start: int = selection_start self._selection_end: int = selection_end self._offset: int = offset def getText(self) -> str: return self._text def getSelectionStart(self) -> int: return self._selection_start def getSelectionEnd(self) -> int: return self._selection_end def getOffset(self) -> int: return self._offset def toString(self) -> str: return (f"SurroundingText(text='{self._text}', " f"selection=({self._selection_start}, {self._selection_end}), " f"offset={self._offset})") # ═══════════════════════════════════════════════════════════════ # InputContentInfo # ═══════════════════════════════════════════════════════════════ class InputContentInfo(Stub): """InputContentInfo — 输入内容信息""" def __init__(self, uri: Any = None, description: Any = None, link_uri: Any = None): super().__init__() self._uri: Any = uri self._description: Any = description self._link_uri: Any = link_uri def getUri(self) -> Any: return self._uri def getDescription(self) -> Any: return self._description def getLinkUri(self) -> Any: return self._link_uri def requestPermission(self): pass def releasePermission(self): pass def toString(self) -> str: return (f"InputContentInfo(uri={self._uri}, " f"description={self._description})") # ═══════════════════════════════════════════════════════════════ # InputConnectionWrapper # ═══════════════════════════════════════════════════════════════ class InputConnectionWrapper(InputConnection): """InputConnectionWrapper — 输入连接包装器""" def __init__(self, target: InputConnection, mutable: bool = True): super().__init__() self._target: InputConnection = target self._mutable: bool = mutable def setTarget(self, target: InputConnection): if self._mutable: self._target = target def getTarget(self) -> InputConnection: return self._target def getTextBeforeCursor(self, length: int, flags: int) -> Optional[str]: return self._target.getTextBeforeCursor(length, flags) def getTextAfterCursor(self, length: int, flags: int) -> Optional[str]: return self._target.getTextAfterCursor(length, flags) def getSelectedText(self, flags: int) -> Optional[str]: return self._target.getSelectedText(flags) def getSurroundingText(self, before_length: int, after_length: int, flags: int) -> Optional["SurroundingText"]: return self._target.getSurroundingText(before_length, after_length, flags) def getCursorCapsMode(self, req_modes: int) -> int: return self._target.getCursorCapsMode(req_modes) def getExtractedText(self, request: "ExtractedTextRequest", flags: int) -> Optional["ExtractedText"]: return self._target.getExtractedText(request, flags) def commitText(self, text: Any, new_cursor_position: int) -> bool: return self._target.commitText(text, new_cursor_position) def commitCompletion(self, completion: "CompletionInfo") -> bool: return self._target.commitCompletion(completion) def commitCorrection(self, correction: "CorrectionInfo") -> bool: return self._target.commitCorrection(correction) def commitContent(self, input_content: "InputContentInfo", flags: int, opts: Optional[Any] = None) -> bool: return self._target.commitContent(input_content, flags, opts) def setComposingText(self, text: Any, new_cursor_position: int) -> bool: return self._target.setComposingText(text, new_cursor_position) def setComposingRegion(self, start: int, end: int) -> bool: return self._target.setComposingRegion(start, end) def finishComposingText(self) -> bool: return self._target.finishComposingText() def setSelection(self, start: int, end: int) -> bool: return self._target.setSelection(start, end) def performEditorAction(self, action_code: int) -> bool: return self._target.performEditorAction(action_code) def performContextMenuAction(self, id: int) -> bool: return self._target.performContextMenuAction(id) def beginBatchEdit(self) -> bool: return self._target.beginBatchEdit() def endBatchEdit(self) -> bool: return self._target.endBatchEdit() def sendKeyEvent(self, event: Any) -> bool: return self._target.sendKeyEvent(event) def clearMetaKeyStates(self, states: int) -> bool: return self._target.clearMetaKeyStates(states) def deleteSurroundingText(self, before_length: int, after_length: int) -> bool: return self._target.deleteSurroundingText(before_length, after_length) def requestCursorUpdates(self, cursor_update_mode: int) -> bool: return self._target.requestCursorUpdates(cursor_update_mode) def closeConnection(self): self._target.closeConnection() def reportFullscreenMode(self, enabled: bool): self._target.reportFullscreenMode(enabled) # ═══════════════════════════════════════════════════════════════ # BaseInputConnection # ═══════════════════════════════════════════════════════════════ class BaseInputConnection(InputConnection): """BaseInputConnection — 输入连接基类""" def __init__(self, view: Any, full_editor: bool = True): super().__init__() self._view = view self._full_editor = full_editor self._text: str = "" self._selection_start: int = 0 self._selection_end: int = 0 self._composing_start: int = -1 self._composing_end: int = -1 def getEditable(self) -> Optional[Any]: return None def reportFullscreenMode(self, enabled: bool): pass def requestCursorUpdates(self, cursor_update_mode: int) -> bool: return self._full_editor def closeConnection(self): pass # ═══════════════════════════════════════════════════════════════ # __all__ # ═══════════════════════════════════════════════════════════════ __all__ = [ "InputMethodManager", "EditorInfo", "InputConnection", "ExtractedText", "ExtractedTextRequest", "InputBinding", "InputMethodSubtype", "CursorAnchorInfo", "CorrectionInfo", "CompletionInfo", "SurroundingText", "InputContentInfo", "InputConnectionWrapper", "BaseInputConnection", ]