""" Android View Accessibility 扩展组件 方法签名完全对齐 Android SDK """ from __future__ import annotations import time import threading from typing import Any, Callable, Dict, List, Optional, Set, Tuple from .._core import Stub, AndroidOnlyError from ..java.lang import RuntimeException # ═══════════════════════════════════════════════════════════════ # 无障碍事件常量 # ═══════════════════════════════════════════════════════════════ TYPE_VIEW_CLICKED = 0x00000001 TYPE_VIEW_LONG_CLICKED = 0x00000002 TYPE_VIEW_SELECTED = 0x00000004 TYPE_VIEW_FOCUSED = 0x00000008 TYPE_VIEW_TEXT_CHANGED = 0x00000010 TYPE_WINDOW_STATE_CHANGED = 0x00000020 TYPE_NOTIFICATION_STATE_CHANGED = 0x00000040 TYPE_VIEW_HOVER_ENTER = 0x00000080 TYPE_VIEW_HOVER_EXIT = 0x00000100 TYPE_TOUCH_EXPLORATION_GESTURE_START = 0x00000200 TYPE_TOUCH_EXPLORATION_GESTURE_END = 0x00000400 TYPE_WINDOW_CONTENT_CHANGED = 0x00000800 TYPE_VIEW_SCROLLED = 0x00001000 TYPE_VIEW_TEXT_SELECTION_CHANGED = 0x00002000 TYPE_ANNOUNCEMENT = 0x00004000 TYPE_VIEW_ACCESSIBILITY_FOCUSED = 0x00008000 TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED = 0x00010000 TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY = 0x00020000 TYPE_GESTURE_DETECTION_START = 0x00040000 TYPE_GESTURE_DETECTION_END = 0x00080000 TYPE_TOUCH_INTERACTION_START = 0x00100000 TYPE_TOUCH_INTERACTION_END = 0x00200000 TYPE_WINDOWS_CHANGED = 0x00400000 TYPE_VIEW_CONTEXT_CLICKED = 0x00800000 TYPE_ASSIST_READING_CONTEXT = 0x01000000 CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 0x00000001 CONTENT_CHANGE_TYPE_STATE_DESCRIPTION = 0x00000002 CONTENT_CHANGE_TYPE_SUBTREE = 0x00000004 CONTENT_CHANGE_TYPE_TEXT = 0x00000008 CONTENT_CHANGE_TYPE_UNDEFINED = 0x00000010 WINDOWS_CHANGE_ADDED = 0x00000001 WINDOWS_CHANGE_REMOVED = 0x00000002 WINDOWS_CHANGE_TITLE = 0x00000004 WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED = 0x00000008 WINDOWS_CHANGE_ACTIVE = 0x00000010 WINDOWS_CHANGE_PARENT = 0x00000020 WINDOWS_CHANGE_CHILDREN = 0x00000040 WINDOWS_CHANGE_BOUNDS = 0x00000080 WINDOWS_CHANGE_LAYER = 0x00000100 WINDOW_TYPE_ACCESSIBILITY_OVERLAY = 4 WINDOW_TYPE_APPLICATION = 1 WINDOW_TYPE_INPUT_METHOD = 2 WINDOW_TYPE_SPLIT_SCREEN_DIVIDER = 5 WINDOW_TYPE_SYSTEM = 3 FLAG_REPORT_VIEW_IDS = 0x10 FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 0x80 FLAG_REQUEST_FILTER_KEY_EVENTS = 0x20 FLAG_REQUEST_TOUCH_EXPLORATION_MODE = 0x40 FLAG_RETRIEVE_INTERACTIVE_WINDOWS = 0x08 FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x02 FLAG_REQUEST_ACCESSIBILITY_BUTTON = 0x100 CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT = 1 CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 2 CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 4 CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS = 8 CAPTIONING_TYPE_UNKNOWN = -1 CAPTIONING_TYPE_FEATURE = 0 CAPTIONING_TYPE_COUNTRY = 1 CAPTIONING_TYPE_SPOKEN = 2 CAPTIONING_TYPE_UNKNOWN_CHARACTER = 3 CAPTIONING_TYPE_MUSIC = 4 CAPTIONING_TYPE_SFX = 5 CAPTIONING_TYPE_VOICE = 6 CAPTIONING_PRESET_INVALID = -1 CAPTIONING_PRESET_CUSTOM = 0 CAPTIONING_PRESET_WHITE_ON_BLACK = 1 CAPTIONING_PRESET_BLACK_ON_WHITE = 2 CAPTIONING_PRESET_YELLOW_ON_BLACK = 3 CAPTIONING_PRESET_YELLOW_ON_BLUE = 4 CAPTIONING_PRESET_CUSTOM_BLACK_ON_WHITE = 5 CAPTIONING_PRESET_CUSTOM_WHITE_ON_BLACK = 6 CAPTIONING_PRESET_CUSTOM_YELLOW_ON_BLACK = 7 CAPTIONING_PRESET_CUSTOM_YELLOW_ON_BLUE = 8 # ═══════════════════════════════════════════════════════════════ # AccessibilityEvent # ═══════════════════════════════════════════════════════════════ class AccessibilityRecord(Stub): """AccessibilityRecord — 无障碍记录""" def __init__(self): super().__init__() self._source_view: Optional[Any] = None self._source_node_id: int = -1 self._source_window_id: int = -1 self._event_time: int = 0 self._package_name: Optional[str] = None self._class_name: Optional[str] = None self._text: List[str] = [] self._content_description: Optional[str] = None self._item_count: int = -1 self._current_item_index: int = -1 self._from_index: int = -1 self._to_index: int = -1 self._scrollable: bool = False self._password: bool = False self._checked: bool = False self._enabled: bool = True self._clickable: bool = False self._focusable: bool = False self._focused: bool = False self._long_clickable: bool = False self._selected: bool = False self._pass_through: bool = False self._before_text: Optional[str] = None self._removed_count: int = 0 self._added_count: int = 0 self._max_scroll_x: int = -1 self._max_scroll_y: int = -1 self._scroll_x: int = -1 self._scroll_y: int = -1 def setSource(self, view: Any) -> None: self._source_view = view def getSource(self) -> Optional[Any]: return self._source_view def setSourceNodeId(self, node_id: int) -> None: self._source_node_id = node_id def getSourceNodeId(self) -> int: return self._source_node_id def setWindowId(self, window_id: int) -> None: self._source_window_id = window_id def getWindowId(self) -> int: return self._source_window_id def setEventTime(self, time: int) -> None: self._event_time = time def getEventTime(self) -> int: return self._event_time if self._event_time else int(time.time() * 1000) def setPackageName(self, name: str) -> None: self._package_name = name def getPackageName(self) -> Optional[str]: return self._package_name def setClassName(self, name: str) -> None: self._class_name = name def getClassName(self) -> Optional[str]: return self._class_name def setText(self, text: List[str]) -> None: self._text = list(text) def getText(self) -> List[str]: return list(self._text) def setContentDescription(self, desc: str) -> None: self._content_description = desc def getContentDescription(self) -> Optional[str]: return self._content_description def setItemCount(self, count: int) -> None: self._item_count = count def getItemCount(self) -> int: return self._item_count def setCurrentItemIndex(self, index: int) -> None: self._current_item_index = index def getCurrentItemIndex(self) -> int: return self._current_item_index def setFromIndex(self, index: int) -> None: self._from_index = index def getFromIndex(self) -> int: return self._from_index def setToIndex(self, index: int) -> None: self._to_index = index def getToIndex(self) -> int: return self._to_index def setScrollable(self, scrollable: bool) -> None: self._scrollable = scrollable def isScrollable(self) -> bool: return self._scrollable def setPassword(self, password: bool) -> None: self._password = password def isPassword(self) -> bool: return self._password def setChecked(self, checked: bool) -> None: self._checked = checked def isChecked(self) -> bool: return self._checked def setEnabled(self, enabled: bool) -> None: self._enabled = enabled def isEnabled(self) -> bool: return self._enabled def setClickable(self, clickable: bool) -> None: self._clickable = clickable def isClickable(self) -> bool: return self._clickable def setFocusable(self, focusable: bool) -> None: self._focusable = focusable def isFocusable(self) -> bool: return self._focusable def setFocused(self, focused: bool) -> None: self._focused = focused def isFocused(self) -> bool: return self._focused def setLongClickable(self, long_clickable: bool) -> None: self._long_clickable = long_clickable def isLongClickable(self) -> bool: return self._long_clickable def setSelected(self, selected: bool) -> None: self._selected = selected def isSelected(self) -> bool: return self._selected def setBeforeText(self, text: str) -> None: self._before_text = text def getBeforeText(self) -> Optional[str]: return self._before_text def setRemovedCount(self, count: int) -> None: self._removed_count = count def getRemovedCount(self) -> int: return self._removed_count def setAddedCount(self, count: int) -> None: self._added_count = count def getAddedCount(self) -> int: return self._added_count def setMaxScrollX(self, max_x: int) -> None: self._max_scroll_x = max_x def getMaxScrollX(self) -> int: return self._max_scroll_x def setMaxScrollY(self, max_y: int) -> None: self._max_scroll_y = max_y def getMaxScrollY(self) -> int: return self._max_scroll_y def setScrollX(self, x: int) -> None: self._scroll_x = x def getScrollX(self) -> int: return self._scroll_x def setScrollY(self, y: int) -> None: self._scroll_y = y def getScrollY(self) -> int: return self._scroll_y def toString(self) -> str: return (f"AccessibilityRecord(class={self._class_name}, " f"text={self._text})") class AccessibilityEvent(AccessibilityRecord): """AccessibilityEvent — 无障碍事件""" INVALID_POSITION = -1 MAX_TEXT_LENGTH = 500 def __init__(self, event_type: int = 0): super().__init__() self._event_type = event_type self._event_type_int = event_type self._movement_granularity: int = 0 self._action: int = 0 self._content_change_types: int = 0 self._window_changes: int = 0 @staticmethod def obtain(event_type: int = 0) -> "AccessibilityEvent": return AccessibilityEvent(event_type) @staticmethod def obtain(event: "AccessibilityEvent") -> "AccessibilityEvent": e = AccessibilityEvent(event.getEventType()) return e def getEventType(self) -> int: return self._event_type def setEventType(self, event_type: int) -> None: self._event_type = event_type def setMovementGranularity(self, granularity: int) -> None: self._movement_granularity = granularity def getMovementGranularity(self) -> int: return self._movement_granularity def setAction(self, action: int) -> None: self._action = action def getAction(self) -> int: return self._action def setContentChangeTypes(self, types: int) -> None: self._content_change_types = types def getContentChangeTypes(self) -> int: return self._content_change_types def setWindowChanges(self, changes: int) -> None: self._window_changes = changes def getWindowChanges(self) -> int: return self._window_changes def recycle(self) -> None: pass def toString(self) -> str: return f"AccessibilityEvent(type={self._event_type})" class AccessibilityWindowInfo(Stub): """AccessibilityWindowInfo — 无障碍窗口信息""" TYPE_ACCESSIBILITY_OVERLAY = WINDOW_TYPE_ACCESSIBILITY_OVERLAY TYPE_APPLICATION = WINDOW_TYPE_APPLICATION TYPE_INPUT_METHOD = WINDOW_TYPE_INPUT_METHOD TYPE_SPLIT_SCREEN_DIVIDER = WINDOW_TYPE_SPLIT_SCREEN_DIVIDER TYPE_SYSTEM = WINDOW_TYPE_SYSTEM UNDEFINED = -1 ACTIVE = 0 INACTIVE = 1 def __init__(self): super().__init__() self._type: int = self.TYPE_APPLICATION self._id: int = -1 self._layer: int = 0 self._is_active = False self._is_focused = False self._is_accessibility_focused = False self._title: Optional[str] = None self._root: Optional[Any] = None self._parent: Optional["AccessibilityWindowInfo"] = None self._bounds: Tuple[int, int, int, int] = (0, 0, 0, 0) self._anchor: Optional[Any] = None self._picture_in_picture = False self._first_child: Optional["AccessibilityWindowInfo"] = None self._last_child: Optional["AccessibilityWindowInfo"] = None self._child_count: int = 0 def getType(self) -> int: return self._type def setType(self, type_val: int) -> None: self._type = type_val def getId(self) -> int: return self._id def setId(self, id_val: int) -> None: self._id = id_val def setLayer(self, layer: int) -> None: self._layer = layer def getLayer(self) -> int: return self._layer def isActive(self) -> bool: return self._is_active def setActive(self, active: bool) -> None: self._is_active = active def isFocused(self) -> bool: return self._is_focused def setFocused(self, focused: bool) -> None: self._is_focused = focused def isAccessibilityFocused(self) -> bool: return self._is_accessibility_focused def setAccessibilityFocused(self, focused: bool) -> None: self._is_accessibility_focused = focused def getTitle(self) -> Optional[str]: return self._title def setTitle(self, title: str) -> None: self._title = title def setBoundsInScreen(self, bounds: Tuple[int, int, int, int]) -> None: self._bounds = bounds def getBoundsInScreen(self) -> Tuple[int, int, int, int]: return self._bounds def setAnchorId(self, anchor: Any) -> None: self._anchor = anchor def getAnchorId(self) -> Optional[Any]: return self._anchor def isInPictureInPictureMode(self) -> bool: return self._picture_in_picture def setPictureInPictureMode(self, pip: bool) -> None: self._picture_in_picture = pip def getRoot(self) -> Optional[Any]: return self._root def setRoot(self, root: Any) -> None: self._root = root def getParent(self) -> Optional["AccessibilityWindowInfo"]: return self._parent def getChild(self, index: int) -> Optional["AccessibilityWindowInfo"]: if index == 0: return self._first_child return None def getChildCount(self) -> int: return self._child_count @staticmethod def obtain() -> "AccessibilityWindowInfo": return AccessibilityWindowInfo() @staticmethod def obtain(info: "AccessibilityWindowInfo") -> "AccessibilityWindowInfo": return AccessibilityWindowInfo() def recycle(self) -> None: pass def toString(self) -> str: types = {1: "APPLICATION", 2: "INPUT_METHOD", 3: "SYSTEM", 4: "OVERLAY"} return (f"AccessibilityWindowInfo(type={types.get(self._type, 'UNKNOWN')}, " f"id={self._id})") # ═══════════════════════════════════════════════════════════════ # AccessibilityManager 扩展 # ═══════════════════════════════════════════════════════════════ class AccessibilityManager(Stub): """AccessibilityManager — 无障碍管理器扩展""" FLAG_REPORT_VIEW_IDS = FLAG_REPORT_VIEW_IDS FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY = FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY FLAG_REQUEST_FILTER_KEY_EVENTS = FLAG_REQUEST_FILTER_KEY_EVENTS FLAG_REQUEST_TOUCH_EXPLORATION_MODE = FLAG_REQUEST_TOUCH_EXPLORATION_MODE FLAG_RETRIEVE_INTERACTIVE_WINDOWS = FLAG_RETRIEVE_INTERACTIVE_WINDOWS FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = FLAG_INCLUDE_NOT_IMPORTANT_VIEWS FLAG_REQUEST_ACCESSIBILITY_BUTTON = FLAG_REQUEST_ACCESSIBILITY_BUTTON class AccessibilityStateChangeListener(Stub): """AccessibilityManager.AccessibilityStateChangeListener""" def onAccessibilityStateChanged(self, enabled: bool) -> None: pass class TouchExplorationStateChangeListener(Stub): """AccessibilityManager.TouchExplorationStateChangeListener""" def onTouchExplorationStateChanged(self, enabled: bool) -> None: pass class HighTextContrastChangeListener(Stub): def onHighTextContrastStateChanged(self, enabled: bool) -> None: pass def __init__(self): super().__init__() self._enabled = False self._touch_exploration_enabled = False self._high_text_contrast_enabled = False self._state_listeners: List[AccessibilityStateChangeListener] = [] self._touch_listeners: List[TouchExplorationStateChangeListener] = [] self._contrast_listeners: List[Any] = [] self._accessibility_button_available = False def isEnabled(self) -> bool: return self._enabled def setEnabled(self, enabled: bool) -> None: old = self._enabled self._enabled = enabled if old != enabled: for listener in list(self._state_listeners): listener.onAccessibilityStateChanged(enabled) def isTouchExplorationEnabled(self) -> bool: return self._touch_exploration_enabled def setTouchExplorationEnabled(self, enabled: bool) -> None: old = self._touch_exploration_enabled self._touch_exploration_enabled = enabled if old != enabled: for listener in list(self._touch_listeners): listener.onTouchExplorationStateChanged(enabled) def isHighTextContrastEnabled(self) -> bool: return self._high_text_contrast_enabled def addAccessibilityStateChangeListener(self, listener: AccessibilityStateChangeListener) -> None: if listener not in self._state_listeners: self._state_listeners.append(listener) def removeAccessibilityStateChangeListener(self, listener: AccessibilityStateChangeListener) -> None: if listener in self._state_listeners: self._state_listeners.remove(listener) def addTouchExplorationStateChangeListener(self, listener: TouchExplorationStateChangeListener) -> None: if listener not in self._touch_listeners: self._touch_listeners.append(listener) def removeTouchExplorationStateChangeListener(self, listener: TouchExplorationStateChangeListener) -> None: if listener in self._touch_listeners: self._touch_listeners.remove(listener) def addHighTextContrastStateChangeListener(self, listener: Any) -> None: if listener not in self._contrast_listeners: self._contrast_listeners.append(listener) def removeHighTextContrastStateChangeListener(self, listener: Any) -> None: if listener in self._contrast_listeners: self._contrast_listeners.remove(listener) def setAccessibilityButtonAvailable(self, available: bool) -> None: self._accessibility_button_available = available def isAccessibilityButtonAvailable(self) -> bool: return self._accessibility_button_available def sendAccessibilityEvent(self, event: AccessibilityEvent) -> None: pass def interrupt(self) -> None: pass def getInstalledAccessibilityServiceList(self) -> List[Any]: return [] def getEnabledAccessibilityServiceList(self) -> List[Any]: return [] def toString(self) -> str: return (f"AccessibilityManager(enabled={self._enabled}, " f"touchExploration={self._touch_exploration_enabled})") # ═══════════════════════════════════════════════════════════════ # CaptioningManager # ═══════════════════════════════════════════════════════════════ class CaptioningManager(Stub): """CaptioningManager — 字幕管理""" CAPTIONING_TYPE_UNKNOWN = CAPTIONING_TYPE_UNKNOWN CAPTIONING_TYPE_FEATURE = CAPTIONING_TYPE_FEATURE CAPTIONING_TYPE_COUNTRY = CAPTIONING_TYPE_COUNTRY CAPTIONING_TYPE_SPOKEN = CAPTIONING_TYPE_SPOKEN CAPTIONING_TYPE_UNKNOWN_CHARACTER = CAPTIONING_TYPE_UNKNOWN_CHARACTER CAPTIONING_TYPE_MUSIC = CAPTIONING_TYPE_MUSIC CAPTIONING_TYPE_SFX = CAPTIONING_TYPE_SFX CAPTIONING_TYPE_VOICE = CAPTIONING_TYPE_VOICE class CaptionStyle(Stub): """CaptioningManager.CaptionStyle — 字幕风格""" EDGE_TYPE_NONE = 0 EDGE_TYPE_OUTLINE = 1 EDGE_TYPE_DROP_SHADOW = 2 EDGE_TYPE_RAISED = 3 EDGE_TYPE_DEPRESSED = 4 PRESET_CUSTOM = CAPTIONING_PRESET_CUSTOM PRESET_WHITE_ON_BLACK = CAPTIONING_PRESET_WHITE_ON_BLACK PRESET_BLACK_ON_WHITE = CAPTIONING_PRESET_BLACK_ON_WHITE PRESET_YELLOW_ON_BLACK = CAPTIONING_PRESET_YELLOW_ON_BLACK PRESET_YELLOW_ON_BLUE = CAPTIONING_PRESET_YELLOW_ON_BLUE def __init__(self): super().__init__() self._foreground_color: int = 0xFFFFFFFF self._background_color: int = 0xFF000000 self._edge_color: int = 0xFF000000 self._edge_type: int = self.EDGE_TYPE_NONE self._window_color: int = 0xFF000000 self._foreground_size: int = 1 self._preset: int = CAPTIONING_PRESET_WHITE_ON_BLACK def getForegroundColor(self) -> int: return self._foreground_color def getBackgroundColor(self) -> int: return self._background_color def getEdgeColor(self) -> int: return self._edge_color def getEdgeType(self) -> int: return self._edge_type def getWindowColor(self) -> int: return self._window_color def getForegroundSize(self) -> int: return self._foreground_size def getPreset(self) -> int: return self._preset def hasBackgroundColor(self) -> bool: return (self._background_color >> 24) > 0 def hasForegroundColor(self) -> bool: return (self._foreground_color >> 24) > 0 def hasEdgeType(self) -> bool: return self._edge_type != self.EDGE_TYPE_NONE def hasWindowColor(self) -> bool: return (self._window_color >> 24) > 0 @staticmethod def getCustomStyle(style_id: int) -> "CaptioningManager.CaptionStyle": return CaptioningManager.CaptionStyle() @staticmethod def getPresetStyle(preset: int) -> "CaptioningManager.CaptionStyle": return CaptioningManager.CaptionStyle() def applyStyle(self, style: "CaptioningManager.CaptionStyle") -> None: pass def toString(self) -> str: return f"CaptionStyle(preset={self._preset})" class CaptioningChangeListener(Stub): """CaptioningManager.CaptioningChangeListener""" def onEnabledChanged(self, enabled: bool) -> None: pass def onUserStyleChanged(self, style: "CaptioningManager.CaptionStyle") -> None: pass def onLocaleChanged(self, locale: Any) -> None: pass def onFontScaleChanged(self, scale: float) -> None: pass def __init__(self): super().__init__() self._enabled = False self._user_style: Optional[CaptioningManager.CaptionStyle] = None self._font_scale: float = 1.0 self._locale: Optional[Any] = None self._listeners: List[CaptioningManager.CaptioningChangeListener] = [] def isEnabled(self) -> bool: return self._enabled def getFontScale(self) -> float: return self._font_scale def getLocale(self) -> Optional[Any]: return self._locale def getUserStyle(self) -> CaptionStyle: return self._user_style or CaptioningManager.CaptionStyle() def addCaptioningChangeListener(self, listener: CaptioningChangeListener) -> None: if listener not in self._listeners: self._listeners.append(listener) def removeCaptioningChangeListener(self, listener: CaptioningChangeListener) -> None: if listener in self._listeners: self._listeners.remove(listener) def toString(self) -> str: return f"CaptioningManager(enabled={self._enabled})" IllegalArgumentException = type("IllegalStateException", (RuntimeException,), {})