""" Android View Accessibility 组件 — AccessibilityEvent, AccessibilityNodeInfo, AccessibilityService, AccessibilityManager 等 方法签名完全对齐 Android SDK """ import sys from typing import Any, Callable, Dict, List, Optional, Tuple from .._core import Stub, AndroidOnlyError, android_context # ═══════════════════════════════════════════════════════════════ # AccessibilityEvent # ═══════════════════════════════════════════════════════════════ class AccessibilityEvent(Stub): """AccessibilityEvent — 无障碍事件""" # 事件类型 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_CONTENT = 0x01000000 # 内容变化类型 CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 0x00000001 CONTENT_CHANGE_TYPE_STATE_DESCRIPTION = 0x00000002 CONTENT_CHANGE_TYPE_SUBTREE = 0x00000004 CONTENT_CHANGE_TYPE_TEXT = 0x00000010 CONTENT_CHANGE_TYPE_UNDEFINED = 0x00000000 CONTENT_CHANGE_TYPE_PANE_TITLE = 0x00000020 CONTENT_CHANGE_TYPE_DRAG_STARTED = 0x00000040 CONTENT_CHANGE_TYPE_DRAG_CANCELLED = 0x00000080 CONTENT_CHANGE_TYPE_DRAG_DROPPED = 0x00000100 CONTENT_CHANGE_TYPE_CLEAR_FOCUS = 0x00000200 CONTENT_CHANGE_TYPE_ADDED = 0x00000400 CONTENT_CHANGE_TYPE_REMOVED = 0x00000800 CONTENT_CHANGE_TYPE_VISIBILITY_CHANGED = 0x00001000 CONTENT_CHANGE_TYPE_PANE_APPEARED = 0x00002000 CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 0x00004000 CONTENT_CHANGE_TYPE_ERROR = 0x00008000 # 窗口变化类型 WINDOWS_CHANGE_ADDED = 0x00000001 WINDOWS_CHANGE_REMOVED = 0x00000002 WINDOWS_CHANGE_TITLE = 0x00000004 WINDOWS_CHANGE_BOUNDS = 0x00000008 WINDOWS_CHANGE_LAYER = 0x00000010 WINDOWS_CHANGE_ACTIVE = 0x00000020 WINDOWS_CHANGE_FOCUSED = 0x00000040 WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED = 0x00000080 WINDOWS_CHANGE_PARENT = 0x00000100 WINDOWS_CHANGE_CHILDREN = 0x00000200 WINDOWS_CHANGE_PIP = 0x00000400 MAX_TEXT_LENGTH = 500 # 预留回收池 _pool: List["AccessibilityEvent"] = [] _pool_max = 10 def __init__(self): super().__init__() self._event_type: int = 0 self._source: Optional[Any] = None self._source_node_id: int = -1 self._class_name: Optional[str] = None self._package_name: Optional[str] = None self._content_description: Optional[str] = None self._text: List[str] = [] self._action: int = 0 self._from_index: int = -1 self._to_index: int = -1 self._item_count: int = 0 self._current_item_index: int = 0 self._enabled: bool = True self._password: bool = False self._checked: bool = False self._full_package_name: Optional[str] = None self._before_text: Optional[str] = None self._movement_granularity: int = 0 self._added_count: int = 0 self._removed_count: int = 0 self._content_change_types: int = 0 self._scroll_delta_x: float = 0.0 self._scroll_delta_y: float = 0.0 self._max_scroll_x: float = 0.0 self._max_scroll_y: float = 0.0 self._event_time: int = 0 self._parcelable_data: Dict[str, Any] = {} self._record_count: int = 0 @staticmethod def obtain(event: Optional["AccessibilityEvent"] = None) -> "AccessibilityEvent": if AccessibilityEvent._pool: result = AccessibilityEvent._pool.pop() else: result = AccessibilityEvent() if event is not None: result._event_type = event._event_type result._source = event._source result._class_name = event._class_name result._package_name = event._package_name result._content_description = event._content_description result._text = list(event._text) result._action = event._action return result def recycle(self): self._event_type = 0 self._source = None self._source_node_id = -1 self._class_name = None self._package_name = None self._content_description = None self._text.clear() self._action = 0 self._from_index = -1 self._to_index = -1 self._item_count = 0 if len(AccessibilityEvent._pool) < AccessibilityEvent._pool_max: AccessibilityEvent._pool.append(self) def getEventType(self) -> int: return self._event_type def setEventType(self, event_type: int): self._event_type = event_type def getSource(self) -> Optional[Any]: return self._source def setSource(self, source: Optional[Any]): self._source = source def getClassName(self) -> Optional[str]: return self._class_name def setClassName(self, class_name: Optional[str]): self._class_name = class_name def getPackageName(self) -> Optional[str]: return self._package_name def setPackageName(self, package_name: Optional[str]): self._package_name = package_name def getContentDescription(self) -> Optional[str]: return self._content_description def setContentDescription(self, content_description: Optional[str]): self._content_description = content_description def getText(self) -> List[str]: return list(self._text) def getAction(self) -> int: return self._action def setAction(self, action: int): self._action = action def setFromIndex(self, from_index: int): self._from_index = from_index def getFromIndex(self) -> int: return self._from_index def setToIndex(self, to_index: int): self._to_index = to_index def getToIndex(self) -> int: return self._to_index def setItemCount(self, item_count: int): self._item_count = item_count def getItemCount(self) -> int: return self._item_count def setCurrentItemIndex(self, index: int): self._current_item_index = index def getCurrentItemIndex(self) -> int: return self._current_item_index def isEnabled(self) -> bool: return self._enabled def setEnabled(self, enabled: bool): self._enabled = enabled def isPassword(self) -> bool: return self._password def setPassword(self, password: bool): self._password = password def isChecked(self) -> bool: return self._checked def setChecked(self, checked: bool): self._checked = checked def getBeforeText(self) -> Optional[str]: return self._before_text def setBeforeText(self, before_text: Optional[str]): self._before_text = before_text def getMovementGranularity(self) -> int: return self._movement_granularity def setMovementGranularity(self, granularity: int): self._movement_granularity = granularity def getAddedCount(self) -> int: return self._added_count def setAddedCount(self, added_count: int): self._added_count = added_count def getRemovedCount(self) -> int: return self._removed_count def setRemovedCount(self, removed_count: int): self._removed_count = removed_count def getContentChangeTypes(self) -> int: return self._content_change_types def setContentChangeTypes(self, change_types: int): self._content_change_types = change_types def getScrollDeltaX(self) -> float: return self._scroll_delta_x def setScrollDeltaX(self, delta_x: float): self._scroll_delta_x = delta_x def getScrollDeltaY(self) -> float: return self._scroll_delta_y def setScrollDeltaY(self, delta_y: float): self._scroll_delta_y = delta_y def getMaxScrollX(self) -> float: return self._max_scroll_x def setMaxScrollX(self, max_x: float): self._max_scroll_x = max_x def getMaxScrollY(self) -> float: return self._max_scroll_y def setMaxScrollY(self, max_y: float): self._max_scroll_y = max_y def getEventTime(self) -> int: return self._event_time def setEventTime(self, event_time: int): self._event_time = event_time def getParcelableData(self) -> Any: return self._parcelable_data def setParcelableData(self, data: Any): if isinstance(data, dict): self._parcelable_data = data else: self._parcelable_data = {"data": data} def getRecordCount(self) -> int: return self._record_count def appendRecord(self, record: "AccessibilityRecord"): self._record_count += 1 def getRecord(self, index: int) -> Optional["AccessibilityRecord"]: return None def toString(self) -> str: return (f"AccessibilityEvent(type={hex(self._event_type)}, " f"className={self._class_name}, " f"packageName={self._package_name}, " f"text={self._text})") # ═══════════════════════════════════════════════════════════════ # AccessibilityRecord # ═══════════════════════════════════════════════════════════════ class AccessibilityRecord(Stub): """AccessibilityRecord — 无障碍记录""" def __init__(self): super().__init__() self._source: Optional[Any] = None self._source_node_id: int = -1 self._class_name: Optional[str] = None self._package_name: Optional[str] = None self._content_description: Optional[str] = None self._text: List[str] = [] self._from_index: int = -1 self._to_index: int = -1 self._item_count: int = 0 self._current_item_index: int = 0 self._scrollable: bool = False self._scroll_x: int = 0 self._scroll_y: int = 0 self._max_scroll_x: int = 0 self._max_scroll_y: int = 0 self._enabled: bool = True self._password: bool = False self._checked: bool = False self._full_package_name: Optional[str] = None self._before_text: Optional[str] = None self._removed_count: int = 0 self._added_count: int = 0 def setSource(self, source: Optional[Any]): self._source = source def getSource(self) -> Optional[Any]: return self._source def setClassName(self, class_name: Optional[str]): self._class_name = class_name def getClassName(self) -> Optional[str]: return self._class_name def setContentDescription(self, content_description: Optional[str]): self._content_description = content_description def getContentDescription(self) -> Optional[str]: return self._content_description def setPackageName(self, package_name: Optional[str]): self._package_name = package_name def getPackageName(self) -> Optional[str]: return self._package_name def getText(self) -> List[str]: return list(self._text) def setFromIndex(self, from_index: int): self._from_index = from_index def getFromIndex(self) -> int: return self._from_index def setToIndex(self, to_index: int): self._to_index = to_index def getToIndex(self) -> int: return self._to_index def setItemCount(self, item_count: int): self._item_count = item_count def getItemCount(self) -> int: return self._item_count def setScrollable(self, scrollable: bool): self._scrollable = scrollable def getScrollable(self) -> bool: return self._scrollable def setScrollX(self, scroll_x: int): self._scroll_x = scroll_x def getScrollX(self) -> int: return self._scroll_x def setScrollY(self, scroll_y: int): self._scroll_y = scroll_y def getScrollY(self) -> int: return self._scroll_y def setMaxScrollX(self, max_scroll_x: int): self._max_scroll_x = max_scroll_x def getMaxScrollX(self) -> int: return self._max_scroll_x def setMaxScrollY(self, max_scroll_y: int): self._max_scroll_y = max_scroll_y def getMaxScrollY(self) -> int: return self._max_scroll_y def isEnabled(self) -> bool: return self._enabled def setEnabled(self, enabled: bool): self._enabled = enabled def isPassword(self) -> bool: return self._password def setPassword(self, password: bool): self._password = password def isChecked(self) -> bool: return self._checked def setChecked(self, checked: bool): self._checked = checked def getBeforeText(self) -> Optional[str]: return self._before_text def setBeforeText(self, before_text: Optional[str]): self._before_text = before_text def getAddedCount(self) -> int: return self._added_count def setAddedCount(self, added_count: int): self._added_count = added_count def getRemovedCount(self) -> int: return self._removed_count def setRemovedCount(self, removed_count: int): self._removed_count = removed_count def toString(self) -> str: return (f"AccessibilityRecord(className={self._class_name}, " f"packageName={self._package_name})") # ═══════════════════════════════════════════════════════════════ # AccessibilityNodeInfo # ═══════════════════════════════════════════════════════════════ class AccessibilityNodeInfo(Stub): """AccessibilityNodeInfo — 无障碍节点信息""" FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000002 FLAG_REPORT_VIEW_IDS = 0x00000010 FLAG_PREFETCH_PREDECESSORS = 0x00000001 FLAG_PREFETCH_SIBLINGS = 0x00000002 FLAG_PREFETCH_DESCENDANTS = 0x00000004 ACTION_CLICK = 0x00000010 ACTION_LONG_CLICK = 0x00000020 ACTION_FOCUS = 0x00000040 ACTION_CLEAR_FOCUS = 0x00000080 ACTION_SELECT = 0x00000100 ACTION_CLEAR_SELECTION = 0x00000200 ACTION_ACCESSIBILITY_FOCUS = 0x00000400 ACTION_CLEAR_ACCESSIBILITY_FOCUS = 0x00000800 ACTION_SCROLL_FORWARD = 0x00001000 ACTION_SCROLL_BACKWARD = 0x00002000 ACTION_SCROLL_TO_POSITION = 0x00004000 ACTION_SET_SELECTION = 0x00008000 ACTION_COPY = 0x00010000 ACTION_PASTE = 0x00020000 ACTION_CUT = 0x00030000 ACTION_SET_TEXT = 0x00040000 ACTION_EXPAND = 0x00080000 ACTION_COLLAPSE = 0x00100000 ACTION_DISMISS = 0x00200000 ACTION_MOVE_WINDOW = 0x00400000 ACTION_SHOW_ON_SCREEN = 0x00800000 ACTION_CONTEXT_CLICK = 0x01000000 ACTION_SCROLL_UP = 0x02000000 ACTION_SCROLL_DOWN = 0x04000000 ACTION_SCROLL_LEFT = 0x08000000 ACTION_SCROLL_RIGHT = 0x10000000 ACTION_PAGE_UP = 0x20000000 ACTION_PAGE_DOWN = 0x40000000 ACTION_PAGE_LEFT = 0x80000000 ACTION_PAGE_RIGHT = 0x100000000 # 移动粒度 MOVEMENT_GRANULARITY_CHARACTER = 1 MOVEMENT_GRANULARITY_WORD = 2 MOVEMENT_GRANULARITY_LINE = 4 MOVEMENT_GRANULARITY_PARAGRAPH = 8 MOVEMENT_GRANULARITY_PAGE = 16 def __init__(self): super().__init__() self._children: List["AccessibilityNodeInfo"] = [] self._actions: int = 0 self._clickable: bool = False self._focusable: bool = False self._long_clickable: bool = False self._scrollable: bool = False self._content_description: Optional[str] = None self._text: Optional[str] = None self._class_name: Optional[str] = None self._package_name: Optional[str] = None self._parent: Optional["AccessibilityNodeInfo"] = None self._view_id: Optional[str] = None self._enabled: bool = True self._password: bool = False self._checked: bool = False self._checkable: bool = False self._selected: bool = False self._focused: bool = False self._visible_to_user: bool = True self._accessibility_focused: bool = False self._can_open_popup: bool = False self._dismissable: bool = False self._editable: bool = False self._multi_line: bool = False self._important_for_accessibility: bool = True self._pane_title: Optional[str] = None self._hint_text: Optional[str] = None self._tooltip_text: Optional[str] = None self._error: Optional[str] = None self._min_text_length: int = -1 self._max_text_length: int = -1 self._child_count: int = 0 self._bounds_in_parent: Optional[Tuple[int, int, int, int]] = None self._bounds_in_screen: Optional[Tuple[int, int, int, int]] = None def findAccessibilityNodeInfosByText(self, text: str) -> List["AccessibilityNodeInfo"]: result: List["AccessibilityNodeInfo"] = [] if self._text and text and text.lower() in self._text.lower(): result.append(self) for child in self._children: result.extend(child.findAccessibilityNodeInfosByText(text)) return result def findAccessibilityNodeInfosByViewId(self, view_id: str) -> List["AccessibilityNodeInfo"]: result: List["AccessibilityNodeInfo"] = [] if self._view_id == view_id: result.append(self) for child in self._children: result.extend(child.findAccessibilityNodeInfosByViewId(view_id)) return result def performAction(self, action: int) -> bool: if action & self._actions: sys.stderr.write(f"[AccessibilityNodeInfo] performAction: {hex(action)}\n") return True return False def setClickable(self, clickable: bool): self._clickable = clickable def isClickable(self) -> bool: return self._clickable def setFocusable(self, focusable: bool): self._focusable = focusable def isFocusable(self) -> bool: return self._focusable def setLongClickable(self, long_clickable: bool): self._long_clickable = long_clickable def isLongClickable(self) -> bool: return self._long_clickable def setScrollable(self, scrollable: bool): self._scrollable = scrollable def isScrollable(self) -> bool: return self._scrollable def setContentDescription(self, content_description: Optional[str]): self._content_description = content_description def getContentDescription(self) -> Optional[str]: return self._content_description def setText(self, text: Optional[str]): self._text = text def getText(self) -> Optional[str]: return self._text def setClassName(self, class_name: Optional[str]): self._class_name = class_name def getClassName(self) -> Optional[str]: return self._class_name def setPackageName(self, package_name: Optional[str]): self._package_name = package_name def getPackageName(self) -> Optional[str]: return self._package_name def getChild(self, index: int) -> Optional["AccessibilityNodeInfo"]: if 0 <= index < len(self._children): return self._children[index] return None def setParent(self, parent: Optional["AccessibilityNodeInfo"]): self._parent = parent def getParent(self) -> Optional["AccessibilityNodeInfo"]: return self._parent def getChildCount(self) -> int: return len(self._children) def addChild(self, child: "AccessibilityNodeInfo"): self._children.append(child) child._parent = self def addAction(self, action: int): self._actions |= action def setActions(self, actions: int): self._actions = actions def getActions(self) -> int: return self._actions def setViewIdResourceName(self, view_id: str): self._view_id = view_id def getViewIdResourceName(self) -> Optional[str]: return self._view_id def isEnabled(self) -> bool: return self._enabled def setEnabled(self, enabled: bool): self._enabled = enabled def isPassword(self) -> bool: return self._password def setPassword(self, password: bool): self._password = password def isChecked(self) -> bool: return self._checked def setChecked(self, checked: bool): self._checked = checked def isCheckable(self) -> bool: return self._checkable def setCheckable(self, checkable: bool): self._checkable = checkable def isSelected(self) -> bool: return self._selected def setSelected(self, selected: bool): self._selected = selected def isFocused(self) -> bool: return self._focused def setFocused(self, focused: bool): self._focused = focused def isVisibleToUser(self) -> bool: return self._visible_to_user def setVisibleToUser(self, visible: bool): self._visible_to_user = visible def isAccessibilityFocused(self) -> bool: return self._accessibility_focused def setAccessibilityFocused(self, focused: bool): self._accessibility_focused = focused def isEditable(self) -> bool: return self._editable def setEditable(self, editable: bool): self._editable = editable def isMultiLine(self) -> bool: return self._multi_line def setMultiLine(self, multi_line: bool): self._multi_line = multi_line def isImportantForAccessibility(self) -> bool: return self._important_for_accessibility def setImportantForAccessibility(self, important: bool): self._important_for_accessibility = important def setPaneTitle(self, pane_title: Optional[str]): self._pane_title = pane_title def getPaneTitle(self) -> Optional[str]: return self._pane_title def setHintText(self, hint_text: Optional[str]): self._hint_text = hint_text def getHintText(self) -> Optional[str]: return self._hint_text def setTooltipText(self, tooltip_text: Optional[str]): self._tooltip_text = tooltip_text def getTooltipText(self) -> Optional[str]: return self._tooltip_text def setError(self, error: Optional[str]): self._error = error def getError(self) -> Optional[str]: return self._error def setBoundsInParent(self, left: int, top: int, right: int, bottom: int): self._bounds_in_parent = (left, top, right, bottom) def getBoundsInParent(self) -> Optional[Tuple[int, int, int, int]]: return self._bounds_in_parent def setBoundsInScreen(self, left: int, top: int, right: int, bottom: int): self._bounds_in_screen = (left, top, right, bottom) def getBoundsInScreen(self) -> Optional[Tuple[int, int, int, int]]: return self._bounds_in_screen def getWindowId(self) -> int: return -1 def recycle(self): self._children.clear() self._parent = None self._actions = 0 def toString(self) -> str: return (f"AccessibilityNodeInfo(className={self._class_name}, " f"text={self._text}, " f"contentDescription={self._content_description})") # ═══════════════════════════════════════════════════════════════ # AccessibilityNodeProvider # ═══════════════════════════════════════════════════════════════ class AccessibilityNodeProvider(Stub): """AccessibilityNodeProvider — 无障碍节点提供者""" HOST_VIEW_ID = -1 def __init__(self): super().__init__() def createAccessibilityNodeInfo(self, virtual_view_id: int) -> AccessibilityNodeInfo: raise AndroidOnlyError("AccessibilityNodeProvider.createAccessibilityNodeInfo") def addExtraDataToAccessibilityNodeInfo(self, virtual_view_id: int, info: AccessibilityNodeInfo, extra_data_key: str, arguments: Optional[Any]): pass def performAction(self, virtual_view_id: int, action: int, arguments: Optional[Any]) -> bool: return False def findFocus(self, focus: int) -> Optional[AccessibilityNodeInfo]: return None # ═══════════════════════════════════════════════════════════════ # AccessibilityServiceInfo # ═══════════════════════════════════════════════════════════════ class AccessibilityServiceInfo(Stub): """AccessibilityServiceInfo — 无障碍服务信息""" FEEDBACK_GENERIC = 16 FEEDBACK_SPOKEN = 1 FEEDBACK_HAPTIC = 2 FEEDBACK_AUDIBLE = 4 FEEDBACK_VISUAL = 8 FEEDBACK_BRAILLE = 32 FEEDBACK_ALL_MASK = -1 FLAG_REQUEST_TOUCH_EXPLORATION_MODE = 0x00000001 FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 0x00000002 FLAG_REPORT_VIEW_IDS = 0x00000010 FLAG_REQUEST_FILTER_KEY_EVENTS = 0x00000020 FLAG_RETRIEVE_INTERACTIVE_WINDOWS = 0x00000040 FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000080 FLAG_REQUEST_ACCESSIBILITY_BUTTON = 0x00000100 FLAG_REQUEST_SHORTCUT_WARNING_DIALOG = 0x00000200 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 CAPABILITY_CAN_CONTROL_MAGNIFICATION = 16 CAPABILITY_CAN_PERFORM_GESTURES = 32 def __init__(self): super().__init__() self.eventTypes: int = 0 self.feedbackType: int = 0 self.flags: int = 0 self.notificationTimeout: int = 0 self.filterTouchExploration: bool = False self.packageNames: Optional[List[str]] = None self.descriptionResId: int = 0 self.description: Optional[str] = None self.settingsActivityName: Optional[str] = None self.capabilities: int = 0 self.interactiveUiTimeout: int = 0 self.nonInteractiveUiTimeout: int = 0 def getCapabilityInfo(self, capability: int) -> Optional[str]: cap_map = { self.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT: "CAN_RETRIEVE_WINDOW_CONTENT", self.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION: "CAN_REQUEST_TOUCH_EXPLORATION", self.CAPABILITY_CAN_CONTROL_MAGNIFICATION: "CAN_CONTROL_MAGNIFICATION", self.CAPABILITY_CAN_PERFORM_GESTURES: "CAN_PERFORM_GESTURES", } return cap_map.get(capability) def toString(self) -> str: return (f"AccessibilityServiceInfo(eventTypes={hex(self.eventTypes)}, " f"feedbackType={self.feedbackType}, " f"flags={hex(self.flags)})") # ═══════════════════════════════════════════════════════════════ # AccessibilityWindowInfo # ═══════════════════════════════════════════════════════════════ class AccessibilityWindowInfo(Stub): """AccessibilityWindowInfo — 无障碍窗口信息""" TYPE_APPLICATION = 1 TYPE_INPUT_METHOD = 2 TYPE_SYSTEM = 3 TYPE_ACCESSIBILITY_OVERLAY = 4 TYPE_SPLIT_SCREEN_DIVIDER = 5 UNDEFINED = -1 ACTIVE = 0x00000001 FOCUSED = 0x00000002 ACCESSIBILITY_FOCUSED = 0x00000004 def __init__(self): super().__init__() self._type: int = TYPE_SYSTEM self._bounds_in_screen: Tuple[int, int, int, int] = (0, 0, 0, 0) self._root: Optional[AccessibilityNodeInfo] = None self._title: Optional[str] = None self._anchor: Optional[Any] = None self._parent: Optional["AccessibilityWindowInfo"] = None self._children: List["AccessibilityWindowInfo"] = [] self._id: int = -1 self._layer: int = 0 self._active: bool = False self._focused: bool = False self._accessibility_focused: bool = False self._picture_in_picture: bool = False @staticmethod def obtain() -> "AccessibilityWindowInfo": return AccessibilityWindowInfo() def recycle(self): self._root = None self._children.clear() self._parent = None def getType(self) -> int: return self._type def setType(self, window_type: int): self._type = window_type def getBoundsInScreen(self) -> Tuple[int, int, int, int]: return self._bounds_in_screen def setBoundsInScreen(self, left: int, top: int, right: int, bottom: int): self._bounds_in_screen = (left, top, right, bottom) def getRoot(self) -> Optional[AccessibilityNodeInfo]: return self._root def setRoot(self, root: Optional[AccessibilityNodeInfo]): self._root = root def getTitle(self) -> Optional[str]: return self._title def setTitle(self, title: Optional[str]): self._title = title def getAnchor(self) -> Optional[Any]: return self._anchor def setAnchor(self, anchor: Optional[Any]): self._anchor = anchor def getId(self) -> int: return self._id def setId(self, window_id: int): self._id = window_id def getLayer(self) -> int: return self._layer def setLayer(self, layer: int): self._layer = layer def getParent(self) -> Optional["AccessibilityWindowInfo"]: return self._parent def setParent(self, parent: Optional["AccessibilityWindowInfo"]): self._parent = parent def getChild(self, index: int) -> Optional["AccessibilityWindowInfo"]: if 0 <= index < len(self._children): return self._children[index] return None def getChildCount(self) -> int: return len(self._children) def addChild(self, child: "AccessibilityWindowInfo"): self._children.append(child) def isActive(self) -> bool: return self._active def setActive(self, active: bool): self._active = active def isFocused(self) -> bool: return self._focused def setFocused(self, focused: bool): self._focused = focused def isAccessibilityFocused(self) -> bool: return self._accessibility_focused def setAccessibilityFocused(self, focused: bool): self._accessibility_focused = focused def isInPictureInPictureMode(self) -> bool: return self._picture_in_picture def setPictureInPictureMode(self, pip: bool): self._picture_in_picture = pip def toString(self) -> str: type_names = {1: "APPLICATION", 2: "INPUT_METHOD", 3: "SYSTEM", 4: "ACCESSIBILITY_OVERLAY", 5: "SPLIT_SCREEN_DIVIDER"} type_name = type_names.get(self._type, "UNKNOWN") return f"AccessibilityWindowInfo(id={self._id}, type={type_name}, title={self._title})" # ═══════════════════════════════════════════════════════════════ # AccessibilityService # ═══════════════════════════════════════════════════════════════ class AccessibilityService(Stub): """AccessibilityService — 无障碍服务基类""" GLOBAL_ACTION_BACK = 1 GLOBAL_ACTION_HOME = 2 GLOBAL_ACTION_RECENTS = 3 GLOBAL_ACTION_NOTIFICATIONS = 4 GLOBAL_ACTION_QUICK_SETTINGS = 5 GLOBAL_ACTION_POWER_DIALOG = 6 GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN = 7 GLOBAL_ACTION_LOCK_SCREEN = 8 GLOBAL_ACTION_TAKE_SCREENSHOT = 9 GLOBAL_ACTION_KEYCODE_HEADSETHOOK = 10 GLOBAL_ACTION_ACCESSIBILITY_SHORTCUT = 11 GLOBAL_ACTION_ACCESSIBILITY_BUTTON = 12 GLOBAL_ACTION_ACCESSIBILITY_BUTTON_CHOOSER = 13 GLOBAL_ACTION_ACCESSIBILITY_SHORTCUT_CHOOSER = 14 GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS = 15 def __init__(self): super().__init__() self._service_info: Optional[AccessibilityServiceInfo] = None self._root_node: Optional[AccessibilityNodeInfo] = None self._windows: List[AccessibilityWindowInfo] = [] def onAccessibilityEvent(self, event: AccessibilityEvent): raise AndroidOnlyError("AccessibilityService.onAccessibilityEvent: not implemented") def onInterrupt(self): raise AndroidOnlyError("AccessibilityService.onInterrupt: not implemented") def onServiceConnected(self): pass def performGlobalAction(self, action: int) -> bool: action_names = { self.GLOBAL_ACTION_BACK: "BACK", self.GLOBAL_ACTION_HOME: "HOME", self.GLOBAL_ACTION_RECENTS: "RECENTS", self.GLOBAL_ACTION_NOTIFICATIONS: "NOTIFICATIONS", self.GLOBAL_ACTION_QUICK_SETTINGS: "QUICK_SETTINGS", } sys.stderr.write(f"[AccessibilityService] performGlobalAction: " f"{action_names.get(action, hex(action))}\n") return True def getRootInActiveWindow(self) -> Optional[AccessibilityNodeInfo]: return self._root_node def findFocus(self, focus_type: int) -> Optional[AccessibilityNodeInfo]: return self._root_node def getServiceInfo(self) -> Optional[AccessibilityServiceInfo]: return self._service_info def setServiceInfo(self, info: AccessibilityServiceInfo): self._service_info = info def getWindows(self) -> List[AccessibilityWindowInfo]: return list(self._windows) def getWindowsOnScreen(self) -> List[AccessibilityWindowInfo]: return [w for w in self._windows if w.isActive()] def disableSelf(self): sys.stderr.write("[AccessibilityService] disableSelf\n") def onKeyEvent(self, event: Any) -> bool: return False def onGesture(self, gesture_id: int) -> bool: return False def onMotionEvent(self, event: Any) -> bool: return False def onTouchExplorationStateChanged(self, enabled: bool): pass def onMagnificationChanged(self, region: Any, scale: float): pass def onSoftKeyboardShowModeChanged(self, show_mode: int): pass def dispatchKeyEventToInputMethod(self, event: Any) -> bool: return False # ═══════════════════════════════════════════════════════════════ # AccessibilityManager # ═══════════════════════════════════════════════════════════════ class AccessibilityManager(Stub): """AccessibilityManager — 无障碍管理器""" def __init__(self): super().__init__() self._enabled: bool = False self._touch_exploration_enabled: bool = False self._state_listeners: List[Callable] = [] self._touch_exploration_listeners: List[Callable] = [] self._accessibility_service_list: List[AccessibilityServiceInfo] = [] self._request_preparers: List[Any] = [] def isEnabled(self) -> bool: return self._enabled def setEnabled(self, enabled: bool): old = self._enabled self._enabled = enabled if old != enabled: for listener in list(self._state_listeners): try: listener.onAccessibilityStateChanged(enabled) except Exception: pass def isTouchExplorationEnabled(self) -> bool: return self._touch_exploration_enabled def addAccessibilityStateChangeListener(self, listener: Callable) -> bool: if listener not in self._state_listeners: self._state_listeners.append(listener) return True return False def removeAccessibilityStateChangeListener(self, listener: Callable) -> bool: if listener in self._state_listeners: self._state_listeners.remove(listener) return True return False def addTouchExplorationStateChangeListener(self, listener: Callable) -> bool: if listener not in self._touch_exploration_listeners: self._touch_exploration_listeners.append(listener) return True return False def removeTouchExplorationStateChangeListener(self, listener: Callable) -> bool: if listener in self._touch_exploration_listeners: self._touch_exploration_listeners.remove(listener) return True return False def getAccessibilityServiceList(self) -> List[AccessibilityServiceInfo]: return list(self._accessibility_service_list) def addAccessibilityRequestPreparer(self, preparer: Any): if preparer not in self._request_preparers: self._request_preparers.append(preparer) def removeAccessibilityRequestPreparer(self, preparer: Any): if preparer in self._request_preparers: self._request_preparers.remove(preparer) def sendAccessibilityEvent(self, event: AccessibilityEvent): sys.stderr.write(f"[AccessibilityManager] sendEvent: type={hex(event.getEventType())}\n") def interrupt(self): sys.stderr.write("[AccessibilityManager] interrupt\n") def getEnabledAccessibilityServiceList(self, feedback_type_flags: int) -> List[AccessibilityServiceInfo]: return [s for s in self._accessibility_service_list if s.feedbackType & feedback_type_flags] def getInstalledAccessibilityServiceList(self) -> List[AccessibilityServiceInfo]: return list(self._accessibility_service_list) def addAccessibilityStateChangeListener(self, listener: Callable) -> bool: if listener not in self._state_listeners: self._state_listeners.append(listener) return True return False def removeAccessibilityStateChangeListener(self, listener: Callable) -> bool: if listener in self._state_listeners: self._state_listeners.remove(listener) return True return False def toString(self) -> str: return f"AccessibilityManager(enabled={self._enabled})" # ═══════════════════════════════════════════════════════════════ # CaptioningManager # ═══════════════════════════════════════════════════════════════ class CaptioningManager(Stub): """CaptioningManager — 字幕管理器""" class CaptionStyle(Stub): """CaptionStyle — 字幕样式""" EDGE_TYPE_NONE = 0 EDGE_TYPE_OUTLINE = 1 EDGE_TYPE_DROP_SHADOW = 2 EDGE_TYPE_RAISED = 3 EDGE_TYPE_DEPRESSED = 4 def __init__(self): super().__init__() self._foreground_color: int = 0xFFFFFFFF self._background_color: int = 0xFF000000 self._edge_color: int = 0xFF000000 self._edge_type: int = EDGE_TYPE_NONE self._typeface: Optional[Any] = None self._font_scale: float = 1.0 self._window_color: int = 0xFF000000 @staticmethod def getCustomStyle(css: Any) -> "CaptioningManager.CaptionStyle": return CaptioningManager.CaptionStyle() 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 getTypeface(self) -> Optional[Any]: return self._typeface def hasForegroundColor(self) -> bool: return True def hasBackgroundColor(self) -> bool: return True def hasEdgeColor(self) -> bool: return True def hasEdgeType(self) -> bool: return True def hasWindowColor(self) -> bool: return True def getWindowColor(self) -> int: return self._window_color def __init__(self): super().__init__() self._enabled: bool = False self._locale: Optional[str] = None self._font_scale: float = 1.0 self._user_style: Optional["CaptioningManager.CaptionStyle"] = None def isCaptioningEnabled(self) -> bool: return self._enabled def getLocale(self) -> Optional[str]: return self._locale def getFontScale(self) -> float: return self._font_scale def getUserStyle(self) -> "CaptioningManager.CaptionStyle": if self._user_style is None: self._user_style = CaptioningManager.CaptionStyle() return self._user_style def addCaptioningChangeListener(self, listener: Any): pass def removeCaptioningChangeListener(self, listener: Any): pass # ═══════════════════════════════════════════════════════════════ # AccessibilityServiceGestureResult # ═══════════════════════════════════════════════════════════════ class AccessibilityServiceGestureResult(Stub): """AccessibilityServiceGestureResult — 手势结果""" def __init__(self, completed: bool): super().__init__() self._completed: bool = completed def isCompleted(self) -> bool: return self._completed # ═══════════════════════════════════════════════════════════════ # TouchExplorationStateChangeListener # ═══════════════════════════════════════════════════════════════ class TouchExplorationStateChangeListener(Stub): """TouchExplorationStateChangeListener — 触摸探索状态变更监听器""" def onTouchExplorationStateChanged(self, enabled: bool): pass # ═══════════════════════════════════════════════════════════════ # AccessibilityButtonController # ═══════════════════════════════════════════════════════════════ class AccessibilityButtonController(Stub): """AccessibilityButtonController — 无障碍按钮控制器""" class AccessibilityButtonCallback(Stub): """AccessibilityButtonCallback — 无障碍按钮回调""" def onAvailabilityChanged(self, available: bool): pass def onClicked(self, controller: "AccessibilityButtonController"): pass def __init__(self): super().__init__() self._callback: Optional["AccessibilityButtonController.AccessibilityButtonCallback"] = None def isAccessibilityButtonAvailable(self) -> bool: return False def registerAccessibilityButtonCallback(self, callback: "AccessibilityButtonController.AccessibilityButtonCallback"): self._callback = callback def unregisterAccessibilityButtonCallback(self, callback: "AccessibilityButtonController.AccessibilityButtonCallback"): if self._callback == callback: self._callback = None def performAccessibilityButtonAction(self) -> bool: if self._callback: self._callback.onClicked(self) return True return False # ═══════════════════════════════════════════════════════════════ # AccessibilityButtonControllerCallback # ═══════════════════════════════════════════════════════════════ class AccessibilityButtonControllerCallback(Stub): """AccessibilityButtonControllerCallback — 别名""" def onAvailabilityChanged(self, available: bool): pass def onClicked(self, controller: AccessibilityButtonController): pass # ═══════════════════════════════════════════════════════════════ # MagnificationConfig # ═══════════════════════════════════════════════════════════════ class MagnificationConfig(Stub): """MagnificationConfig — 放大配置""" SCALE = 0 CENTER_X = 1 CENTER_Y = 2 ALL = -1 def __init__(self): super().__init__() self._scale: float = 1.0 self._center_x: float = 0.0 self._center_y: float = 0.0 self._mode: int = 0 self._frame_left: float = 0.0 self._frame_top: float = 0.0 self._frame_right: float = 0.0 self._frame_bottom: float = 0.0 def getScale(self) -> float: return self._scale def setScale(self, scale: float): self._scale = scale def getCenterX(self) -> float: return self._center_x def setCenterX(self, center_x: float): self._center_x = center_x def getCenterY(self) -> float: return self._center_y def setCenterY(self, center_y: float): self._center_y = center_y def getMode(self) -> int: return self._mode def setMode(self, mode: int): self._mode = mode def toString(self) -> str: return (f"MagnificationConfig(scale={self._scale}, " f"center=({self._center_x}, {self._center_y}))") # ═══════════════════════════════════════════════════════════════ # MagnificationController # ═══════════════════════════════════════════════════════════════ class MagnificationController(Stub): """MagnificationController — 放大控制器""" class OnMagnificationChangedListener(Stub): def onMagnificationChanged(self, controller: "MagnificationController", region: Any, scale: float, center_x: float, center_y: float): pass def __init__(self): super().__init__() self._listeners: List["MagnificationController.OnMagnificationChangedListener"] = [] self._scale: float = 1.0 self._center_x: float = 0.0 self._center_y: float = 0.0 self._magnification_region: Any = None def getScale(self) -> float: return self._scale def getCenterX(self) -> float: return self._center_x def getCenterY(self) -> float: return self._center_y def getMagnificationRegion(self) -> Any: return self._magnification_region def setScale(self, scale: float, animate: bool = False) -> bool: self._scale = scale self._notifyListeners() return True def setCenter(self, center_x: float, center_y: float, animate: bool = False) -> bool: self._center_x = center_x self._center_y = center_y self._notifyListeners() return True def addListener(self, listener: "MagnificationController.OnMagnificationChangedListener"): if listener not in self._listeners: self._listeners.append(listener) def removeListener(self, listener: "MagnificationController.OnMagnificationChangedListener"): if listener in self._listeners: self._listeners.remove(listener) def _notifyListeners(self): for listener in list(self._listeners): try: listener.onMagnificationChanged(self, self._magnification_region, self._scale, self._center_x, self._center_y) except Exception: pass def isMagnifying(self) -> bool: return abs(self._scale - 1.0) > 0.01 def reset(self, animate: bool = False) -> bool: if self.isMagnifying(): self._scale = 1.0 self._center_x = 0.0 self._center_y = 0.0 self._notifyListeners() return True return False # ═══════════════════════════════════════════════════════════════ # __all__ # ═══════════════════════════════════════════════════════════════ __all__ = [ "AccessibilityEvent", "AccessibilityRecord", "AccessibilityNodeInfo", "AccessibilityNodeProvider", "AccessibilityService", "AccessibilityServiceInfo", "AccessibilityWindowInfo", "CaptioningManager", "AccessibilityManager", "AccessibilityServiceGestureResult", "TouchExplorationStateChangeListener", "AccessibilityButtonController", "AccessibilityButtonControllerCallback", "MagnificationConfig", "MagnificationController", ]