commit bfe27fdc08346cc6a135f2c5880068f888703414
Author: shuiyu1123
", _re.IGNORECASE)
+ _P_RE = _re.compile(r"
]*>", _re.IGNORECASE) + + @staticmethod + def fromHtml(html_text: str, flags: int = 0) -> str: + """将 HTML 转为纯文本""" + text = html_text + text = Html._BR_RE.sub("\n", text) + text = Html._P_RE.sub("\n\n", text) + text = Html._TAG_RE.sub("", text) + text = _html.unescape(text) + return text.strip() + + @staticmethod + def toHtml(text: str, option: int = 0) -> str: + """将纯文本转为 HTML""" + paragraphs = text.split("\n") + html_parts = [] + for p in paragraphs: + escaped = _html.escape(p) + html_parts.append(f"
{escaped}
") + return "".join(html_parts) + + @staticmethod + def fromHtml(html_text: str, image_getter: Any, tag_handler: Any) -> str: + return Html.fromHtml(html_text) + + @staticmethod + def toHtmlWithOptions(text: str, options: int) -> str: + return Html.toHtml(text) + + class ImageGetter: + def getDrawable(self, source: str): + return None + + class TagHandler: + def handleTag(self, opening: bool, tag: str, output: Editable, xml_reader: Any): + pass + + +# ═══════════════════════════════════════════════════════════════ +# CharacterStyle / 各 Span 实现 +# ═══════════════════════════════════════════════════════════════ + +class CharacterStyle(Stub): + """CharacterStyle — 字符样式基类""" + + def underLine(self): + pass + + def updateDrawState(self, tp: TextPaint): + pass + + def getUnderline(self) -> bool: + return False + + @staticmethod + def wrap(cs: "CharacterStyle") -> "CharacterStyle": + return cs + + +class StyleSpan(CharacterStyle): + """StyleSpan — 字体样式""" + + def __init__(self, style: int): + super().__init__() + self._style = style + + def getStyle(self) -> int: + return self._style + + def updateDrawState(self, tp: TextPaint): + tp.style = self._style + + +class TypefaceSpan(CharacterStyle): + """TypefaceSpan — 字体族""" + + def __init__(self, family: str): + super().__init__() + self._family = family + + def getFamily(self) -> str: + return self._family + + def updateDrawState(self, tp: TextPaint): + if self._family == "monospace": + tp.typeface = Typeface.MONOSPACE + elif self._family == "serif": + tp.typeface = Typeface.SERIF + else: + tp.typeface = Typeface.DEFAULT + + +class RelativeSizeSpan(CharacterStyle): + """RelativeSizeSpan — 相对大小""" + + def __init__(self, size: float): + super().__init__() + self._size = size + + def getSizeChange(self) -> float: + return self._size + + def updateDrawState(self, tp: TextPaint): + tp.textSize *= self._size + + +class ForegroundColorSpan(CharacterStyle): + """ForegroundColorSpan — 前景色""" + + def __init__(self, color: int): + super().__init__() + self._color = color + + def getForegroundColor(self) -> int: + return self._color + + def updateDrawState(self, tp: TextPaint): + tp.color = self._color + + +class BackgroundColorSpan(CharacterStyle): + """BackgroundColorSpan — 背景色""" + + def __init__(self, color: int): + super().__init__() + self._color = color + + def getBackgroundColor(self) -> int: + return self._color + + def updateDrawState(self, tp: TextPaint): + tp.bgColor = self._color + tp.bgColorSet = True + + +class UnderlineSpan(CharacterStyle): + """UnderlineSpan — 下划线""" + + def updateDrawState(self, tp: TextPaint): + tp.setUnderlineText(True) + + +class StrikethroughSpan(CharacterStyle): + """StrikethroughSpan — 删除线""" + + def updateDrawState(self, tp: TextPaint): + pass # desktop 无直接等价 + + +class SubscriptSpan(CharacterStyle): + """SubscriptSpan — 下标""" + + def updateDrawState(self, tp: TextPaint): + tp.baselineShift = -tp.textSize / 4 + + +class SuperscriptSpan(CharacterStyle): + """SuperscriptSpan — 上标""" + + def updateDrawState(self, tp: TextPaint): + tp.baselineShift = tp.textSize / 2 + + +class ImageSpan(CharacterStyle): + """ImageSpan — 图片""" + + ALIGN_BOTTOM = 0 + ALIGN_BASELINE = 1 + ALIGN_CENTER = 2 + ALIGN_TOP = 3 + + def __init__(self, drawable: Any, source: str = "", align: int = ALIGN_BOTTOM): + super().__init__() + self._drawable = drawable + self._source = source + self._align = align + + def getDrawable(self): + return self._drawable + + def getSource(self) -> str: + return self._source + + def getAlignment(self) -> int: + return self._align + + +class ClickableSpan(CharacterStyle): + """ClickableSpan — 可点击文本""" + + def __init__(self): + super().__init__() + + def onClick(self, view: Any): + pass + + def updateDrawState(self, tp: TextPaint): + tp.color = tp.linkColor + tp.setUnderlineText(True) + + +class URLSpan(ClickableSpan): + """URLSpan — URL 链接""" + + def __init__(self, url: str): + super().__init__() + self._url = url + + def getURL(self) -> str: + return self._url + + def onClick(self, view: Any): + sys.stderr.write(f"[URLSpan] onClick: {self._url}\n") + + def updateDrawState(self, tp: TextPaint): + super().updateDrawState(tp) + + +# ═══════════════════════════════════════════════════════════════ +# NoCopySpan / ParagraphStyle 接口 +# ═══════════════════════════════════════════════════════════════ + +class NoCopySpan(Stub): + """NoCopySpan — 标记不在拷贝时复制的 span""" + pass + +class NoCopySpanConcise(NoCopySpan): + """NoCopySpan.Concrete — 具体实现""" + pass + + +class ParagraphStyle(Stub): + """ParagraphStyle — 段落样式接口""" + pass + + +# ═══════════════════════════════════════════════════════════════ +# Ellipsize / TruncateAt 枚举 +# ═══════════════════════════════════════════════════════════════ + +class Ellipsize(Stub): + """Ellipsize 常量""" + END = "END" + MIDDLE = "MIDDLE" + MARQUEE = "MARQUEE" + START = "START" + NONE = "NONE" + + +class TruncateAt(Stub): + """TruncateAt 常量""" + END = "END" + MIDDLE = "MIDDLE" + MARQUEE = "MARQUEE" + START = "START" + + +# ═══════════════════════════════════════════════════════════════ +# Align +# ═══════════════════════════════════════════════════════════════ + +class Align(Stub): + """Layout.Alignment 枚举""" + ALIGN_NORMAL = 0 + ALIGN_OPPOSITE = 1 + ALIGN_CENTER = 2 + + +# ═══════════════════════════════════════════════════════════════ +# TextDirectionHeuristic / TextDirectionHeuristics +# ═══════════════════════════════════════════════════════════════ + +class TextDirectionHeuristic(Stub): + """TextDirectionHeuristic — 文本方向启发式""" + pass + + +class TextDirectionHeuristics(Stub): + """TextDirectionHeuristics — 文本方向启发式常量""" + + FIRSTSTRONG_LTR = TextDirectionHeuristic() + FIRSTSTRONG_RTL = TextDirectionHeuristic() + ANYRTL_LTR = TextDirectionHeuristic() + LTR = TextDirectionHeuristic() + RTL = TextDirectionHeuristic() + LOCALE = TextDirectionHeuristic() + + +# ═══════════════════════════════════════════════════════════════ +# LoginFilter +# ═══════════════════════════════════════════════════════════════ + +class LoginFilter(InputFilter): + """LoginFilter — 登录名输入过滤器""" + + def __init__(self, append_ok: bool = True): + super().__init__() + self._append_ok = append_ok + + def isAllowed(self, c: str) -> bool: + return c.isalnum() or c in "@._-" + + def filter(self, source: str, start: int, end: int, dest: Spanned, + dstart: int, dend: int) -> Optional[str]: + filtered = [] + for c in source[start:end]: + if self.isAllowed(c): + filtered.append(c) + elif self._append_ok: + pass + result = "".join(filtered) + return result if result != source else None + + class UsernameFilterGMail(InputFilter): + def filter(self, source, start, end, dest, dstart, dend): + result = [] + for c in source[start:end]: + if c.isalnum() or c in "@._-": + result.append(c) + return "".join(result) or None + + class PasswordFilterGMail(InputFilter): + def filter(self, source, start, end, dest, dstart, dend): + return None + + +# ═══════════════════════════════════════════════════════════════ +# Method / MovementMethod +# ═══════════════════════════════════════════════════════════════ + +class Method(Stub): + """Method — 输入法接口""" + pass + + +class MovementMethod(Stub): + """MovementMethod — 移动方式接口""" + pass + + +# ═══════════════════════════════════════════════════════════════ +# Layout / StaticLayout / DynamicLayout / BoringLayout +# ═══════════════════════════════════════════════════════════════ + +class Layout(Stub): + """Layout — 布局基类""" + + Alignment = Align + + BREAK_STRATEGY_SIMPLE = 0 + BREAK_STRATEGY_HIGH_QUALITY = 1 + BREAK_STRATEGY_BALANCED = 2 + + HYPHENATION_FREQUENCY_NONE = 0 + HYPHENATION_FREQUENCY_NORMAL = 1 + HYPHENATION_FREQUENCY_FULL = 2 + + JUSTIFICATION_MODE_NONE = 0 + JUSTIFICATION_MODE_INTER_WORD = 1 + + def __init__(self, text: str, paint: TextPaint, width: int, alignment: int, + spacing_mult: float, spacing_add: float): + super().__init__() + self._text = text + self._paint = paint + self._width = width + self._alignment = alignment + self._spacing_mult = spacing_mult + self._spacing_add = spacing_add + self._line_count = max(1, len(text) // max(1, width // 10) + 1) + + def getLineCount(self) -> int: + return self._line_count + + def getLineBounds(self, line: int, bounds: Optional[Any]) -> int: + line_height = int(self._paint.textSize * self._spacing_mult + self._spacing_add) + return line * line_height + + def getPrimaryHorizontal(self, offset: int) -> float: + line = 0 + return (offset % max(1, self._width)) * self._paint.measureText("x") + + def getSecondaryHorizontal(self, offset: int) -> float: + return 0.0 + + def getLineTop(self, line: int) -> int: + line_height = int(self._paint.textSize * self._spacing_mult + self._spacing_add) + return line * line_height + + def getLineBottom(self, line: int) -> int: + line_height = int(self._paint.textSize * self._spacing_mult + self._spacing_add) + return (line + 1) * line_height + + def getLineStart(self, line: int) -> int: + chars_per_line = max(1, self._width // max(1, int(self._paint.measureText("x")))) + return line * chars_per_line + + def getLineEnd(self, line: int) -> int: + chars_per_line = max(1, self._width // max(1, int(self._paint.measureText("x")))) + return min((line + 1) * chars_per_line, len(self._text)) + + def getText(self) -> str: + return self._text + + def getPaint(self) -> TextPaint: + return self._paint + + def getWidth(self) -> int: + return self._width + + def getAlignment(self) -> int: + return self._alignment + + def getSpacingMultiplier(self) -> float: + return self._spacing_mult + + def getSpacingAdd(self) -> float: + return self._spacing_add + + def isRtlCharAt(self, offset: int) -> bool: + return False + + def getParagraphDirection(self, line: int) -> int: + return 1 + + def draw(self, canvas: Any): + pass + + +class StaticLayout(Layout): + """StaticLayout — 静态布局""" + + def __init__(self, source: str, paint: TextPaint, width: int, alignment: int, + spacing_mult: float, spacing_add: float, include_pad: bool): + super().__init__(source, paint, width, alignment, spacing_mult, spacing_add) + self._include_pad = include_pad + + +class DynamicLayout(Layout): + """DynamicLayout — 动态布局""" + + def __init__(self, base: Any, paint: TextPaint, width: int, alignment: int, + spacing_mult: float, spacing_add: float, include_pad: bool, + ellipsize: Any = None, ellipsize_width: int = 0): + super().__init__(str(base), paint, width, alignment, spacing_mult, spacing_add) + self._include_pad = include_pad + + +class BoringLayout(Layout): + """BoringLayout — 单行布局""" + + def __init__(self, source: str, paint: TextPaint, width: int, alignment: int, + spacing_mult: float, spacing_add: float, include_pad: bool, + ellipsize: Any = None, ellipsize_width: int = 0): + super().__init__(source, paint, width, alignment, spacing_mult, spacing_add) + self._include_pad = include_pad + self._line_count = 1 if source else 0 + + class Metrics(Stub): + """Metrics for BoringLayout""" + width = 0 + height = 0 + + @staticmethod + def isBoring(text: str, paint: TextPaint) -> Optional["BoringLayout.Metrics"]: + m = BoringLayout.Metrics() + m.width = int(paint.measureText(text)) + m.height = int(paint.textSize) + return m if text else None + + @staticmethod + def make(source: str, paint: TextPaint, width: int, alignment: int, + spacing_mult: float, spacing_add: float, metrics: "BoringLayout.Metrics", + include_pad: bool) -> "BoringLayout": + return BoringLayout(source, paint, width, alignment, spacing_mult, spacing_add, include_pad) + + +class ClipRectangle(Stub): + """ClipRectangle — 裁剪矩形""" + def __init__(self, left: int = 0, top: int = 0, right: int = 0, bottom: int = 0): + super().__init__() + self.left = left + self.top = top + self.right = right + self.bottom = bottom + + +# ═══════════════════════════════════════════════════════════════ +# Spannable (接口别名) +# ═══════════════════════════════════════════════════════════════ + +class Spannable(Spanned): + """Spannable — 可标记接口""" + pass + + +# ═══════════════════════════════════════════════════════════════ +# __all__ +# ═══════════════════════════════════════════════════════════════ + +__all__ = [ + "TextUtils", + "SpannableString", + "SpannableStringBuilder", + "Spannable", + "Editable", + "Spanned", + "TextWatcher", + "InputFilter", + "Html", + "Layout", + "StaticLayout", + "DynamicLayout", + "BoringLayout", + "ClipRectangle", + "TextPaint", + "TextDirectionHeuristic", + "TextDirectionHeuristics", + "Align", + "Ellipsize", + "TruncateAt", + "LoginFilter", + "Method", + "MovementMethod", + "NoCopySpan", + "ParagraphStyle", + "CharacterStyle", + "StyleSpan", + "TypefaceSpan", + "RelativeSizeSpan", + "ForegroundColorSpan", + "BackgroundColorSpan", + "UnderlineSpan", + "StrikethroughSpan", + "SubscriptSpan", + "SuperscriptSpan", + "ImageSpan", + "ClickableSpan", + "URLSpan", + "Typeface", +] \ No newline at end of file diff --git a/android_pyapi/android/util.py b/android_pyapi/android/util.py new file mode 100644 index 0000000..37b5e32 --- /dev/null +++ b/android_pyapi/android/util.py @@ -0,0 +1,1055 @@ +""" +Android Util 组件 — Log, SparseArray, LruCache, DisplayMetrics 等 +方法签名完全对齐 Android SDK +""" + +import math +import re as _re +import sys +from datetime import datetime +from typing import Any, Callable, Dict, Generic, List, Optional, Set, Tuple, TypeVar, Union + +from .._core import ( + Stub, AndroidOnlyError, android_context, + resolve_android_path, to_android_path, + _sdcard_root, _app_data_dir, +) + + +# ═══════════════════════════════════════════════════════════════ +# Log +# ═══════════════════════════════════════════════════════════════ + +class Log(Stub): + """Log — 日志输出""" + + ASSERT = 7 + DEBUG = 3 + ERROR = 6 + INFO = 4 + VERBOSE = 2 + WARN = 5 + + _min_log_level = VERBOSE + + @staticmethod + def v(tag: str, msg: str) -> int: + if Log._min_log_level <= Log.VERBOSE: + sys.stderr.write(f"V/{tag}: {msg}\n") + return 0 + + @staticmethod + def d(tag: str, msg: str) -> int: + if Log._min_log_level <= Log.DEBUG: + sys.stderr.write(f"D/{tag}: {msg}\n") + return 0 + + @staticmethod + def i(tag: str, msg: str) -> int: + if Log._min_log_level <= Log.INFO: + sys.stderr.write(f"I/{tag}: {msg}\n") + return 0 + + @staticmethod + def w(tag: str, msg: str) -> int: + if Log._min_log_level <= Log.WARN: + sys.stderr.write(f"W/{tag}: {msg}\n") + return 0 + + @staticmethod + def e(tag: str, msg: str) -> int: + if Log._min_log_level <= Log.ERROR: + sys.stderr.write(f"E/{tag}: {msg}\n") + return 0 + + @staticmethod + def wtf(tag: str, msg: str) -> int: + sys.stderr.write(f"WTF/{tag}: {msg}\n") + return 0 + + @staticmethod + def println(log_level: int, tag: str, msg: str) -> int: + level_map = { + Log.VERBOSE: "V", + Log.DEBUG: "D", + Log.INFO: "I", + Log.WARN: "W", + Log.ERROR: "E", + Log.ASSERT: "A", + } + prefix = level_map.get(log_level, "?") + sys.stderr.write(f"{prefix}/{tag}: {msg}\n") + return 0 + + @staticmethod + def isLoggable(tag: str, level: int) -> bool: + return level >= Log._min_log_level + + @staticmethod + def getStackTraceString(tr: Exception) -> str: + import traceback + return "".join(traceback.format_exception(type(tr), tr, tr.__traceback__)) + + @staticmethod + def setMinLogLevel(level: int): + Log._min_log_level = level + + @staticmethod + def wtf(tag: str, tr: Exception) -> int: + sys.stderr.write(f"WTF/{tag}: {Log.getStackTraceString(tr)}\n") + return 0 + + +# ═══════════════════════════════════════════════════════════════ +# SparseArray +# ═══════════════════════════════════════════════════════════════ + +T = TypeVar("T") + +class SparseArray(Generic[T], Stub): + """SparseArray — 稀疏数组(int 键映射)""" + + def __init__(self, initial_capacity: int = 10): + super().__init__() + self._keys: List[int] = [] + self._values: List[Optional[T]] = [] + self._size = 0 + + def put(self, key: int, value: T): + """如果 key 已存在则替换""" + try: + idx = self._keys.index(key) + self._values[idx] = value + except ValueError: + self._keys.append(key) + self._values.append(value) + self._size += 1 + + def get(self, key: int, default: Optional[T] = None) -> Optional[T]: + try: + idx = self._keys.index(key) + return self._values[idx] + except ValueError: + return default + + def delete(self, key: int): + try: + idx = self._keys.index(key) + self._keys.pop(idx) + self._values.pop(idx) + self._size -= 1 + except ValueError: + pass + + def remove(self, key: int): + self.delete(key) + + def removeAt(self, index: int): + if 0 <= index < self._size: + self._keys.pop(index) + self._values.pop(index) + self._size -= 1 + + def size(self) -> int: + return self._size + + def keyAt(self, index: int) -> int: + if 0 <= index < self._size: + return self._keys[index] + return 0 + + def valueAt(self, index: int) -> Optional[T]: + if 0 <= index < self._size: + return self._values[index] + return None + + def setValueAt(self, index: int, value: T): + if 0 <= index < self._size: + self._values[index] = value + + def indexOfKey(self, key: int) -> int: + try: + return self._keys.index(key) + except ValueError: + return -1 + + def indexOfValue(self, value: T) -> int: + try: + return self._values.index(value) + except ValueError: + return -1 + + def clear(self): + self._keys.clear() + self._values.clear() + self._size = 0 + + def append(self, key: int, value: T): + self.put(key, value) + + def containsKey(self, key: int) -> bool: + return key in self._keys + + def putAll(self, other: "SparseArray[T]"): + for i in range(other.size()): + self.put(other.keyAt(i), other.valueAt(i)) + + def clone(self) -> "SparseArray[T]": + sa = SparseArray[T]() + sa._keys = list(self._keys) + sa._values = list(self._values) + sa._size = self._size + return sa + + def toString(self) -> str: + pairs = ", ".join( + f"{k}={v}" for k, v in zip(self._keys, self._values) + ) + return f"SparseArray{{{pairs}}}" + + def __getitem__(self, key: int) -> Optional[T]: + return self.get(key) + + def __setitem__(self, key: int, value: T): + self.put(key, value) + + def __delitem__(self, key: int): + self.delete(key) + + def __len__(self) -> int: + return self._size + + def __contains__(self, key: int) -> bool: + return key in self._keys + + +class SparseIntArray(Stub): + """SparseIntArray — int 到 int 的映射""" + + def __init__(self, initial_capacity: int = 10): + super().__init__() + self._keys: List[int] = [] + self._values: List[int] = [] + + def put(self, key: int, value: int): + try: + idx = self._keys.index(key) + self._values[idx] = value + except ValueError: + self._keys.append(key) + self._values.append(value) + + def get(self, key: int, default: int = 0) -> int: + try: + idx = self._keys.index(key) + return self._values[idx] + except ValueError: + return default + + def delete(self, key: int): + try: + idx = self._keys.index(key) + self._keys.pop(idx) + self._values.pop(idx) + except ValueError: + pass + + def removeAt(self, index: int): + if 0 <= index < len(self._keys): + self._keys.pop(index) + self._values.pop(index) + + def size(self) -> int: + return len(self._keys) + + def keyAt(self, index: int) -> int: + if 0 <= index < len(self._keys): + return self._keys[index] + return 0 + + def valueAt(self, index: int) -> int: + if 0 <= index < len(self._values): + return self._values[index] + return 0 + + def indexOfKey(self, key: int) -> int: + try: + return self._keys.index(key) + except ValueError: + return -1 + + def indexOfValue(self, value: int) -> int: + try: + return self._values.index(value) + except ValueError: + return -1 + + def clear(self): + self._keys.clear() + self._values.clear() + + def append(self, key: int, value: int): + self.put(key, value) + + def toString(self) -> str: + pairs = ", ".join(f"{k}={v}" for k, v in zip(self._keys, self._values)) + return f"SparseIntArray{{{pairs}}}" + + +class SparseBooleanArray(Stub): + """SparseBooleanArray — int 到 bool 的映射""" + + def __init__(self, initial_capacity: int = 10): + super().__init__() + self._keys: List[int] = [] + self._values: List[bool] = [] + + def put(self, key: int, value: bool): + try: + idx = self._keys.index(key) + self._values[idx] = value + except ValueError: + self._keys.append(key) + self._values.append(value) + + def get(self, key: int, default: bool = False) -> bool: + try: + idx = self._keys.index(key) + return self._values[idx] + except ValueError: + return default + + def delete(self, key: int): + try: + idx = self._keys.index(key) + self._keys.pop(idx) + self._values.pop(idx) + except ValueError: + pass + + def size(self) -> int: + return len(self._keys) + + def keyAt(self, index: int) -> int: + if 0 <= index < len(self._keys): + return self._keys[index] + return 0 + + def valueAt(self, index: int) -> bool: + if 0 <= index < len(self._values): + return self._values[index] + return False + + def indexOfKey(self, key: int) -> int: + try: + return self._keys.index(key) + except ValueError: + return -1 + + def indexOfValue(self, value: bool) -> int: + try: + return self._values.index(value) + except ValueError: + return -1 + + def clear(self): + self._keys.clear() + self._values.clear() + + def append(self, key: int, value: bool): + self.put(key, value) + + def toString(self) -> str: + pairs = ", ".join(f"{k}={v}" for k, v in zip(self._keys, self._values)) + return f"SparseBooleanArray{{{pairs}}}" + + +# ═══════════════════════════════════════════════════════════════ +# LruCache +# ═══════════════════════════════════════════════════════════════ + +class LruCache(Stub): + """LruCache — LRU 缓存""" + + def __init__(self, max_size: int): + super().__init__() + self._max_size = max_size + self._size = 0 + self._map: Dict[Any, Any] = {} + + def get(self, key: Any) -> Optional[Any]: + return self._map.get(key) + + def put(self, key: Any, value: Any) -> Optional[Any]: + old = self._map.get(key) + self._map[key] = value + self._size += self._safe_size_of(key, value) + if old is not None: + self._size -= self._safe_size_of(key, old) + self._trim_to_size(self._max_size) + return old + + def remove(self, key: Any) -> Optional[Any]: + value = self._map.pop(key, None) + if value is not None: + self._size -= self._safe_size_of(key, value) + return value + + def size(self) -> int: + return self._size + + def maxSize(self) -> int: + return self._max_size + + def trimToSize(self, max_size: int): + self._trim_to_size(max_size) + + def _trim_to_size(self, max_size: int): + while self._size > max_size and self._map: + # Remove oldest (first key) + oldest_key = next(iter(self._map)) + self.remove(oldest_key) + + def snapshot(self) -> Dict[Any, Any]: + return dict(self._map) + + def evictAll(self): + self._map.clear() + self._size = 0 + + def _safe_size_of(self, key: Any, value: Any) -> int: + try: + return self.sizeOf(key, value) + except Exception: + return 1 + + def sizeOf(self, key: Any, value: Any) -> int: + return 1 + + def entryRemoved(self, evicted: bool, key: Any, old_value: Any, new_value: Optional[Any]): + pass + + def create(self, key: Any) -> Optional[Any]: + return None + + def getCount(self) -> int: + return len(self._map) + + def putCount(self) -> int: + return len(self._map) + + def evictionCount(self) -> int: + return 0 + + def hitCount(self) -> int: + return 0 + + def missCount(self) -> int: + return 0 + + def toString(self) -> str: + return f"LruCache(maxSize={self._max_size}, size={self._size})" + + +# ═══════════════════════════════════════════════════════════════ +# DisplayMetrics +# ═══════════════════════════════════════════════════════════════ + +class DisplayMetrics(Stub): + """DisplayMetrics — 屏幕显示度量""" + + DENSITY_LOW = 120 + DENSITY_MEDIUM = 160 + DENSITY_TV = 213 + DENSITY_HIGH = 240 + DENSITY_XHIGH = 320 + DENSITY_XXHIGH = 480 + DENSITY_XXXHIGH = 640 + DENSITY_DEFAULT = DENSITY_MEDIUM + DENSITY_DEVICE_STABLE = DENSITY_XXXHIGH + + def __init__(self): + super().__init__() + self.widthPixels = 1080 + self.heightPixels = 1920 + self.density = 2.75 + self.densityDpi = 440 + self.scaledDensity = 2.75 + self.xdpi = 440.0 + self.ydpi = 440.0 + self.noncompatWidthPixels = 1080 + self.noncompatHeightPixels = 1920 + self.noncompatDensity = 2.75 + self.noncompatDensityDpi = 440 + self.noncompatScaledDensity = 2.75 + self.noncompatXdpi = 440.0 + self.noncompatYdpi = 440.0 + + def setTo(self, other: "DisplayMetrics"): + self.widthPixels = other.widthPixels + self.heightPixels = other.heightPixels + self.density = other.density + self.densityDpi = other.densityDpi + self.scaledDensity = other.scaledDensity + self.xdpi = other.xdpi + self.ydpi = other.ydpi + + def setToDefaults(self): + self.__init__() + + def toString(self) -> str: + return (f"DisplayMetrics({self.widthPixels}x{self.heightPixels}, " + f"density={self.density}, dpi={self.densityDpi})") + + +# ═══════════════════════════════════════════════════════════════ +# TypedValue +# ═══════════════════════════════════════════════════════════════ + +class TypedValue(Stub): + """TypedValue — 类型化值的容器""" + + COMPLEX_UNIT_PX = 0 + COMPLEX_UNIT_DIP = 1 + COMPLEX_UNIT_SP = 2 + COMPLEX_UNIT_PT = 3 + COMPLEX_UNIT_IN = 4 + COMPLEX_UNIT_MM = 5 + COMPLEX_UNIT_FRACTION = 6 + COMPLEX_UNIT_FRACTION_PARENT = 7 + + TYPE_NULL = 0x00 + TYPE_REFERENCE = 0x01 + TYPE_ATTRIBUTE = 0x02 + TYPE_STRING = 0x03 + TYPE_FLOAT = 0x04 + TYPE_DIMENSION = 0x05 + TYPE_FRACTION = 0x06 + TYPE_FIRST_INT = 0x10 + TYPE_INT_DEC = 0x10 + TYPE_INT_HEX = 0x11 + TYPE_INT_BOOLEAN = 0x12 + TYPE_INT_COLOR_ARGB8 = 0x1C + TYPE_INT_COLOR_RGB8 = 0x1D + TYPE_INT_COLOR_ARGB4 = 0x1E + TYPE_INT_COLOR_RGB4 = 0x1F + TYPE_LAST_COLOR_INT = 0x1F + TYPE_LAST_INT = 0x1F + + def __init__(self): + super().__init__() + self.type = self.TYPE_NULL + self.data = 0 + self.assetCookie = 0 + self.resourceId = 0 + self.changingConfigurations = 0 + self.string = None + self.density = 0 + + @staticmethod + def applyDimension(unit: int, value: float, metrics: DisplayMetrics) -> float: + if unit == TypedValue.COMPLEX_UNIT_PX: + return value + if unit == TypedValue.COMPLEX_UNIT_DIP: + return value * metrics.density + if unit == TypedValue.COMPLEX_UNIT_SP: + return value * metrics.scaledDensity + if unit == TypedValue.COMPLEX_UNIT_PT: + return value * metrics.xdpi * (1.0 / 72) + if unit == TypedValue.COMPLEX_UNIT_IN: + return value * metrics.xdpi + if unit == TypedValue.COMPLEX_UNIT_MM: + return value * metrics.xdpi * (1.0 / 25.4) + return value + + @staticmethod + def complexToDimension(data: int, metrics: DisplayMetrics) -> float: + unit = data & 0xF + value = (data >> 4) & 0xFFFFFF + # Sign-extend + if value & 0x800000: + value = value - 0x1000000 + return TypedValue.applyDimension(unit, float(value), metrics) + + @staticmethod + def complexToFloat(data: int) -> float: + value = (data & 0xFFFFFF00) >> 8 + exp = (data & 0xFF) - 150 + return float(value) * (10.0 ** exp) + + @staticmethod + def complexToDimensionPixelOffset(data: int, metrics: DisplayMetrics) -> int: + return int(TypedValue.complexToDimension(data, metrics)) + + @staticmethod + def complexToDimensionPixelSize(data: int, metrics: DisplayMetrics) -> int: + val = TypedValue.complexToDimension(data, metrics) + if val >= 0: + return int(val + 0.5) + return int(val - 0.5) + + def getFloat(self) -> float: + return float(self.data) + + def toString(self) -> str: + return f"TypedValue(type={self.type}, data={self.data})" + + +# ═══════════════════════════════════════════════════════════════ +# Pair +# ═══════════════════════════════════════════════════════════════ + +class Pair(Stub): + """Pair — 有序对""" + + def __init__(self, first: Any, second: Any): + super().__init__() + self.first = first + self.second = second + + @staticmethod + def create(a: Any, b: Any) -> "Pair": + return Pair(a, b) + + def hashCode(self) -> int: + return hash((self.first, self.second)) + + def equals(self, other: Any) -> bool: + if not isinstance(other, Pair): + return False + return self.first == other.first and self.second == other.second + + def toString(self) -> str: + return f"Pair({self.first}, {self.second})" + + def __eq__(self, other): + return self.equals(other) + + def __hash__(self): + return self.hashCode() + + def __repr__(self): + return self.toString() + + def __iter__(self): + return iter((self.first, self.second)) + + +# ═══════════════════════════════════════════════════════════════ +# Size / SizeF +# ═══════════════════════════════════════════════════════════════ + +class Size(Stub): + """Size — 整数尺寸""" + + def __init__(self, width: int, height: int): + super().__init__() + self._width = width + self._height = height + + def getWidth(self) -> int: + return self._width + + def getHeight(self) -> int: + return self._height + + def equals(self, other: Any) -> bool: + if not isinstance(other, Size): + return False + return self._width == other._width and self._height == other._height + + def hashCode(self) -> int: + return (self._height << 16) ^ self._width + + def toString(self) -> str: + return f"{self._width}x{self._height}" + + @staticmethod + def parseSize(string: str) -> "Size": + parts = string.split("x") + if len(parts) == 2: + return Size(int(parts[0]), int(parts[1])) + raise ValueError(f"Invalid size: {string}") + + def __eq__(self, other): + return self.equals(other) + + def __hash__(self): + return self.hashCode() + + def __repr__(self): + return self.toString() + + +class SizeF(Stub): + """SizeF — 浮点数尺寸""" + + def __init__(self, width: float, height: float): + super().__init__() + self._width = width + self._height = height + + def getWidth(self) -> float: + return self._width + + def getHeight(self) -> float: + return self._height + + def equals(self, other: Any) -> bool: + if not isinstance(other, SizeF): + return False + return abs(self._width - other._width) < 0.001 and abs(self._height - other._height) < 0.001 + + def hashCode(self) -> int: + return hash((self._width, self._height)) + + def toString(self) -> str: + return f"{self._width:.1f}x{self._height:.1f}" + + @staticmethod + def parseSizeF(string: str) -> "SizeF": + parts = string.split("x") + if len(parts) == 2: + return SizeF(float(parts[0]), float(parts[1])) + raise ValueError(f"Invalid size: {string}") + + def __eq__(self, other): + return self.equals(other) + + def __hash__(self): + return self.hashCode() + + def __repr__(self): + return self.toString() + + +# ═══════════════════════════════════════════════════════════════ +# StateSet +# ═══════════════════════════════════════════════════════════════ + +class StateSet(Stub): + """StateSet — 状态集合""" + + VIEW_STATE_WINDOW_FOCUSED = 1 + VIEW_STATE_SELECTED = 1 << 1 + VIEW_STATE_FOCUSED = 1 << 2 + VIEW_STATE_ENABLED = 1 << 3 + VIEW_STATE_PRESSED = 1 << 4 + VIEW_STATE_ACTIVATED = 1 << 5 + VIEW_STATE_ACCELERATED = 1 << 6 + VIEW_STATE_HOVERED = 1 << 7 + VIEW_STATE_DRAG_CAN_ACCEPT = 1 << 8 + VIEW_STATE_DRAG_HOVERED = 1 << 9 + + WILD_CARD = 0x00000000 + + @staticmethod + def isWildCard(state_spec_or_mask: Any) -> bool: + if isinstance(state_spec_or_mask, list): + return len(state_spec_or_mask) == 0 + return state_spec_or_mask == StateSet.WILD_CARD + + @staticmethod + def trimStateSet(states: List[int], new_size: int) -> List[int]: + return states[:new_size] + + @staticmethod + def dump(states: List[int]) -> str: + if not states: + return "[]" + return "[" + ", ".join(str(s) for s in states) + "]" + + +# ═══════════════════════════════════════════════════════════════ +# MathUtils +# ═══════════════════════════════════════════════════════════════ + +class MathUtils(Stub): + """MathUtils — 数学工具""" + + DEG_TO_RAD = math.pi / 180.0 + RAD_TO_DEG = 180.0 / math.pi + + @staticmethod + def abs(v: float) -> float: + return abs(v) + + @staticmethod + def constrain(amount: float, low: float, high: float) -> float: + return max(low, min(high, amount)) + + @staticmethod + def constrainInt(amount: int, low: int, high: int) -> int: + return max(low, min(high, amount)) + + @staticmethod + def lerp(start: float, stop: float, amount: float) -> float: + return start + (stop - start) * amount + + @staticmethod + def lerpInv(a: float, b: float, value: float) -> float: + if a != b: + return (value - a) / (b - a) + return 0.0 + + @staticmethod + def norm(start: float, stop: float, value: float) -> float: + return MathUtils.lerpInv(start, stop, value) + + @staticmethod + def map(min_start: float, min_stop: float, max_start: float, max_stop: float, value: float) -> float: + return max_start + (value - min_start) * (max_stop - max_start) / (min_stop - min_start) + + @staticmethod + def dist(x1: float, y1: float, x2: float, y2: float) -> float: + return math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) + + @staticmethod + def max(var1: float, var2: float) -> float: + return max(var1, var2) + + @staticmethod + def min(var1: float, var2: float) -> float: + return min(var1, var2) + + @staticmethod + def degrees(radians: float) -> float: + return math.degrees(radians) + + @staticmethod + def radians(degrees: float) -> float: + return math.radians(degrees) + + @staticmethod + def exp(a: float) -> float: + return math.exp(a) + + @staticmethod + def log(a: float) -> float: + return math.log(a) + + @staticmethod + def pow(a: float, b: float) -> float: + return a ** b + + @staticmethod + def sqrt(a: float) -> float: + return math.sqrt(a) + + @staticmethod + def sin(angle: float) -> float: + return math.sin(angle) + + @staticmethod + def cos(angle: float) -> float: + return math.cos(angle) + + @staticmethod + def tan(angle: float) -> float: + return math.tan(angle) + + @staticmethod + def atan2(a: float, b: float) -> float: + return math.atan2(a, b) + + @staticmethod + def random() -> float: + import random + return random.random() + + @staticmethod + def isNaN(v: float) -> bool: + return math.isnan(v) + + @staticmethod + def nanToZero(value: float) -> float: + return 0.0 if math.isnan(value) else value + + +# ═══════════════════════════════════════════════════════════════ +# Pattern (re) +# ═══════════════════════════════════════════════════════════════ + +class Pattern(Stub): + """Pattern — 正则表达式模式""" + + CASE_INSENSITIVE = 2 + MULTILINE = 8 + DOTALL = 32 + UNICODE_CASE = 64 + CANON_EQ = 128 + UNIX_LINES = 1 + LITERAL = 16 + COMMENTS = 4 + + def __init__(self, pattern: str, flags: int = 0): + super().__init__() + self._pattern_str = pattern + self._flags = flags + re_flags = 0 + if flags & self.CASE_INSENSITIVE: + re_flags |= _re.IGNORECASE + if flags & self.MULTILINE: + re_flags |= _re.MULTILINE + if flags & self.DOTALL: + re_flags |= _re.DOTALL + if flags & self.UNICODE_CASE: + re_flags |= _re.UNICODE + if flags & self.UNIX_LINES: + pass # no re equivalent + self._compiled = _re.compile(pattern, re_flags) + + @staticmethod + def compile(pattern: str, flags: int = 0) -> "Pattern": + return Pattern(pattern, flags) + + def matcher(self, input_str: str) -> "Matcher": + return Matcher(self._compiled, input_str) + + def split(self, input_str: str, limit: int = 0) -> List[str]: + if limit < 0: + return self._compiled.split(input_str, maxsplit=0) + return self._compiled.split(input_str, maxsplit=limit - 1 if limit > 0 else 0) + + def pattern(self) -> str: + return self._pattern_str + + def flags(self) -> int: + return self._flags + + @staticmethod + def matches(regex: str, input_str: str) -> bool: + return bool(_re.fullmatch(regex, input_str)) + + @staticmethod + def quote(s: str) -> str: + return _re.escape(s) + + def toString(self) -> str: + return self._pattern_str + + +class Matcher(Stub): + """Matcher — 正则匹配器""" + + def __init__(self, compiled_pattern: "Pattern", input_str: str): + super().__init__() + self._pattern = compiled_pattern + self._input = input_str + self._region_start = 0 + self._region_end = len(input_str) + self._match: Optional[Any] = None + + def matches(self) -> bool: + self._match = self._pattern.fullmatch(self._input, + self._region_start, self._region_end) + return self._match is not None + + def find(self, start: int = 0) -> bool: + self._match = self._pattern.search(self._input, start or self._region_start, + self._region_end) + return self._match is not None + + def lookingAt(self) -> bool: + self._match = self._pattern.match(self._input, self._region_start, + self._region_end) + return self._match is not None + + def group(self, group: int = 0) -> Optional[str]: + if self._match: + return self._match.group(group) + return None + + def groupCount(self) -> int: + if self._match: + return len(self._match.groups()) + return 0 + + def start(self, group: int = 0) -> int: + if self._match: + return self._match.start(group) + return -1 + + def end(self, group: int = 0) -> int: + if self._match: + return self._match.end(group) + return -1 + + def replaceAll(self, replacement: str) -> str: + return self._pattern.sub(replacement, self._input) + + def replaceFirst(self, replacement: str) -> str: + return self._pattern.sub(replacement, self._input, count=1) + + def region(self, start: int, end: int) -> "Matcher": + self._region_start = start + self._region_end = end + return self + + def regionStart(self) -> int: + return self._region_start + + def regionEnd(self) -> int: + return self._region_end + + def reset(self, input_str: Optional[str] = None) -> "Matcher": + if input_str is not None: + self._input = input_str + self._match = None + return self + + def hitEnd(self) -> bool: + return False + + def requireEnd(self) -> bool: + return False + + def usePattern(self, new_pattern: "Pattern") -> "Matcher": + self._pattern = new_pattern + return self + + def appendReplacement(self, sb: Any, replacement: str): + pass + + def appendTail(self, sb: Any): + pass + + def toString(self) -> str: + return f"Matcher({self._pattern.pattern()})" + + +# ═══════════════════════════════════════════════════════════════ +# TYPED_VALUE 常量 +# ═══════════════════════════════════════════════════════════════ + +TYPED_VALUE = TypedValue # 别名 + + +# ═══════════════════════════════════════════════════════════════ +# Pair 反向兼容 (android.util.Pair) +# ═══════════════════════════════════════════════════════════════ + +# Pair 已在上面定义 + + +# ═══════════════════════════════════════════════════════════════ +# __all__ +# ═══════════════════════════════════════════════════════════════ + +__all__ = [ + "Log", + "SparseArray", + "SparseIntArray", + "SparseBooleanArray", + "LruCache", + "DisplayMetrics", + "TypedValue", + "Pair", + "Size", + "SizeF", + "StateSet", + "MathUtils", + "Pattern", + "Matcher", + "TYPED_VALUE", +] \ No newline at end of file diff --git a/android_pyapi/android/view.py b/android_pyapi/android/view.py new file mode 100644 index 0000000..42df7fd --- /dev/null +++ b/android_pyapi/android/view.py @@ -0,0 +1,3801 @@ +""" +Android View 组件 — View, ViewGroup, MotionEvent, KeyEvent, GestureDetector 等 +方法签名完全对齐 Android SDK +""" + +import math +import time +import weakref +from typing import Any, Callable, Dict, List, Optional, Tuple, Union + +from .._core import Stub, AndroidOnlyError, android_context, HAS_PYAUTOGUI + + +# ═══════════════════════════════════════════════════════════════ +# Gravity +# ═══════════════════════════════════════════════════════════════ + +class Gravity: + """Gravity — 布局重力常量""" + NO_GRAVITY = 0x0000 + TOP = 0x0030 + BOTTOM = 0x0050 + LEFT = 0x0003 + RIGHT = 0x0005 + CENTER = 0x0011 + CENTER_HORIZONTAL = 0x0001 + CENTER_VERTICAL = 0x0010 + FILL = 0x0077 + FILL_HORIZONTAL = 0x0007 + FILL_VERTICAL = 0x0070 + CLIP_HORIZONTAL = 0x0008 + CLIP_VERTICAL = 0x0080 + START = 0x00800001 + END = 0x00800005 + RELATIVE_LAYOUT_DIRECTION = 0x00800000 + + @staticmethod + def getAbsoluteGravity(gravity: int, layout_direction: int) -> int: + from ..os import View + if layout_direction == View.LAYOUT_DIRECTION_RTL: + if gravity & Gravity.START: + gravity = (gravity & ~Gravity.START) | Gravity.RIGHT + if gravity & Gravity.END: + gravity = (gravity & ~Gravity.END) | Gravity.LEFT + else: + if gravity & Gravity.START: + gravity = (gravity & ~Gravity.START) | Gravity.LEFT + if gravity & Gravity.END: + gravity = (gravity & ~Gravity.END) | Gravity.RIGHT + return gravity + + @staticmethod + def apply(gravity: int, w: int, h: int, container: "Rect") -> int: + return 0 + + +Rect = None + + +def _set_rect_class(): + global Rect + if Rect is None: + class _Rect: + def __init__(self, l=0, t=0, r=0, b=0): + self.left = l + self.top = t + self.right = r + self.bottom = b + def width(self): return self.right - self.left + def height(self): return self.bottom - self.top + def set(self, l, t, r, b): + self.left, self.top, self.right, self.bottom = l, t, r, b + def isEmpty(self): return self.width() <= 0 or self.height() <= 0 + def toString(self): return f"Rect({self.left}, {self.top} - {self.right}, {self.bottom})" + Rect = _Rect + + +_set_rect_class() + + +# ═══════════════════════════════════════════════════════════════ +# View +# ═══════════════════════════════════════════════════════════════ + +class View(Stub): + """View — 所有 UI 组件的基类""" + + # 可见性常量 + VISIBLE = 0 + INVISIBLE = 4 + GONE = 8 + + # 测量常量 + MEASURED_SIZE_STATE_MASK = 0xFF000000 + MEASURED_STATE_MASK = 0xFF000000 + MEASURED_HEIGHT_STATE_SHIFT = 16 + MEASURED_WIDTH_STATE_SHIFT = 0 + + # 测量模式 + EXACTLY = 0x40000000 + AT_MOST = 0x80000000 # -2147483648 + UNSPECIFIED = 0 + + # 滚动轴 + SCROLL_AXIS_NONE = 0 + SCROLL_AXIS_HORIZONTAL = 1 + SCROLL_AXIS_VERTICAL = 2 + + # 布局方向 + LAYOUT_DIRECTION_LTR = 0 + LAYOUT_DIRECTION_RTL = 1 + + # 绘制缓存 + DRAWING_CACHE_QUALITY_LOW = 0x00040000 + DRAWING_CACHE_QUALITY_HIGH = 0x00080000 + DRAWING_CACHE_QUALITY_AUTO = 0 + + # 焦点方向 + FOCUS_BACKWARD = 0x00000001 + FOCUS_FORWARD = 0x00000002 + FOCUS_LEFT = 0x00000011 + FOCUS_UP = 0x00000021 + FOCUS_RIGHT = 0x00000042 + FOCUS_DOWN = 0x00000082 + + # Keep screen on + KEEP_SCREEN_ON = 0x02000000 + + # 状态 + STATUS_BAR_HIDDEN = 0x00000001 + STATUS_BAR_VISIBLE = 0x00000000 + NAVIGATION_BAR_HIDDEN = 0x00000002 + NAVIGATION_BAR_VISIBLE = 0x00000000 + + # 系统UI可见性 + SYSTEM_UI_FLAG_VISIBLE = 0 + SYSTEM_UI_FLAG_LOW_PROFILE = 0x00000001 + SYSTEM_UI_FLAG_HIDE_NAVIGATION = 0x00000002 + SYSTEM_UI_FLAG_FULLSCREEN = 0x00000004 + SYSTEM_UI_FLAG_LAYOUT_STABLE = 0x00000100 + SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION = 0x00000200 + SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN = 0x00000400 + SYSTEM_UI_FLAG_IMMERSIVE = 0x00000800 + SYSTEM_UI_FLAG_IMMERSIVE_STICKY = 0x00001000 + + _next_id = 0x10000000 + _id_lock = None + + def __init__(self, context=None): + super().__init__() + if context is None: + context = android_context + self._context = context + self._id = View.NO_ID + self._tag: Any = None + self._layout_params: Optional["ViewGroup.LayoutParams"] = None + self._visibility = View.VISIBLE + self._enabled = True + self._clickable = False + self._focusable = False + self._selected = False + self._activated = False + self._x: float = 0.0 + self._y: float = 0.0 + self._translation_x: float = 0.0 + self._translation_y: float = 0.0 + self._rotation: float = 0.0 + self._scale_x: float = 1.0 + self._scale_y: float = 1.0 + self._alpha: float = 1.0 + self._pivot_x: float = 0.0 + self._pivot_y: float = 0.0 + self._measured_width: int = 0 + self._measured_height: int = 0 + self._left: int = 0 + self._top: int = 0 + self._right: int = 0 + self._bottom: int = 0 + self._padding_left: int = 0 + self._padding_top: int = 0 + self._padding_right: int = 0 + self._padding_bottom: int = 0 + self._parent: Optional["ViewGroup"] = None + self._background_color: Optional[int] = None + self._background_drawable: Any = None + self._background_resource: int = 0 + self._on_click_listener: Optional[Callable] = None + self._on_long_click_listener: Optional[Callable] = None + self._on_touch_listener: Optional[Callable] = None + self._on_key_listener: Optional[Callable] = None + self._callbacks: Dict[Any, Tuple[Callable, float]] = {} + self._scroll_x: int = 0 + self._scroll_y: int = 0 + self._will_not_draw = False + self._layer_type = View.LAYER_TYPE_NONE + self._layout_direction = View.LAYOUT_DIRECTION_LTR + self._min_width: int = 0 + self._min_height: int = 0 + self._elevation: float = 0.0 + self._clickable = False + self._long_clickable = False + self._context_clickable = False + self._children: List["View"] = [] + + # ─── 常量 ────────────────────────────────────────────────── + + NO_ID = -1 + DRAWING_CACHE_QUALITY_LOW = 0x00040000 + DRAWING_CACHE_QUALITY_HIGH = 0x00080000 + DRAWING_CACHE_QUALITY_AUTO = 0 + LAYER_TYPE_NONE = 0 + LAYER_TYPE_SOFTWARE = 1 + LAYER_TYPE_HARDWARE = 2 + FADING_EDGE_NONE = 0 + FADING_EDGE_HORIZONTAL = 0x00001000 + FADING_EDGE_VERTICAL = 0x00002000 + OVER_SCROLL_ALWAYS = 0 + OVER_SCROLL_IF_CONTENT_SCROLLS = 1 + OVER_SCROLL_NEVER = 2 + + # ─── id / tag ────────────────────────────────────────────── + + def setId(self, id_: int): + self._id = id_ + + def getId(self) -> int: + return self._id + + def setTag(self, tag: Any): + self._tag = tag + + def getTag(self) -> Any: + return self._tag + + # ─── layout params ───────────────────────────────────────── + + def setLayoutParams(self, params: "ViewGroup.LayoutParams"): + self._layout_params = params + + def getLayoutParams(self) -> Optional["ViewGroup.LayoutParams"]: + return self._layout_params + + # ─── visibility ──────────────────────────────────────────── + + def setVisibility(self, visibility: int): + self._visibility = visibility + + def getVisibility(self) -> int: + return self._visibility + + def isShown(self) -> bool: + return self._visibility == View.VISIBLE + + # ─── position / size ─────────────────────────────────────── + + def getX(self) -> float: + return self._x + + def setX(self, x: float): + self._x = x + + def getY(self) -> float: + return self._y + + def setY(self, y: float): + self._y = y + + def getWidth(self) -> int: + return self._right - self._left + + def getHeight(self) -> int: + return self._bottom - self._top + + def getLeft(self) -> int: + return self._left + + def getTop(self) -> int: + return self._top + + def getRight(self) -> int: + return self._right + + def getBottom(self) -> int: + return self._bottom + + def getMeasuredWidth(self) -> int: + return self._measured_width + + def getMeasuredHeight(self) -> int: + return self._measured_height + + def setFrame(self, left: int, top: int, right: int, bottom: int) -> bool: + changed = (left != self._left or top != self._top or + right != self._right or bottom != self._bottom) + self._left, self._top, self._right, self._bottom = left, top, right, bottom + return changed + + def layout(self, l: int, t: int, r: int, b: int): + old_l, old_t, old_r, old_b = self._left, self._top, self._right, self._bottom + changed = self.setFrame(l, t, r, b) + if changed: + self.onLayout(changed, l - old_l, t - old_t, r - old_r, b - old_b) + + def onLayout(self, changed: bool, left: int, top: int, right: int, bottom: int): + pass + + def measure(self, width_measure_spec: int, height_measure_spec: int): + self.onMeasure(width_measure_spec, height_measure_spec) + + def onMeasure(self, width_measure_spec: int, height_measure_spec: int): + w = View.getDefaultSize(self.getSuggestedMinimumWidth(), width_measure_spec) + h = View.getDefaultSize(self.getSuggestedMinimumHeight(), height_measure_spec) + self.setMeasuredDimension(w, h) + + def setMeasuredDimension(self, measured_width: int, measured_height: int): + self._measured_width = measured_width + self._measured_height = measured_height + + def getSuggestedMinimumWidth(self) -> int: + return self._min_width + + def getSuggestedMinimumHeight(self) -> int: + return self._min_height + + def getMinimumWidth(self) -> int: + return self._min_width + + def getMinimumHeight(self) -> int: + return self._min_height + + def setMinimumWidth(self, min_w: int): + self._min_width = min_w + + def setMinimumHeight(self, min_h: int): + self._min_height = min_h + + def draw(self, canvas: Any): + self.onDraw(canvas) + + def onDraw(self, canvas: Any): + pass + + # ─── listeners ───────────────────────────────────────────── + + def setOnClickListener(self, listener: Optional[Callable]): + self._on_click_listener = listener + + def setOnLongClickListener(self, listener: Optional[Callable]) -> None: + self._on_long_click_listener = listener + + def setOnTouchListener(self, listener: Optional[Callable]) -> None: + self._on_touch_listener = listener + + def setOnKeyListener(self, listener: Optional[Callable]) -> None: + self._on_key_listener = listener + + def callOnClick(self) -> bool: + if self._on_click_listener: + self._on_click_listener(self) + return True + return False + + def performClick(self) -> bool: + return self.callOnClick() + + def performLongClick(self) -> bool: + if self._on_long_click_listener: + self._on_long_click_listener(self) + return True + return False + + # ─── post / delayed ──────────────────────────────────────── + + def post(self, action: Callable) -> bool: + action() + return True + + def postDelayed(self, action: Callable, delay_millis: int) -> bool: + import threading + timer = threading.Timer(delay_millis / 1000.0, action) + timer.daemon = True + timer.start() + self._callbacks[action] = (action, time.time() + delay_millis / 1000.0) + return True + + def removeCallbacks(self, action: Optional[Callable] = None): + if action is None: + self._callbacks.clear() + else: + self._callbacks.pop(action, None) + + # ─── invalidate / requestLayout ──────────────────────────── + + def invalidate(self): + pass + + def requestLayout(self): + pass + + def requestFocus(self, direction: int = FOCUS_FORWARD) -> bool: + if self._visibility != View.VISIBLE or not self._enabled: + return False + return True + + # ─── padding ─────────────────────────────────────────────── + + def setPadding(self, left: int, top: int, right: int, bottom: int): + self._padding_left = left + self._padding_top = top + self._padding_right = right + self._padding_bottom = bottom + + def getPaddingLeft(self) -> int: + return self._padding_left + + def getPaddingTop(self) -> int: + return self._padding_top + + def getPaddingRight(self) -> int: + return self._padding_right + + def getPaddingBottom(self) -> int: + return self._padding_bottom + + def getPaddingStart(self) -> int: + return self._padding_left + + def getPaddingEnd(self) -> int: + return self._padding_right + + # ─── background ──────────────────────────────────────────── + + def setBackgroundColor(self, color: int): + self._background_color = color + self._background_drawable = None + + def setBackground(self, background: Any): + self._background_drawable = background + + def setBackgroundDrawable(self, background: Any): + self._background_drawable = background + + def setBackgroundResource(self, resid: int): + self._background_resource = resid + + def getBackground(self) -> Any: + return self._background_drawable + + # ─── transform properties ────────────────────────────────── + + def setAlpha(self, alpha: float): + self._alpha = alpha + + def getAlpha(self) -> float: + return self._alpha + + def setRotation(self, rotation: float): + self._rotation = rotation + + def getRotation(self) -> float: + return self._rotation + + def setScaleX(self, scale_x: float): + self._scale_x = scale_x + + def getScaleX(self) -> float: + return self._scale_x + + def setScaleY(self, scale_y: float): + self._scale_y = scale_y + + def getScaleY(self) -> float: + return self._scale_y + + def setTranslationX(self, translation_x: float): + self._translation_x = translation_x + + def getTranslationX(self) -> float: + return self._translation_x + + def setTranslationY(self, translation_y: float): + self._translation_y = translation_y + + def getTranslationY(self) -> float: + return self._translation_y + + def getPivotX(self) -> float: + return self._pivot_x + + def setPivotX(self, pivot_x: float): + self._pivot_x = pivot_x + + def getPivotY(self) -> float: + return self._pivot_y + + def setPivotY(self, pivot_y: float): + self._pivot_y = pivot_y + + def setElevation(self, elevation: float): + self._elevation = elevation + + def getElevation(self) -> float: + return self._elevation + + def animate(self) -> "ViewPropertyAnimator": + return ViewPropertyAnimator(self) + + # ─── parent / root ───────────────────────────────────────── + + def getParent(self) -> Optional["ViewParent"]: + return self._parent + + def getRootView(self) -> "View": + v = self + while v._parent is not None: + v = v._parent + return v + + # ─── hierarchy ───────────────────────────────────────────── + + def findViewById(self, id_: int) -> Optional["View"]: + if self._id == id_: + return self + if hasattr(self, "_children"): + for child in self._children: + result = child.findViewById(id_) + if result is not None: + return result + return None + + def bringToFront(self): + if self._parent is not None: + self._parent.bringChildToFront(self) + + # ─── enable / focusable / clickable ──────────────────────── + + def setEnabled(self, enabled: bool): + self._enabled = enabled + + def isEnabled(self) -> bool: + return self._enabled + + def setFocusable(self, focusable: bool): + self._focusable = focusable + + def isFocusable(self) -> bool: + return self._focusable + + def setClickable(self, clickable: bool): + self._clickable = clickable + + def isClickable(self) -> bool: + return self._clickable + + def setLongClickable(self, long_clickable: bool): + self._long_clickable = long_clickable + + def isLongClickable(self) -> bool: + return self._long_clickable + + def setContextClickable(self, context_clickable: bool): + self._context_clickable = context_clickable + + def isContextClickable(self) -> bool: + return self._context_clickable + + def setSelected(self, selected: bool): + self._selected = selected + + def isSelected(self) -> bool: + return self._selected + + def setActivated(self, activated: bool): + self._activated = activated + + def isActivated(self) -> bool: + return self._activated + + # ─── context / resources ─────────────────────────────────── + + def getContext(self): + return self._context + + def getResources(self): + if hasattr(self._context, "getResources"): + return self._context.getResources() + return None + + # ─── scroll ──────────────────────────────────────────────── + + def setScrollX(self, value: int): + self._scroll_x = value + + def setScrollY(self, value: int): + self._scroll_y = value + + def getScrollX(self) -> int: + return self._scroll_x + + def getScrollY(self) -> int: + return self._scroll_y + + def scrollTo(self, x: int, y: int): + self._scroll_x = x + self._scroll_y = y + + def scrollBy(self, x: int, y: int): + self._scroll_x += x + self._scroll_y += y + + def computeScroll(self): + pass + + # ─── drawing / layer ─────────────────────────────────────── + + def setWillNotDraw(self, will_not_draw: bool): + self._will_not_draw = will_not_draw + + def willNotDraw(self) -> bool: + return self._will_not_draw + + def setLayerType(self, layer_type: int, paint: Any = None): + self._layer_type = layer_type + + def getLayerType(self) -> int: + return self._layer_type + + def buildLayer(self): + pass + + def setDrawingCacheQuality(self, quality: int): + pass + + def getDrawingCacheQuality(self) -> int: + return View.DRAWING_CACHE_QUALITY_AUTO + + def getDrawingCache(self, auto_scale: bool = True) -> Any: + return None + + def buildDrawingCache(self, auto_scale: bool = True): + pass + + def destroyDrawingCache(self): + pass + + # ─── layout direction ────────────────────────────────────── + + def setLayoutDirection(self, layout_direction: int): + self._layout_direction = layout_direction + + def getLayoutDirection(self) -> int: + return self._layout_direction + + def setTextDirection(self, text_direction: int): + pass + + def getTextDirection(self) -> int: + return 0 + + def setTextAlignment(self, text_alignment: int): + pass + + def getTextAlignment(self) -> int: + return 0 + + # ─── hit / focus ─────────────────────────────────────────── + + def onTouchEvent(self, event: "MotionEvent") -> bool: + return self._clickable or self._long_clickable + + def onTrackballEvent(self, event: "MotionEvent") -> bool: + return False + + def onKeyDown(self, key_code: int, event: "KeyEvent") -> bool: + return False + + def onKeyUp(self, key_code: int, event: "KeyEvent") -> bool: + return False + + def onKeyLongPress(self, key_code: int, event: "KeyEvent") -> bool: + return False + + def onKeyMultiple(self, key_code: int, repeat_count: int, event: "KeyEvent") -> bool: + return False + + def dispatchTouchEvent(self, event: "MotionEvent") -> bool: + if self._on_touch_listener is not None: + handled = self._on_touch_listener(self, event) + if handled: + return True + return self.onTouchEvent(event) + + def dispatchKeyEvent(self, event: "KeyEvent") -> bool: + if self._on_key_listener is not None: + handled = self._on_key_listener(self, event.getKeyCode(), event) + if handled: + return True + if event.getAction() == KeyEvent.ACTION_DOWN: + return self.onKeyDown(event.getKeyCode(), event) + elif event.getAction() == KeyEvent.ACTION_UP: + return self.onKeyUp(event.getKeyCode(), event) + return False + + def dispatchGenericMotionEvent(self, event: "MotionEvent") -> bool: + return False + + def hasFocus(self) -> bool: + return False + + def isFocused(self) -> bool: + return self.hasFocus() + + def clearFocus(self): + pass + + def onWindowFocusChanged(self, has_window_focus: bool): + pass + + def hasWindowFocus(self) -> bool: + return True + + def getWindowToken(self) -> Any: + return None + + def getWindowVisibility(self) -> int: + return View.VISIBLE + + def getWindowAttachCount(self) -> int: + return 1 + + def onAttachedToWindow(self): + pass + + def onDetachedFromWindow(self): + pass + + def isAttachedToWindow(self) -> bool: + return self._parent is not None + + # ─── Haptic ──────────────────────────────────────────────── + + def performHapticFeedback(self, feedback_constant: int) -> bool: + return False + + HapticFeedbackConstants = None + + # ─── static helpers ──────────────────────────────────────── + + @staticmethod + def generateViewId() -> int: + View._next_id += 1 + return View._next_id + + @staticmethod + def getDefaultSize(size: int, measure_spec: int) -> int: + spec_mode = View.MeasureSpec.getMode(measure_spec) + spec_size = View.MeasureSpec.getSize(measure_spec) + if spec_mode == View.UNSPECIFIED: + return size + else: + return spec_size + + @staticmethod + def combineMeasuredStates(cur_state: int, new_state: int) -> int: + return cur_state | new_state + + @staticmethod + def resolveSize(size: int, measure_spec: int) -> int: + return View.getDefaultSize(size, measure_spec) + + @staticmethod + def resolveSizeAndState(size: int, measure_spec: int, child_measured_state: int) -> int: + result = View.resolveSize(size, measure_spec) + return result | (child_measured_state & View.MEASURED_STATE_MASK) + + @staticmethod + def inflate(context, resource, root): + return View(context) + + +class MeasureSpec: + """MeasureSpec — 测量规格包装""" + EXACTLY = View.EXACTLY + AT_MOST = 0x80000000 # -2147483648 + UNSPECIFIED = 0 + MODE_SHIFT = 30 + MODE_MASK = 0xC0000000 + + @staticmethod + def makeMeasureSpec(size: int, mode: int) -> int: + return (size & ~MeasureSpec.MODE_MASK) | (mode & MeasureSpec.MODE_MASK) + + @staticmethod + def getMode(measure_spec: int) -> int: + return measure_spec & MeasureSpec.MODE_MASK + + @staticmethod + def getSize(measure_spec: int) -> int: + return measure_spec & ~MeasureSpec.MODE_MASK + + @staticmethod + def toString(measure_spec: int) -> str: + mode = MeasureSpec.getMode(measure_spec) + size = MeasureSpec.getSize(measure_spec) + mode_str = "UNSPECIFIED" + if mode == MeasureSpec.EXACTLY: + mode_str = "EXACTLY" + elif mode == MeasureSpec.AT_MOST: + mode_str = "AT_MOST" + return f"MeasureSpec: {mode_str} {size}" + + +View.MeasureSpec = MeasureSpec + + +class ViewPropertyAnimator: + """ViewPropertyAnimator — View 属性动画""" + + def __init__(self, view: View): + self._view = view + self._duration: int = 300 + self._interpolator: Any = None + + def setDuration(self, duration: int) -> "ViewPropertyAnimator": + self._duration = duration + return self + + def alpha(self, value: float) -> "ViewPropertyAnimator": + self._view.setAlpha(value) + return self + + def translationX(self, value: float) -> "ViewPropertyAnimator": + self._view.setTranslationX(value) + return self + + def translationY(self, value: float) -> "ViewPropertyAnimator": + self._view.setTranslationY(value) + return self + + def rotation(self, value: float) -> "ViewPropertyAnimator": + self._view.setRotation(value) + return self + + def scaleX(self, value: float) -> "ViewPropertyAnimator": + self._view.setScaleX(value) + return self + + def scaleY(self, value: float) -> "ViewPropertyAnimator": + self._view.setScaleY(value) + return self + + def x(self, value: float) -> "ViewPropertyAnimator": + self._view.setX(value) + return self + + def y(self, value: float) -> "ViewPropertyAnimator": + self._view.setY(value) + return self + + def start(self): + pass + + def cancel(self): + pass + + def setInterpolator(self, interpolator: Any) -> "ViewPropertyAnimator": + self._interpolator = interpolator + return self + + def withStartAction(self, runnable: Callable) -> "ViewPropertyAnimator": + return self + + def withEndAction(self, runnable: Callable) -> "ViewPropertyAnimator": + return self + + +# ═══════════════════════════════════════════════════════════════ +# ViewParent / ViewManager +# ═══════════════════════════════════════════════════════════════ + +class ViewParent(Stub): + """ViewParent — 视图父接口""" + + def requestLayout(self): + pass + + def invalidateChild(self, child: View, r: Any): + pass + + def requestChildFocus(self, child: View, focused: View): + pass + + def clearChildFocus(self, child: View): + pass + + def getParent(self) -> Optional["ViewParent"]: + return None + + def bringChildToFront(self, child: View): + pass + + def focusSearch(self, child: View, direction: int) -> Optional[View]: + return None + + def requestChildRectangleOnScreen(self, child: View, rectangle: Any, immediate: bool) -> bool: + return False + + def getChildVisibleRect(self, child: View, r: Any, offset: Any) -> bool: + return False + + def requestTransparentRegion(self, child: View): + pass + + def onStartNestedScroll(self, child: View, target: View, nested_scroll_axes: int) -> bool: + return False + + def onStopNestedScroll(self, target: View): + pass + + def onNestedScroll(self, target: View, dx_consumed: int, dy_consumed: int, + dx_unconsumed: int, dy_unconsumed: int): + pass + + def onNestedPreScroll(self, target: View, dx: int, dy: int, consumed: List[int]): + pass + + def onNestedFling(self, target: View, velocity_x: float, velocity_y: float, consumed: bool) -> bool: + return False + + def onNestedPreFling(self, target: View, velocity_x: float, velocity_y: float) -> bool: + return False + + def isNestedScrollingEnabled(self) -> bool: + return False + + def setNestedScrollingEnabled(self, enabled: bool): + pass + + def notifySubtreeAccessibilityStateChanged(self, child: View, source: View, change_type: int): + pass + + +class ViewManager(Stub): + """ViewManager — 视图管理接口""" + + def addView(self, view: View, params: "ViewGroup.LayoutParams"): + pass + + def updateViewLayout(self, view: View, params: "ViewGroup.LayoutParams"): + pass + + def removeView(self, view: View): + pass + + +# ═══════════════════════════════════════════════════════════════ +# ViewGroup +# ═══════════════════════════════════════════════════════════════ + +class ViewGroup(View, ViewParent, ViewManager): + """ViewGroup — 可包含子视图的容器""" + + CLIP_TO_PADDING_MASK = 0x00000001 + CLIP_CHILDREN_MASK = 0x00000002 + FLAG_CLIP_CHILDREN = 0x00000001 + FLAG_CLIP_TO_PADDING = 0x00000002 + FLAG_USE_CHILD_DRAWING_ORDER = 0x00000400 + + def __init__(self, context=None): + super().__init__(context) + self._children: List[View] = [] + self._clip_children = True + self._clip_to_padding = True + self._layout_mode = 0 + self._child_drawing_order_reversed = False + self._persistent_drawing_cache = False + self._always_drawn_with_cache = True + self._animate_on_set_layout = True + self._add_stateset = True + self._layout_animations = False + + # ─── child management ────────────────────────────────────── + + def addView(self, child: View, index: int = -1, + params: Optional["ViewGroup.LayoutParams"] = None): + if params is None: + params = ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT) + if index < 0 or index >= len(self._children): + self._children.append(child) + else: + self._children.insert(index, child) + child._parent = self + child.setLayoutParams(params) + child.onAttachedToWindow() + + def addView(self, child: View, params: Optional["ViewGroup.LayoutParams"] = None): + self.addView(child, -1, params) + + def removeView(self, view: View): + if view in self._children: + self._children.remove(view) + view._parent = None + view.onDetachedFromWindow() + + def removeViewAt(self, index: int): + if 0 <= index < len(self._children): + self.removeView(self._children[index]) + + def removeAllViews(self): + for child in list(self._children): + child._parent = None + child.onDetachedFromWindow() + self._children.clear() + + def getChildCount(self) -> int: + return len(self._children) + + def getChildAt(self, index: int) -> Optional[View]: + if 0 <= index < len(self._children): + return self._children[index] + return None + + def indexOfChild(self, child: View) -> int: + try: + return self._children.index(child) + except ValueError: + return -1 + + def bringChildToFront(self, child: View): + if child in self._children: + self._children.remove(child) + self._children.append(child) + + def requestChildFocus(self, child: View, focused: View): + pass + + def onInterceptTouchEvent(self, event: "MotionEvent") -> bool: + return False + + def onInterceptHoverEvent(self, event: "MotionEvent") -> bool: + return False + + def addChildrenForAccessibility(self, children: List[View]): + for child in self._children: + children.append(child) + + def setClipChildren(self, clip_children: bool): + self._clip_children = clip_children + + def getClipChildren(self) -> bool: + return self._clip_children + + def setClipToPadding(self, clip_to_padding: bool): + self._clip_to_padding = clip_to_padding + + def getClipToPadding(self) -> bool: + return self._clip_to_padding + + def setLayoutAnimation(self, layout_animation: Any): + pass + + def getLayoutAnimation(self) -> Any: + return None + + def setLayoutMode(self, layout_mode: int): + self._layout_mode = layout_mode + + def getLayoutMode(self) -> int: + return self._layout_mode + + def setPersistentDrawingCache(self, drawing_cache_to_keep: int): + self._persistent_drawing_cache = bool(drawing_cache_to_keep) + + def setAddStatesFromChildren(self, add_states: bool): + self._add_stateset = add_states + + def addStatesFromChildren(self) -> bool: + return self._add_stateset + + def setChildDrawingOrderCallback(self, callback: Any): + pass + + def setChildrenDrawingOrderEnabled(self, enabled: bool): + self._child_drawing_order_reversed = enabled + + def isChildrenDrawingOrderEnabled(self) -> bool: + return self._child_drawing_order_reversed + + def setChildrenDrawnWithCacheEnabled(self, enabled: bool): + self._always_drawn_with_cache = enabled + + def isChildrenDrawnWithCacheEnabled(self) -> bool: + return self._always_drawn_with_cache + + def setMotionEventSplittingEnabled(self, split: bool): + pass + + def isMotionEventSplittingEnabled(self) -> bool: + return True + + def setTransitionGroup(self, is_transition_group: bool): + pass + + def isTransitionGroup(self) -> bool: + return False + + def startLayoutAnimation(self): + pass + + def scheduleLayoutAnimation(self): + pass + + def gatherTransparentRegion(self, region: Any) -> bool: + return False + + def requestTransparentRegion(self, child: View): + pass + + def onChildVisibilityChanged(self, child: View, old_visibility: int, new_visibility: int): + pass + + def focusSearch(self, focused: View, direction: int) -> Optional[View]: + return None + + def focusableViewAvailable(self, v: View): + pass + + def showContextMenuForChild(self, original_view: View) -> bool: + return False + + def createContextMenu(self, menu: Any): + pass + + def showContextMenu(self) -> bool: + return False + + def startActionModeForChild(self, original_view: View, action_mode: Any) -> Optional[Any]: + return None + + # ─── LayoutParams ────────────────────────────────────────── + + class LayoutParams: + """ViewGroup.LayoutParams — 布局参数""" + + MATCH_PARENT = -1 + WRAP_CONTENT = -2 + + def __init__(self, width: int = WRAP_CONTENT, height: int = WRAP_CONTENT): + self.width = width + self.height = height + + @staticmethod + def getSize(size: int) -> int: + return size + + # ─── MarginLayoutParams ──────────────────────────────────── + + class MarginLayoutParams(LayoutParams): + """ViewGroup.MarginLayoutParams — 带边距的布局参数""" + + def __init__(self, width: int = -2, height: int = -2): + super().__init__(width, height) + self.leftMargin = 0 + self.topMargin = 0 + self.rightMargin = 0 + self.bottomMargin = 0 + self.startMargin = 0 + self.endMargin = 0 + + def setMargins(self, left: int, top: int, right: int, bottom: int): + self.leftMargin = left + self.topMargin = top + self.rightMargin = right + self.bottomMargin = bottom + + def getMarginLeft(self) -> int: + return self.leftMargin + + def getMarginTop(self) -> int: + return self.topMargin + + def getMarginRight(self) -> int: + return self.rightMargin + + def getMarginBottom(self) -> int: + return self.bottomMargin + + def getMarginStart(self) -> int: + return self.startMargin if self.startMargin != 0 else self.leftMargin + + def getMarginEnd(self) -> int: + return self.endMargin if self.endMargin != 0 else self.rightMargin + + def setMarginStart(self, start: int): + self.startMargin = start + + def setMarginEnd(self, end: int): + self.endMargin = end + + +# ═══════════════════════════════════════════════════════════════ +# WindowManager +# ═══════════════════════════════════════════════════════════════ + +class WindowManager(ViewManager): + """WindowManager — 窗口管理器""" + + class LayoutParams(ViewGroup.LayoutParams): + """WindowManager.LayoutParams — 窗口布局参数""" + + # 窗口类型 + TYPE_FIRST_APPLICATION = 1 + TYPE_APPLICATION = 2 + TYPE_APPLICATION_STARTING = 3 + TYPE_APPLICATION_PANEL = 1000 + TYPE_APPLICATION_MEDIA = 1001 + TYPE_APPLICATION_SUB_PANEL = 1002 + TYPE_APPLICATION_ATTACHED_DIALOG = 1003 + TYPE_APPLICATION_OVERLAY = 1004 + + # 标志位 + FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001 + FLAG_DIM_BEHIND = 0x00000002 + FLAG_BLUR_BEHIND = 0x00000004 + FLAG_NOT_FOCUSABLE = 0x00000008 + FLAG_NOT_TOUCHABLE = 0x00000010 + FLAG_NOT_TOUCH_MODAL = 0x00000020 + FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040 + FLAG_KEEP_SCREEN_ON = 0x00000080 + FLAG_LAYOUT_IN_SCREEN = 0x00000100 + FLAG_LAYOUT_NO_LIMITS = 0x00000200 + FLAG_FULLSCREEN = 0x00000400 + FLAG_FORCE_NOT_FULLSCREEN = 0x00000800 + FLAG_DITHER = 0x00001000 + FLAG_SECURE = 0x00002000 + FLAG_SCALED = 0x00004000 + FLAG_IGNORE_CHEEK_PRESSES = 0x00008000 + FLAG_LAYOUT_INSET_DECOR = 0x00010000 + FLAG_ALT_FOCUSABLE_IM = 0x00020000 + FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000 + FLAG_SHOW_WHEN_LOCKED = 0x00080000 + FLAG_SHOW_WALLPAPER = 0x00100000 + FLAG_TURN_SCREEN_ON = 0x00200000 + FLAG_DISMISS_KEYGUARD = 0x00400000 + FLAG_SPLIT_TOUCH = 0x00800000 + FLAG_HARDWARE_ACCELERATED = 0x01000000 + FLAG_LOCAL_FOCUS_MODE = 0x02000000 + FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS = 0x04000000 + FLAG_TRANSLUCENT_STATUS = 0x04000000 + FLAG_TRANSLUCENT_NAVIGATION = 0x08000000 + + # 软输入模式 + SOFT_INPUT_STATE_UNSPECIFIED = 0 + SOFT_INPUT_STATE_UNCHANGED = 1 + SOFT_INPUT_STATE_HIDDEN = 2 + SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3 + SOFT_INPUT_STATE_VISIBLE = 4 + SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5 + SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00 + SOFT_INPUT_ADJUST_RESIZE = 0x10 + SOFT_INPUT_ADJUST_PAN = 0x20 + SOFT_INPUT_ADJUST_NOTHING = 0x30 + SOFT_INPUT_IS_FORWARD_NAVIGATION = 0x100 + + # 屏幕方向 + SCREEN_ORIENTATION_UNSPECIFIED = -1 + SCREEN_ORIENTATION_LANDSCAPE = 0 + SCREEN_ORIENTATION_PORTRAIT = 1 + SCREEN_ORIENTATION_USER = 2 + SCREEN_ORIENTATION_BEHIND = 3 + SCREEN_ORIENTATION_SENSOR = 4 + SCREEN_ORIENTATION_NOSENSOR = 5 + SCREEN_ORIENTATION_SENSOR_LANDSCAPE = 6 + SCREEN_ORIENTATION_SENSOR_PORTRAIT = 7 + SCREEN_ORIENTATION_REVERSE_LANDSCAPE = 8 + SCREEN_ORIENTATION_REVERSE_PORTRAIT = 9 + SCREEN_ORIENTATION_FULL_SENSOR = 10 + SCREEN_ORIENTATION_USER_LANDSCAPE = 11 + SCREEN_ORIENTATION_USER_PORTRAIT = 12 + SCREEN_ORIENTATION_FULL_USER = 13 + SCREEN_ORIENTATION_LOCKED = 14 + + # 重力 + gravity = Gravity.NO_GRAVITY + x = 0 + y = 0 + horizontalMargin = 0.0 + verticalMargin = 0.0 + + def __init__(self, w: int = ViewGroup.LayoutParams.WRAP_CONTENT, + h: int = ViewGroup.LayoutParams.WRAP_CONTENT): + super().__init__(w, h) + self.type = WindowManager.LayoutParams.TYPE_APPLICATION + self.flags = 0 + self.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED + self.screenOrientation = WindowManager.LayoutParams.SCREEN_ORIENTATION_UNSPECIFIED + self.gravity = Gravity.NO_GRAVITY + self.x = 0 + self.y = 0 + self.token: Any = None + self.windowAnimations: int = 0 + self.packageName: str = "" + self.screenBrightness = -1.0 + self.buttonBrightness = -1.0 + self.rotationAnimation = 0 + self.preferredDisplayModeId = 0 + self.colorMode = 0 + self.format = -1 + + def __init__(self): + super().__init__() + self._views: Dict[View, WindowManager.LayoutParams] = {} + + def addView(self, view: View, params: LayoutParams): + self._views[view] = params + + def updateViewLayout(self, view: View, params: LayoutParams): + if view in self._views: + self._views[view] = params + + def removeView(self, view: View): + self._views.pop(view, None) + + def removeViewImmediate(self, view: View): + self._views.pop(view, None) + + def getDefaultDisplay(self) -> "Display": + return Display() + + +# ═══════════════════════════════════════════════════════════════ +# MotionEvent +# ═══════════════════════════════════════════════════════════════ + +class MotionEvent(Stub): + """MotionEvent — 触摸事件""" + + # 动作常量 + ACTION_DOWN = 0 + ACTION_UP = 1 + ACTION_MOVE = 2 + ACTION_CANCEL = 3 + ACTION_OUTSIDE = 4 + ACTION_POINTER_DOWN = 5 + ACTION_POINTER_UP = 6 + ACTION_HOVER_MOVE = 7 + ACTION_HOVER_ENTER = 8 + ACTION_HOVER_EXIT = 9 + ACTION_SCROLL = 11 + ACTION_BUTTON_PRESS = 12 + ACTION_BUTTON_RELEASE = 13 + + # 动作掩码 + ACTION_MASK = 0xFF + ACTION_POINTER_INDEX_MASK = 0xFF00 + ACTION_POINTER_INDEX_SHIFT = 8 + + # 按钮 + BUTTON_PRIMARY = 1 + BUTTON_SECONDARY = 2 + BUTTON_TERTIARY = 4 + BUTTON_BACK = 8 + BUTTON_FORWARD = 16 + BUTTON_STYLUS_PRIMARY = 32 + BUTTON_STYLUS_SECONDARY = 64 + + # 边缘 + EDGE_LEFT = 1 + EDGE_TOP = 2 + EDGE_RIGHT = 4 + EDGE_BOTTOM = 8 + + # 坐标分类 + AXIS_X = 0 + AXIS_Y = 1 + AXIS_PRESSURE = 2 + AXIS_SIZE = 3 + AXIS_TOUCH_MAJOR = 4 + AXIS_TOUCH_MINOR = 5 + AXIS_TOOL_MAJOR = 6 + AXIS_TOOL_MINOR = 7 + AXIS_ORIENTATION = 8 + AXIS_VSCROLL = 9 + AXIS_HSCROLL = 10 + AXIS_Z = 11 + AXIS_RX = 12 + AXIS_RY = 13 + AXIS_RZ = 14 + AXIS_HAT_X = 15 + AXIS_HAT_Y = 16 + AXIS_LTRIGGER = 17 + AXIS_RTRIGGER = 18 + AXIS_THROTTLE = 19 + AXIS_RUDDER = 20 + AXIS_WHEEL = 21 + AXIS_GAS = 22 + AXIS_BRAKE = 23 + AXIS_DISTANCE = 24 + AXIS_TILT = 25 + AXIS_SCROLL = 26 + AXIS_RELATIVE_X = 27 + AXIS_RELATIVE_Y = 28 + AXIS_GENERIC_1 = 32 + AXIS_GENERIC_2 = 33 + AXIS_GENERIC_3 = 34 + AXIS_GENERIC_4 = 35 + AXIS_GENERIC_5 = 36 + AXIS_GENERIC_6 = 37 + AXIS_GENERIC_7 = 38 + AXIS_GENERIC_8 = 39 + AXIS_GENERIC_9 = 40 + AXIS_GENERIC_10 = 41 + AXIS_GENERIC_11 = 42 + AXIS_GENERIC_12 = 43 + AXIS_GENERIC_13 = 44 + AXIS_GENERIC_14 = 45 + AXIS_GENERIC_15 = 46 + AXIS_GENERIC_16 = 47 + + # 输入类型 + TOOL_TYPE_UNKNOWN = 0 + TOOL_TYPE_FINGER = 1 + TOOL_TYPE_STYLUS = 2 + TOOL_TYPE_MOUSE = 3 + TOOL_TYPE_ERASER = 4 + + INVALID_POINTER_ID = -1 + + def __init__(self, action: int = 0, x: float = 0.0, y: float = 0.0, + event_time: int = 0, pointer_count: int = 1, + meta_state: int = 0, button_state: int = 0): + super().__init__() + self._action = action + self._x = x + self._y = y + self._raw_x = x + self._raw_y = y + self._event_time = event_time if event_time else int(time.time() * 1000) + self._down_time = self._event_time + self._pointer_count = pointer_count + self._meta_state = meta_state + self._button_state = button_state + self._pointers: List[Dict] = [] + for i in range(pointer_count): + self._pointers.append({ + "id": i, + "x": x, + "y": y, + "pressure": 1.0, + "size": 0.5, + "tool_type": MotionEvent.TOOL_TYPE_FINGER, + }) + + @classmethod + def obtain(cls, down_time: int, event_time: int, action: int, + x: float, y: float, meta_state: int = 0) -> "MotionEvent": + return cls(action=action, x=x, y=y, event_time=event_time) + + @classmethod + def obtainNoHistory(cls, me: "MotionEvent") -> "MotionEvent": + return cls(action=me._action, x=me._x, y=me._y, + event_time=me._event_time, pointer_count=me._pointer_count) + + def recycle(self): + pass + + def getAction(self) -> int: + return self._action + + def getActionMasked(self) -> int: + return self._action & MotionEvent.ACTION_MASK + + def getActionIndex(self) -> int: + return (self._action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT + + def getX(self, pointer_index: int = 0) -> float: + if 0 <= pointer_index < len(self._pointers): + return self._pointers[pointer_index]["x"] + return self._x + + def getY(self, pointer_index: int = 0) -> float: + if 0 <= pointer_index < len(self._pointers): + return self._pointers[pointer_index]["y"] + return self._y + + def getRawX(self) -> float: + return self._raw_x + + def getRawY(self) -> float: + return self._raw_y + + def getPointerCount(self) -> int: + return self._pointer_count + + def getPointerId(self, pointer_index: int) -> int: + if 0 <= pointer_index < len(self._pointers): + return self._pointers[pointer_index]["id"] + return pointer_index + + def findPointerIndex(self, pointer_id: int) -> int: + for i, p in enumerate(self._pointers): + if p["id"] == pointer_id: + return i + return -1 + + def getPressure(self, pointer_index: int = 0) -> float: + if 0 <= pointer_index < len(self._pointers): + return self._pointers[pointer_index]["pressure"] + return 1.0 + + def getSize(self, pointer_index: int = 0) -> float: + if 0 <= pointer_index < len(self._pointers): + return self._pointers[pointer_index]["size"] + return 0.5 + + def getEventTime(self) -> int: + return self._event_time + + def getDownTime(self) -> int: + return self._down_time + + def getMetaState(self) -> int: + return self._meta_state + + def getButtonState(self) -> int: + return self._button_state + + def getToolType(self, pointer_index: int = 0) -> int: + if 0 <= pointer_index < len(self._pointers): + return self._pointers[pointer_index]["tool_type"] + return MotionEvent.TOOL_TYPE_FINGER + + def setLocation(self, x: float, y: float): + self._x = x + self._y = y + + def offsetLocation(self, dx: float, dy: float): + self._x += dx + self._y += dy + + def transform(self, matrix: Any): + pass + + def isLongPress(self) -> bool: + return (self._event_time - self._down_time) > 500 + + def getHistoricalSize(self, pos: int, pointer: int = 0) -> float: + return 0.5 + + +# ═══════════════════════════════════════════════════════════════ +# KeyEvent +# ═══════════════════════════════════════════════════════════════ + +class KeyEvent(Stub): + """KeyEvent — 按键事件""" + + # 动作 + ACTION_DOWN = 0 + ACTION_UP = 1 + ACTION_MULTIPLE = 2 + + # 键码 + KEYCODE_UNKNOWN = 0 + KEYCODE_SOFT_LEFT = 1 + KEYCODE_SOFT_RIGHT = 2 + KEYCODE_HOME = 3 + KEYCODE_BACK = 4 + KEYCODE_CALL = 5 + KEYCODE_ENDCALL = 6 + KEYCODE_0 = 7 + KEYCODE_1 = 8 + KEYCODE_2 = 9 + KEYCODE_3 = 10 + KEYCODE_4 = 11 + KEYCODE_5 = 12 + KEYCODE_6 = 13 + KEYCODE_7 = 14 + KEYCODE_8 = 15 + KEYCODE_9 = 16 + KEYCODE_STAR = 17 + KEYCODE_POUND = 18 + KEYCODE_DPAD_UP = 19 + KEYCODE_DPAD_DOWN = 20 + KEYCODE_DPAD_LEFT = 21 + KEYCODE_DPAD_RIGHT = 22 + KEYCODE_DPAD_CENTER = 23 + KEYCODE_VOLUME_UP = 24 + KEYCODE_VOLUME_DOWN = 25 + KEYCODE_POWER = 26 + KEYCODE_CAMERA = 27 + KEYCODE_CLEAR = 28 + KEYCODE_A = 29 + KEYCODE_B = 30 + KEYCODE_C = 31 + KEYCODE_D = 32 + KEYCODE_E = 33 + KEYCODE_F = 34 + KEYCODE_G = 35 + KEYCODE_H = 36 + KEYCODE_I = 37 + KEYCODE_J = 38 + KEYCODE_K = 39 + KEYCODE_L = 40 + KEYCODE_M = 41 + KEYCODE_N = 42 + KEYCODE_O = 43 + KEYCODE_P = 44 + KEYCODE_Q = 45 + KEYCODE_R = 46 + KEYCODE_S = 47 + KEYCODE_T = 48 + KEYCODE_U = 49 + KEYCODE_V = 50 + KEYCODE_W = 51 + KEYCODE_X = 52 + KEYCODE_Y = 53 + KEYCODE_Z = 54 + KEYCODE_COMMA = 55 + KEYCODE_PERIOD = 56 + KEYCODE_ALT_LEFT = 57 + KEYCODE_ALT_RIGHT = 58 + KEYCODE_SHIFT_LEFT = 59 + KEYCODE_SHIFT_RIGHT = 60 + KEYCODE_TAB = 61 + KEYCODE_SPACE = 62 + KEYCODE_SYM = 63 + KEYCODE_EXPLORER = 64 + KEYCODE_ENVELOPE = 65 + KEYCODE_ENTER = 66 + KEYCODE_DEL = 67 + KEYCODE_GRAVE = 68 + KEYCODE_MINUS = 69 + KEYCODE_EQUALS = 70 + KEYCODE_LEFT_BRACKET = 71 + KEYCODE_RIGHT_BRACKET = 72 + KEYCODE_BACKSLASH = 73 + KEYCODE_SEMICOLON = 74 + KEYCODE_APOSTROPHE = 75 + KEYCODE_SLASH = 76 + KEYCODE_AT = 77 + KEYCODE_NUM = 78 + KEYCODE_HEADSETHOOK = 79 + KEYCODE_FOCUS = 80 + KEYCODE_PLUS = 81 + KEYCODE_MENU = 82 + KEYCODE_NOTIFICATION = 83 + KEYCODE_SEARCH = 84 + KEYCODE_MEDIA_PLAY_PAUSE = 85 + KEYCODE_MEDIA_STOP = 86 + KEYCODE_MEDIA_NEXT = 87 + KEYCODE_MEDIA_PREVIOUS = 88 + KEYCODE_MEDIA_REWIND = 89 + KEYCODE_MEDIA_FAST_FORWARD = 90 + KEYCODE_MUTE = 91 + KEYCODE_PAGE_UP = 92 + KEYCODE_PAGE_DOWN = 93 + KEYCODE_PICTSYMBOLS = 94 + KEYCODE_SWITCH_CHARSET = 95 + KEYCODE_BUTTON_A = 96 + KEYCODE_BUTTON_B = 97 + KEYCODE_BUTTON_C = 98 + KEYCODE_BUTTON_X = 99 + KEYCODE_BUTTON_Y = 100 + KEYCODE_BUTTON_Z = 101 + KEYCODE_BUTTON_L1 = 102 + KEYCODE_BUTTON_R1 = 103 + KEYCODE_BUTTON_L2 = 104 + KEYCODE_BUTTON_R2 = 105 + KEYCODE_BUTTON_THUMBL = 106 + KEYCODE_BUTTON_THUMBR = 107 + KEYCODE_BUTTON_START = 108 + KEYCODE_BUTTON_SELECT = 109 + KEYCODE_BUTTON_MODE = 110 + KEYCODE_ESCAPE = 111 + KEYCODE_FORWARD_DEL = 112 + KEYCODE_CTRL_LEFT = 113 + KEYCODE_CTRL_RIGHT = 114 + KEYCODE_CAPS_LOCK = 115 + KEYCODE_SCROLL_LOCK = 116 + KEYCODE_META_LEFT = 117 + KEYCODE_META_RIGHT = 118 + KEYCODE_FUNCTION = 119 + KEYCODE_SYSRQ = 120 + KEYCODE_BREAK = 121 + KEYCODE_MOVE_HOME = 122 + KEYCODE_MOVE_END = 123 + KEYCODE_INSERT = 124 + KEYCODE_FORWARD = 125 + KEYCODE_MEDIA_PLAY = 126 + KEYCODE_MEDIA_PAUSE = 127 + KEYCODE_MEDIA_CLOSE = 128 + KEYCODE_MEDIA_EJECT = 129 + KEYCODE_MEDIA_RECORD = 130 + KEYCODE_F1 = 131 + KEYCODE_F2 = 132 + KEYCODE_F3 = 133 + KEYCODE_F4 = 134 + KEYCODE_F5 = 135 + KEYCODE_F6 = 136 + KEYCODE_F7 = 137 + KEYCODE_F8 = 138 + KEYCODE_F9 = 139 + KEYCODE_F10 = 140 + KEYCODE_F11 = 141 + KEYCODE_F12 = 142 + KEYCODE_NUM_LOCK = 143 + KEYCODE_NUMPAD_0 = 144 + KEYCODE_NUMPAD_1 = 145 + KEYCODE_NUMPAD_2 = 146 + KEYCODE_NUMPAD_3 = 147 + KEYCODE_NUMPAD_4 = 148 + KEYCODE_NUMPAD_5 = 149 + KEYCODE_NUMPAD_6 = 150 + KEYCODE_NUMPAD_7 = 151 + KEYCODE_NUMPAD_8 = 152 + KEYCODE_NUMPAD_9 = 153 + KEYCODE_NUMPAD_DIVIDE = 154 + KEYCODE_NUMPAD_MULTIPLY = 155 + KEYCODE_NUMPAD_SUBTRACT = 156 + KEYCODE_NUMPAD_ADD = 157 + KEYCODE_NUMPAD_DOT = 158 + KEYCODE_NUMPAD_COMMA = 159 + KEYCODE_NUMPAD_ENTER = 160 + KEYCODE_NUMPAD_EQUALS = 161 + KEYCODE_NUMPAD_LEFT_PAREN = 162 + KEYCODE_NUMPAD_RIGHT_PAREN = 163 + KEYCODE_META_CTRL_LEFT = 164 + KEYCODE_META_CTRL_RIGHT = 165 + KEYCODE_META_ALT_LEFT = 166 + KEYCODE_META_ALT_RIGHT = 167 + KEYCODE_META_SHIFT_LEFT = 168 + KEYCODE_META_SHIFT_RIGHT = 169 + KEYCODE_META_SYM = 170 + + # 元键掩码 + META_ALT_ON = 0x02 + META_ALT_LEFT_ON = 0x10 + META_ALT_RIGHT_ON = 0x20 + META_SHIFT_ON = 0x01 + META_SHIFT_LEFT_ON = 0x40 + META_SHIFT_RIGHT_ON = 0x80 + META_SYM_ON = 0x04 + META_FUNCTION_ON = 0x08 + META_CTRL_ON = 0x1000 + META_CTRL_LEFT_ON = 0x2000 + META_CTRL_RIGHT_ON = 0x4000 + META_META_ON = 0x10000 + META_META_LEFT_ON = 0x20000 + META_META_RIGHT_ON = 0x40000 + META_CAPS_LOCK_ON = 0x100000 + META_NUM_LOCK_ON = 0x200000 + META_SCROLL_LOCK_ON = 0x400000 + + def __init__(self, action: int = ACTION_DOWN, key_code: int = KEYCODE_UNKNOWN, + repeat_count: int = 0, event_time: int = 0, + meta_state: int = 0): + super().__init__() + self._action = action + self._key_code = key_code + self._repeat_count = repeat_count + self._event_time = event_time if event_time else int(time.time() * 1000) + self._down_time = self._event_time + self._meta_state = meta_state + self._device_id = 0 + self._source = 0x101 # InputDevice.SOURCE_KEYBOARD + self._flags = 0 + self._display_id = 0 + + getAction = lambda self: self._action + getKeyCode = lambda self: self._key_code + getRepeatCount = lambda self: self._repeat_count + getEventTime = lambda self: self._event_time + getDownTime = lambda self: self._down_time + getMetaState = lambda self: self._meta_state + getDeviceId = lambda self: self._device_id + getSource = lambda self: self._source + getFlags = lambda self: self._flags + getDisplayId = lambda self: self._display_id + + def isLongPress(self) -> bool: + return self._repeat_count > 0 + + def dispatch(self, receiver: View, on_receive: Callable) -> bool: + return on_receive(receiver, self) + + def isAltPressed(self) -> bool: + return bool(self._meta_state & KeyEvent.META_ALT_ON) + + def isShiftPressed(self) -> bool: + return bool(self._meta_state & KeyEvent.META_SHIFT_ON) + + def isCtrlPressed(self) -> bool: + return bool(self._meta_state & KeyEvent.META_CTRL_ON) + + def isMetaPressed(self) -> bool: + return bool(self._meta_state & KeyEvent.META_META_ON) + + def isSymPressed(self) -> bool: + return bool(self._meta_state & KeyEvent.META_SYM_ON) + + def isFunctionPressed(self) -> bool: + return bool(self._meta_state & KeyEvent.META_FUNCTION_ON) + + def isCapsLockOn(self) -> bool: + return bool(self._meta_state & KeyEvent.META_CAPS_LOCK_ON) + + def isNumLockOn(self) -> bool: + return bool(self._meta_state & KeyEvent.META_NUM_LOCK_ON) + + def isScrollLockOn(self) -> bool: + return bool(self._meta_state & KeyEvent.META_SCROLL_LOCK_ON) + + @staticmethod + def changeTimeRepeat(event: "KeyEvent", event_time: int, repeat_count: int) -> "KeyEvent": + return KeyEvent( + action=event._action, + key_code=event._key_code, + repeat_count=repeat_count, + event_time=event_time, + meta_state=event._meta_state, + ) + + @staticmethod + def changeAction(event: "KeyEvent", action: int) -> "KeyEvent": + return KeyEvent( + action=action, + key_code=event._key_code, + repeat_count=event._repeat_count, + event_time=event._event_time, + meta_state=event._meta_state, + ) + + +# ═══════════════════════════════════════════════════════════════ +# GestureDetector +# ═══════════════════════════════════════════════════════════════ + +class GestureDetector(Stub): + """GestureDetector — 手势检测器""" + + class SimpleOnGestureListener: + """SimpleOnGestureListener — 简单手势监听器""" + + def onDown(self, e: MotionEvent) -> bool: + return False + + def onShowPress(self, e: MotionEvent): + pass + + def onSingleTapUp(self, e: MotionEvent) -> bool: + return False + + def onScroll(self, e1: MotionEvent, e2: MotionEvent, + distance_x: float, distance_y: float) -> bool: + return False + + def onLongPress(self, e: MotionEvent): + pass + + def onFling(self, e1: MotionEvent, e2: MotionEvent, + velocity_x: float, velocity_y: float) -> bool: + return False + + def onSingleTapConfirmed(self, e: MotionEvent) -> bool: + return False + + def onDoubleTap(self, e: MotionEvent) -> bool: + return False + + def onDoubleTapEvent(self, e: MotionEvent) -> bool: + return False + + def onContextClick(self, e: MotionEvent) -> bool: + return False + + class OnGestureListener: + def onDown(self, e: MotionEvent) -> bool: + return False + def onShowPress(self, e: MotionEvent): + pass + def onSingleTapUp(self, e: MotionEvent) -> bool: + return False + def onScroll(self, e1: MotionEvent, e2: MotionEvent, + distance_x: float, distance_y: float) -> bool: + return False + def onLongPress(self, e: MotionEvent): + pass + def onFling(self, e1: MotionEvent, e2: MotionEvent, + velocity_x: float, velocity_y: float) -> bool: + return False + + class OnDoubleTapListener: + def onSingleTapConfirmed(self, e: MotionEvent) -> bool: + return False + def onDoubleTap(self, e: MotionEvent) -> bool: + return False + def onDoubleTapEvent(self, e: MotionEvent) -> bool: + return False + + def __init__(self, context=None, listener: Optional["GestureDetector.OnGestureListener"] = None, + handler: Any = None): + super().__init__() + self._listener = listener + self._double_tap_listener: Optional["GestureDetector.OnDoubleTapListener"] = None + self._is_longpress_enabled = True + self._context = context + self._current_down_event: Optional[MotionEvent] = None + self._previous_down_event: Optional[MotionEvent] = None + self._in_long_press = False + self._long_press_timeout = 500 + self._touch_slop = 8 + + def setOnDoubleTapListener(self, listener: Optional["GestureDetector.OnDoubleTapListener"]): + self._double_tap_listener = listener + + def setIsLongpressEnabled(self, enabled: bool): + self._is_longpress_enabled = enabled + + def isLongpressEnabled(self) -> bool: + return self._is_longpress_enabled + + def onTouchEvent(self, ev: MotionEvent) -> bool: + action = ev.getAction() + if action == MotionEvent.ACTION_DOWN: + self._in_long_press = False + self._current_down_event = ev + if self._listener: + return self._listener.onDown(ev) + elif action == MotionEvent.ACTION_MOVE: + pass + elif action == MotionEvent.ACTION_UP: + if self._listener: + return self._listener.onSingleTapUp(ev) + elif action == MotionEvent.ACTION_CANCEL: + pass + return False + + def setContextClickListener(self, listener: Callable): + pass + + def setContextClickable(self, context_clickable: bool): + pass + + def isContextClickable(self) -> bool: + return False + + +# ═══════════════════════════════════════════════════════════════ +# ScaleGestureDetector +# ═══════════════════════════════════════════════════════════════ + +class ScaleGestureDetector(Stub): + """ScaleGestureDetector — 缩放手势检测器""" + + class SimpleOnScaleGestureListener: + """SimpleOnScaleGestureListener — 简单缩放手势监听器""" + + def onScaleBegin(self, detector: "ScaleGestureDetector") -> bool: + return True + + def onScale(self, detector: "ScaleGestureDetector") -> bool: + return False + + def onScaleEnd(self, detector: "ScaleGestureDetector"): + pass + + class OnScaleGestureListener: + def onScaleBegin(self, detector: "ScaleGestureDetector") -> bool: + return True + def onScale(self, detector: "ScaleGestureDetector") -> bool: + return False + def onScaleEnd(self, detector: "ScaleGestureDetector"): + pass + + def __init__(self, context=None, listener: Optional[OnScaleGestureListener] = None): + super().__init__() + self._listener = listener + self._scale_factor = 1.0 + self._current_span = 0.0 + self._current_span_x = 0.0 + self._current_span_y = 0.0 + self._focus_x = 0.0 + self._focus_y = 0.0 + self._previous_span = 0.0 + self._time_delta = 0 + self._in_progress = False + + def getScaleFactor(self) -> float: + return self._scale_factor + + def getCurrentSpan(self) -> float: + return self._current_span + + def getCurrentSpanX(self) -> float: + return self._current_span_x + + def getCurrentSpanY(self) -> float: + return self._current_span_y + + def getPreviousSpan(self) -> float: + return self._previous_span + + def getPreviousSpanX(self) -> float: + return self._current_span_x + + def getPreviousSpanY(self) -> float: + return self._current_span_y + + def getFocusX(self) -> float: + return self._focus_x + + def getFocusY(self) -> float: + return self._focus_y + + def getTimeDelta(self) -> int: + return self._time_delta + + def getEventTime(self) -> int: + return int(time.time() * 1000) + + def isInProgress(self) -> bool: + return self._in_progress + + def onTouchEvent(self, event: MotionEvent) -> bool: + action = event.getActionMasked() + if action == MotionEvent.ACTION_DOWN: + self._in_progress = False + elif action == MotionEvent.ACTION_POINTER_DOWN: + if event.getPointerCount() >= 2: + self._in_progress = True + x1, y1 = event.getX(0), event.getY(0) + x2, y2 = event.getX(1), event.getY(1) + self._current_span = math.hypot(x2 - x1, y2 - y1) + self._focus_x = (x1 + x2) / 2.0 + self._focus_y = (y1 + y2) / 2.0 + if self._listener: + self._listener.onScaleBegin(self) + return True + elif action == MotionEvent.ACTION_MOVE: + if self._in_progress and event.getPointerCount() >= 2: + x1, y1 = event.getX(0), event.getY(0) + x2, y2 = event.getX(1), event.getY(1) + new_span = math.hypot(x2 - x1, y2 - y1) + self._previous_span = self._current_span + self._current_span = new_span + if self._previous_span > 0: + self._scale_factor = self._current_span / self._previous_span + self._focus_x = (x1 + x2) / 2.0 + self._focus_y = (y1 + y2) / 2.0 + if self._listener: + self._listener.onScale(self) + return True + elif action in (MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL): + if self._in_progress and self._listener: + self._listener.onScaleEnd(self) + self._in_progress = False + return False + + +# ═══════════════════════════════════════════════════════════════ +# VelocityTracker +# ═══════════════════════════════════════════════════════════════ + +class VelocityTracker(Stub): + """VelocityTracker — 速度追踪器""" + + def __init__(self): + super().__init__() + self._movements: List[Tuple[float, float, float]] = [] # (x, y, time) + self._x_velocity: float = 0.0 + self._y_velocity: float = 0.0 + + @staticmethod + def obtain() -> "VelocityTracker": + return VelocityTracker() + + @staticmethod + def obtainUnit(max_units: int, generator_id: int) -> "VelocityTracker": + return VelocityTracker() + + def addMovement(self, event: MotionEvent): + self._movements.append((event.getRawX(), event.getRawY(), + event.getEventTime() / 1000.0)) + if len(self._movements) > 100: + self._movements = self._movements[-50:] + + def computeCurrentVelocity(self, units: int, max_velocity: float = float("inf")): + if len(self._movements) < 2: + self._x_velocity = 0.0 + self._y_velocity = 0.0 + return + recent = self._movements[-4:] if len(self._movements) >= 4 else self._movements + first_x, first_y, first_t = recent[0] + last_x, last_y, last_t = recent[-1] + dt = last_t - first_t + if dt > 0: + unit_scale = units / 1000.0 + self._x_velocity = (last_x - first_x) / dt * unit_scale + self._y_velocity = (last_y - first_y) / dt * unit_scale + self._x_velocity = max(-max_velocity, min(max_velocity, self._x_velocity)) + self._y_velocity = max(-max_velocity, min(max_velocity, self._y_velocity)) + else: + self._x_velocity = 0.0 + self._y_velocity = 0.0 + + def getXVelocity(self, id_: int = -1) -> float: + return self._x_velocity + + def getYVelocity(self, id_: int = -1) -> float: + return self._y_velocity + + def recycle(self): + self._movements.clear() + self._x_velocity = 0.0 + self._y_velocity = 0.0 + + def clear(self): + self._movements.clear() + + def getEstimator(self) -> Any: + return None + + +# ═══════════════════════════════════════════════════════════════ +# DragEvent +# ═══════════════════════════════════════════════════════════════ + +class DragEvent(Stub): + """DragEvent — 拖放事件""" + + ACTION_DRAG_STARTED = 1 + ACTION_DRAG_LOCATION = 2 + ACTION_DROP = 3 + ACTION_DRAG_ENTERED = 4 + ACTION_DRAG_EXITED = 5 + ACTION_DRAG_ENDED = 6 + + def __init__(self, action: int = 0, x: float = 0.0, y: float = 0.0, + clip_data: Any = None, result: bool = False): + super().__init__() + self._action = action + self._x = x + self._y = y + self._clip_data = clip_data + self._result = result + self._local_state: Any = None + + def getAction(self) -> int: + return self._action + + def getX(self) -> float: + return self._x + + def getY(self) -> float: + return self._y + + def getClipData(self) -> Any: + return self._clip_data + + def getResult(self) -> bool: + return self._result + + def getLocalState(self) -> Any: + return self._local_state + + def setLocalState(self, state: Any): + self._local_state = state + + def toString(self) -> str: + action_names = { + 1: "ACTION_DRAG_STARTED", + 2: "ACTION_DRAG_LOCATION", + 3: "ACTION_DROP", + 4: "ACTION_DRAG_ENTERED", + 5: "ACTION_DRAG_EXITED", + 6: "ACTION_DRAG_ENDED", + } + name = action_names.get(self._action, f"UNKNOWN({self._action})") + return f"DragEvent(action={name}, x={self._x}, y={self._y})" + + +# ═══════════════════════════════════════════════════════════════ +# AccessibilityDelegate +# ═══════════════════════════════════════════════════════════════ + +class AccessibilityDelegate(Stub): + """AccessibilityDelegate — 辅助功能委托""" + + def sendAccessibilityEvent(self, host: View, event_type: int): + pass + + def sendAccessibilityEventUnchecked(self, host: View, event: Any): + pass + + def dispatchPopulateAccessibilityEvent(self, host: View, event: Any) -> bool: + return False + + def onPopulateAccessibilityEvent(self, host: View, event: Any): + pass + + def onInitializeAccessibilityEvent(self, host: View, event: Any): + pass + + def onInitializeAccessibilityNodeInfo(self, host: View, info: Any): + pass + + def onRequestSendAccessibilityEvent(self, host: ViewGroup, child: View, event: Any) -> bool: + return True + + def getAccessibilityNodeProvider(self, host: View) -> Optional[Any]: + return None + + def performAccessibilityAction(self, host: View, action: int, arguments: Any) -> bool: + return False + + +# ═══════════════════════════════════════════════════════════════ +# ViewTreeObserver +# ═══════════════════════════════════════════════════════════════ + +class ViewTreeObserver(Stub): + """ViewTreeObserver — 视图树观察者""" + + class OnGlobalLayoutListener: + def onGlobalLayout(self): + pass + + class OnGlobalFocusChangeListener: + def onGlobalFocusChanged(self, old_focus: View, new_focus: View): + pass + + class OnScrollChangedListener: + def onScrollChanged(self): + pass + + class OnTouchModeChangeListener: + def onTouchModeChanged(self, is_in_touch_mode: bool): + pass + + class OnWindowAttachListener: + def onWindowAttached(self): + pass + def onWindowDetached(self): + pass + + class OnWindowFocusChangeListener: + def onWindowFocusChanged(self, has_focus: bool): + pass + + class OnDrawListener: + def onDraw(self): + pass + + class OnPreDrawListener: + def onPreDraw(self) -> bool: + return True + + class OnWindowShownListener: + def onWindowShown(self): + pass + + def __init__(self): + super().__init__() + self._global_layout_listeners: List[ViewTreeObserver.OnGlobalLayoutListener] = [] + self._scroll_changed_listeners: List[ViewTreeObserver.OnScrollChangedListener] = [] + self._pre_draw_listeners: List[ViewTreeObserver.OnPreDrawListener] = [] + self._draw_listeners: List[ViewTreeObserver.OnDrawListener] = [] + self._touch_mode_listeners: List[ViewTreeObserver.OnTouchModeChangeListener] = [] + self._window_focus_listeners: List[ViewTreeObserver.OnWindowFocusChangeListener] = [] + self._window_attach_listeners: List[ViewTreeObserver.OnWindowAttachListener] = [] + self._alive = True + + def addOnGlobalLayoutListener(self, listener: OnGlobalLayoutListener): + self._global_layout_listeners.append(listener) + + def removeOnGlobalLayoutListener(self, listener: OnGlobalLayoutListener): + if listener in self._global_layout_listeners: + self._global_layout_listeners.remove(listener) + + def addOnScrollChangedListener(self, listener: OnScrollChangedListener): + self._scroll_changed_listeners.append(listener) + + def removeOnScrollChangedListener(self, listener: OnScrollChangedListener): + if listener in self._scroll_changed_listeners: + self._scroll_changed_listeners.remove(listener) + + def addOnPreDrawListener(self, listener: OnPreDrawListener): + self._pre_draw_listeners.append(listener) + + def removeOnPreDrawListener(self, listener: OnPreDrawListener): + if listener in self._pre_draw_listeners: + self._pre_draw_listeners.remove(listener) + + def addOnDrawListener(self, listener: OnDrawListener): + self._draw_listeners.append(listener) + + def removeOnDrawListener(self, listener: OnDrawListener): + if listener in self._draw_listeners: + self._draw_listeners.remove(listener) + + def addOnGlobalFocusChangeListener(self, listener: OnGlobalFocusChangeListener): + pass + + def removeOnGlobalFocusChangeListener(self, listener: OnGlobalFocusChangeListener): + pass + + def addOnTouchModeChangeListener(self, listener: OnTouchModeChangeListener): + self._touch_mode_listeners.append(listener) + + def removeOnTouchModeChangeListener(self, listener: OnTouchModeChangeListener): + if listener in self._touch_mode_listeners: + self._touch_mode_listeners.remove(listener) + + def addOnWindowAttachListener(self, listener: OnWindowAttachListener): + self._window_attach_listeners.append(listener) + + def removeOnWindowAttachListener(self, listener: OnWindowAttachListener): + if listener in self._window_attach_listeners: + self._window_attach_listeners.remove(listener) + + def addOnWindowFocusChangeListener(self, listener: OnWindowFocusChangeListener): + self._window_focus_listeners.append(listener) + + def removeOnWindowFocusChangeListener(self, listener: OnWindowFocusChangeListener): + if listener in self._window_focus_listeners: + self._window_focus_listeners.remove(listener) + + def addOnWindowShownListener(self, listener: OnWindowShownListener): + pass + + def removeOnWindowShownListener(self, listener: OnWindowShownListener): + pass + + def dispatchOnGlobalLayout(self): + for listener in list(self._global_layout_listeners): + listener.onGlobalLayout() + + def dispatchOnScrollChanged(self): + for listener in list(self._scroll_changed_listeners): + listener.onScrollChanged() + + def isAlive(self) -> bool: + return self._alive + + def kill(self): + self._alive = False + self._global_layout_listeners.clear() + self._scroll_changed_listeners.clear() + self._pre_draw_listeners.clear() + self._draw_listeners.clear() + + +# ═══════════════════════════════════════════════════════════════ +# ViewConfiguration +# ═══════════════════════════════════════════════════════════════ + +class ViewConfiguration(Stub): + """ViewConfiguration — 视图配置常量""" + + def __init__(self, context=None): + super().__init__() + if context is None: + context = android_context + self._context = context + + @staticmethod + def get(context=None) -> "ViewConfiguration": + return ViewConfiguration(context) + + @staticmethod + def getLongPressTimeout() -> int: + return 500 + + @staticmethod + def getTapTimeout() -> int: + return 100 + + @staticmethod + def getJumpTapTimeout() -> int: + return 500 + + @staticmethod + def getDoubleTapTimeout() -> int: + return 300 + + @staticmethod + def getEdgeSlop() -> int: + return 12 + + @staticmethod + def getTouchSlop() -> int: + return 8 + + @staticmethod + def getScaledTouchSlop(self) -> int: + return 8 + + @staticmethod + def getScaledEdgeSlop(self) -> int: + return 12 + + @staticmethod + def getScaledFadingEdgeLength(self) -> int: + return 12 + + @staticmethod + def getScaledMinimumFlingVelocity(self) -> int: + return 50 + + @staticmethod + def getScaledMaximumFlingVelocity(self) -> int: + return 8000 + + @staticmethod + def getScaledPagingTouchSlop(self) -> int: + return 16 + + @staticmethod + def getScaledOverflingDistance(self) -> int: + return 32 + + @staticmethod + def getScaledOverscrollDistance(self) -> int: + return 32 + + @staticmethod + def getScaledScrollFactor(self) -> int: + return 64 + + @staticmethod + def getScrollFriction() -> float: + return 0.015 + + @staticmethod + def getMaximumDrawingCacheSize() -> int: + return 480 * 800 * 4 + + @staticmethod + def getWindowTouchSlop() -> int: + return 16 + + @staticmethod + def getGlobalActionKeySlop() -> int: + return 12 + + def getScaledWindowTouchSlop(self) -> int: + return 16 + + +# ═══════════════════════════════════════════════════════════════ +# SurfaceView +# ═══════════════════════════════════════════════════════════════ + +class SurfaceView(View): + """SurfaceView — 表面视图,用于直接绘制""" + + def __init__(self, context=None): + super().__init__(context) + self._surface = Surface() + self._surface_holder = SurfaceHolder() + + def getHolder(self) -> "SurfaceHolder": + return self._surface_holder + + def getSurfaceControl(self) -> Any: + return None + + def setVisibility(self, visibility: int): + super().setVisibility(visibility) + + def setSecure(self, is_secure: bool): + pass + + def setZOrderMediaOverlay(self, is_media_overlay: bool): + pass + + def setZOrderOnTop(self, on_top: bool): + pass + + def gatherTransparentRegion(self, region: Any) -> bool: + return False + + def draw(self, canvas: Any): + pass + + +class SurfaceHolder(Stub): + """SurfaceHolder — Surface 持有者""" + + class Callback: + def surfaceCreated(self, holder: "SurfaceHolder"): + pass + def surfaceChanged(self, holder: "SurfaceHolder", format: int, width: int, height: int): + pass + def surfaceDestroyed(self, holder: "SurfaceHolder"): + pass + + class Callback2(Callback): + def surfaceRedrawNeeded(self, holder: "SurfaceHolder"): + pass + + SURFACE_TYPE_NORMAL = 0 + SURFACE_TYPE_HARDWARE = 1 + SURFACE_TYPE_GPU = 2 + SURFACE_TYPE_PUSH_BUFFERS = 3 + + def __init__(self): + super().__init__() + self._surface = Surface() + self._callbacks: List[SurfaceHolder.Callback] = [] + self._type = SurfaceHolder.SURFACE_TYPE_NORMAL + self._fixed_size: Optional[Tuple[int, int]] = None + self._format = -1 + + def getSurface(self) -> "Surface": + return self._surface + + def addCallback(self, callback: Callback): + self._callbacks.append(callback) + + def removeCallback(self, callback: Callback): + if callback in self._callbacks: + self._callbacks.remove(callback) + + def isCreating(self) -> bool: + return False + + def setType(self, type_: int): + self._type = type_ + + def setFixedSize(self, width: int, height: int): + self._fixed_size = (width, height) + + def setSizeFromLayout(self): + self._fixed_size = None + + def setFormat(self, format: int): + self._format = format + + def lockCanvas(self) -> Any: + return None + + def lockCanvas(self, dirty: Any) -> Any: + return None + + def unlockCanvasAndPost(self, canvas: Any): + pass + + +# ═══════════════════════════════════════════════════════════════ +# TextureView +# ═══════════════════════════════════════════════════════════════ + +class TextureView(View): + """TextureView — 纹理视图""" + + def __init__(self, context=None): + super().__init__(context) + self._surface_texture = SurfaceTexture() + self._layer = None + self._update_listener: Optional[Callable] = None + self._surface_texture_listener: Optional[Callable] = None + + class SurfaceTextureListener: + def onSurfaceTextureAvailable(self, surface: "SurfaceTexture", width: int, height: int): + pass + def onSurfaceTextureSizeChanged(self, surface: "SurfaceTexture", width: int, height: int): + pass + def onSurfaceTextureDestroyed(self, surface: "SurfaceTexture") -> bool: + return True + def onSurfaceTextureUpdated(self, surface: "SurfaceTexture"): + pass + + def isOpaque(self) -> bool: + return False + + def setOpaque(self, opaque: bool): + pass + + def getSurfaceTexture(self) -> "SurfaceTexture": + return self._surface_texture + + def setSurfaceTexture(self, surface_texture: "SurfaceTexture"): + self._surface_texture = surface_texture + + def setSurfaceTextureListener(self, listener: SurfaceTextureListener): + self._surface_texture_listener = listener + + def getSurfaceTextureListener(self) -> Optional[SurfaceTextureListener]: + return self._surface_texture_listener + + def buildLayer(self): + pass + + def setLayerPaint(self, paint: Any): + pass + + def getLayerPaint(self) -> Any: + return None + + def setTransform(self, transform: Any): + pass + + def getTransform(self, transform: Any) -> Any: + return transform + + def draw(self, canvas: Any): + pass + + def lockCanvas(self) -> Any: + return None + + def unlockCanvasAndPost(self, canvas: Any): + pass + + +class SurfaceTexture(Stub): + """SurfaceTexture — 表面纹理""" + + def __init__(self, tex_name: int = 0, allow_legacy: bool = True): + super().__init__() + self._tex_name = tex_name + self._timestamp = 0 + self._frame_available_listener: Optional[Callable] = None + self._default_buffer_size: Optional[Tuple[int, int]] = None + + def setOnFrameAvailableListener(self, listener: Callable, handler: Any = None): + self._frame_available_listener = listener + + def getTimestamp(self) -> int: + return self._timestamp + + def updateTexImage(self): + pass + + def release(self): + pass + + def setDefaultBufferSize(self, width: int, height: int): + self._default_buffer_size = (width, height) + + def getTransformMatrix(self, mtx: List[float]): + for i in range(16): + mtx[i] = 1.0 if i % 5 == 0 else 0.0 + + +# ═══════════════════════════════════════════════════════════════ +# Window +# ═══════════════════════════════════════════════════════════════ + +class Window(Stub): + """Window — 抽象窗口基类""" + + # 特性ID + FEATURES_DEFAULT = -1 + FEATURE_OPTIONS_PANEL = 0 + FEATURE_NO_TITLE = 1 + FEATURE_PROGRESS = 2 + FEATURE_LEFT_ICON = 3 + FEATURE_RIGHT_ICON = 4 + FEATURE_INDETERMINATE_PROGRESS = 5 + FEATURE_CONTEXT_MENU = 6 + FEATURE_CUSTOM_TITLE = 7 + FEATURE_ACTION_BAR = 8 + FEATURE_ACTION_BAR_OVERLAY = 9 + FEATURE_ACTION_MODE_OVERLAY = 10 + FEATURE_SWIPE_TO_DISMISS = 11 + FEATURE_CONTENT_TRANSITIONS = 12 + FEATURE_WATCH = 13 + + # 本地特性 + ID_ANDROID_CONTENT = 0x01020002 + + DECOR_CAPTION_SHORTCUT_DURATION_MS = 1000 + + # 标志 + NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME = "navigationBarBackground" + STATUS_BAR_BACKGROUND_TRANSITION_NAME = "statusBarBackground" + + def __init__(self, context=None): + super().__init__() + if context is None: + context = android_context + self._context = context + self._features: Dict[int, bool] = {} + self._title: str = "" + self._decor_view: Optional[View] = None + self._callback: Optional["Window.Callback"] = None + self._window_manager: Optional[WindowManager] = None + self._soft_input_mode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED + self._flags: Dict[int, bool] = {} + self._container: Optional[Window] = None + self._local_features: List[int] = [] + + class Callback: + def dispatchKeyEvent(self, event: KeyEvent) -> bool: + return False + def dispatchTouchEvent(self, event: MotionEvent) -> bool: + return False + def dispatchTrackballEvent(self, event: MotionEvent) -> bool: + return False + def dispatchGenericMotionEvent(self, event: MotionEvent) -> bool: + return False + def dispatchPopulateAccessibilityEvent(self, event: Any) -> bool: + return False + def onCreatePanelView(self, feature_id: int) -> Optional[View]: + return None + def onCreatePanelMenu(self, feature_id: int, menu: Any) -> bool: + return False + def onPreparePanel(self, feature_id: int, view: View, menu: Any) -> bool: + return False + def onMenuOpened(self, feature_id: int, menu: Any): + pass + def onMenuItemSelected(self, feature_id: int, item: Any) -> bool: + return False + def onWindowAttributesChanged(self, attrs: Any): + pass + def onContentChanged(self): + pass + def onWindowFocusChanged(self, has_focus: bool): + pass + def onAttachedToWindow(self): + pass + def onDetachedFromWindow(self): + pass + def onPanelClosed(self, feature_id: int, menu: Any): + pass + def onSearchRequested(self) -> bool: + return False + def onSearchRequested(self, search_event: Any) -> bool: + return False + def onActionModeStarted(self, mode: Any): + pass + def onActionModeFinished(self, mode: Any): + pass + def onWindowStartingActionMode(self, callback: Any) -> Optional[Any]: + return None + def onWindowStartingActionMode(self, callback: Any, type_: int) -> Optional[Any]: + return None + + def setContentView(self, layout_res_id: int): + pass + + def setContentView(self, view: View): + pass + + def setContentView(self, view: View, params: ViewGroup.LayoutParams): + pass + + def addContentView(self, view: View, params: ViewGroup.LayoutParams): + pass + + def setTitle(self, title: str): + self._title = title + + def getTitle(self) -> str: + return self._title + + def setFeatureInt(self, feature_id: int, value: int): + self._features[feature_id] = True + + def requestFeature(self, feature_id: int) -> bool: + self._features[feature_id] = True + return True + + def hasFeature(self, feature_id: int) -> bool: + return self._features.get(feature_id, False) + + def setFlags(self, flags: int, mask: int): + for flag_bit in range(32): + if mask & (1 << flag_bit): + self._flags[1 << flag_bit] = bool(flags & (1 << flag_bit)) + + def addFlags(self, flags: int): + for flag_bit in range(32): + if flags & (1 << flag_bit): + self._flags[1 << flag_bit] = True + + def clearFlags(self, flags: int): + for flag_bit in range(32): + if flags & (1 << flag_bit): + self._flags[1 << flag_bit] = False + + def setSoftInputMode(self, soft_input_mode: int): + self._soft_input_mode = soft_input_mode + + def getSoftInputMode(self) -> int: + return self._soft_input_mode + + def setLayout(self, width: int, height: int): + pass + + def findViewById(self, id_: int) -> Optional[View]: + if self._decor_view: + return self._decor_view.findViewById(id_) + return None + + def getDecorView(self) -> Optional[View]: + return self._decor_view + + def setDecorView(self, view: View): + self._decor_view = view + + def getWindowManager(self) -> Optional[WindowManager]: + return self._window_manager + + def setWindowManager(self, wm: WindowManager, app_token: Any = None, app_name: str = ""): + self._window_manager = wm + + def setCallback(self, callback: Callback): + self._callback = callback + + def getCallback(self) -> Optional[Callback]: + return self._callback + + def superDispatchTouchEvent(self, event: MotionEvent) -> bool: + if self._decor_view: + return self._decor_view.dispatchTouchEvent(event) + return False + + def superDispatchKeyEvent(self, event: KeyEvent) -> bool: + if self._decor_view: + return self._decor_view.dispatchKeyEvent(event) + return False + + def superDispatchTrackballEvent(self, event: MotionEvent) -> bool: + return False + + def superDispatchGenericMotionEvent(self, event: MotionEvent) -> bool: + return False + + def takeSurface(self, callback: Any): + pass + + def takeInputQueue(self, callback: Any): + pass + + def isActive(self) -> bool: + return True + + def isFloating(self) -> bool: + return False + + def setBackgroundDrawable(self, drawable: Any): + pass + + def setBackgroundDrawableResource(self, res_id: int): + pass + + def setDimAmount(self, amount: float): + pass + + def getLayoutInflater(self): + return None + + def setWindowAnimations(self, res_id: int): + pass + + def getWindowStyle(self) -> Any: + return None + + def setContainer(self, container: "Window"): + self._container = container + + def getContainer(self) -> Optional["Window"]: + return self._container + + def setLocalFeatures(self, local_features: int): + pass + + def makeActive(self): + pass + + def getContext(self): + return self._context + + def getAttributes(self) -> WindowManager.LayoutParams: + params = WindowManager.LayoutParams() + params.softInputMode = self._soft_input_mode + return params + + def setAttributes(self, attrs: WindowManager.LayoutParams): + pass + + def reconfigure(self, attrs: WindowManager.LayoutParams): + pass + + def onConfigurationChanged(self, new_config: Any): + pass + + def getCurrentFocus(self) -> Optional[View]: + return None + + def getVolumeControlStream(self) -> int: + return -1 + + def setVolumeControlStream(self, stream_type: int): + pass + + def getMediaController(self) -> Any: + return None + + def setMediaController(self, controller: Any): + pass + + def openGlEsVersion(self) -> int: + return 2 + + def isShortcutKey(self, key_code: int, event: KeyEvent) -> bool: + return False + + def setStatusBarColor(self, color: int): + pass + + def setNavigationBarColor(self, color: int): + pass + + def setStatusBarVisibility(self, visible: bool): + pass + + def setNavigationBarVisibility(self, visible: bool): + pass + + def getStatusBarColor(self) -> int: + return 0 + + def getNavigationBarColor(self) -> int: + return 0 + + +# ═══════════════════════════════════════════════════════════════ +# WindowInsets +# ═══════════════════════════════════════════════════════════════ + +class WindowInsets(Stub): + """WindowInsets — 窗口插入区域""" + + def __init__(self, left: int = 0, top: int = 0, right: int = 0, bottom: int = 0): + super().__init__() + self._system_window_insets = Rect(left, top, right, bottom) + self._stable_insets = Rect(0, 0, 0, 0) + self._display_cutout_insets = Rect(0, 0, 0, 0) + self._consumed = False + + def getSystemWindowInsetLeft(self) -> int: + return self._system_window_insets.left + + def getSystemWindowInsetTop(self) -> int: + return self._system_window_insets.top + + def getSystemWindowInsetRight(self) -> int: + return self._system_window_insets.right + + def getSystemWindowInsetBottom(self) -> int: + return self._system_window_insets.bottom + + def getSystemWindowInsets(self) -> Rect: + return self._system_window_insets + + def getStableInsetTop(self) -> int: + return self._stable_insets.top + + def getStableInsetLeft(self) -> int: + return self._stable_insets.left + + def getStableInsetRight(self) -> int: + return self._stable_insets.right + + def getStableInsetBottom(self) -> int: + return self._stable_insets.bottom + + def getDisplayCutoutInsets(self) -> Rect: + return self._display_cutout_insets + + def consumeDisplayCutout(self) -> "WindowInsets": + return WindowInsets() + + def consumeSystemWindowInsets(self) -> "WindowInsets": + return WindowInsets() + + def replaceSystemWindowInsets(self, left: int, top: int, right: int, bottom: int) -> "WindowInsets": + new_ins = WindowInsets(left, top, right, bottom) + new_ins._stable_insets = self._stable_insets + return new_ins + + def isConsumed(self) -> bool: + return self._consumed + + def hasSystemWindowInsets(self) -> bool: + return (self._system_window_insets.left > 0 or self._system_window_insets.top > 0 or + self._system_window_insets.right > 0 or self._system_window_insets.bottom > 0) + + def hasInsets(self) -> bool: + return self.hasSystemWindowInsets() + + def isRound(self) -> bool: + return False + + def getDisplayCutout(self) -> Any: + return None + + def consumeStableInsets(self) -> "WindowInsets": + return WindowInsets() + + def toString(self) -> str: + return f"WindowInsets(system={self._system_window_insets})" + + +# ═══════════════════════════════════════════════════════════════ +# WindowInsetsController +# ═══════════════════════════════════════════════════════════════ + +class WindowInsetsController(Stub): + """WindowInsetsController — 窗口插入控制器""" + + BEHAVIOR_SHOW_BARS_BY_TOUCH = 0 + BEHAVIOR_SHOW_BARS_BY_SWIPE = 1 + BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE = 2 + + APPEARANCE_OPAQUE_STATUS_BARS = 0x00000001 + APPEARANCE_OPAQUE_NAVIGATION_BARS = 0x00000002 + APPEARANCE_LOW_PROFILE_BARS = 0x00000004 + APPEARANCE_LIGHT_STATUS_BARS = 0x00000008 + APPEARANCE_LIGHT_NAVIGATION_BARS = 0x00000010 + + def __init__(self): + super().__init__() + self._appearance = 0 + self._behavior = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_TOUCH + + def show(self, types: int): + pass + + def hide(self, types: int): + pass + + def setSystemBarsAppearance(self, appearance: int, mask: int): + self._appearance = (self._appearance & ~mask) | (appearance & mask) + + def getSystemBarsAppearance(self) -> int: + return self._appearance + + def setSystemBarsBehavior(self, behavior: int): + self._behavior = behavior + + def getSystemBarsBehavior(self) -> int: + return self._behavior + + def setAnimationsDisabled(self, disabled: bool): + pass + + +# ═══════════════════════════════════════════════════════════════ +# Display +# ═══════════════════════════════════════════════════════════════ + +class Display(Stub): + """Display — 显示信息""" + + DEFAULT_DISPLAY = 0 + + ROTATION_0 = 0 + ROTATION_90 = 1 + ROTATION_180 = 2 + ROTATION_270 = 3 + + FLAG_PRESENTATION = 0x00000008 + FLAG_PRIVATE = 0x00000004 + FLAG_SECURE = 0x00000002 + FLAG_SUPPORTS_PROTECTED_BUFFERS = 0x00000001 + STATE_DOZE = 3 + STATE_DOZE_SUSPEND = 4 + STATE_OFF = 1 + STATE_ON = 2 + STATE_UNKNOWN = 0 + + def __init__(self, display_id: int = DEFAULT_DISPLAY): + super().__init__() + self._display_id = display_id + self._width = 1080 + self._height = 1920 + self._rotation = Display.ROTATION_0 + self._refresh_rate = 60.0 + self._density = 2.0 + self._density_dpi = 320 + self._state = Display.STATE_ON + self._flags = 0 + self._name = "Built-in Screen" + + def getDisplayId(self) -> int: + return self._display_id + + def getWidth(self) -> int: + return self._width + + def getHeight(self) -> int: + return self._height + + def getRotation(self) -> int: + return self._rotation + + def getRefreshRate(self) -> float: + return self._refresh_rate + + def getName(self) -> str: + return self._name + + def getState(self) -> int: + return self._state + + def getFlags(self) -> int: + return self._flags + + def isHdr(self) -> bool: + return False + + def isWideColorGamut(self) -> bool: + return False + + def getSize(self, out_size: Any = None): + if out_size is not None: + out_size.width = self._width + out_size.height = self._height + return None + + def getRealSize(self, out_size: Any = None): + if out_size is not None: + out_size.width = self._width + out_size.height = self._height + return None + + def getMetrics(self, metrics: Any = None): + if metrics is not None: + metrics.widthPixels = self._width + metrics.heightPixels = self._height + metrics.density = self._density + metrics.densityDpi = self._density_dpi + metrics.scaledDensity = self._density + metrics.xdpi = 320.0 + metrics.ydpi = 320.0 + return None + + def getRealMetrics(self, metrics: Any = None): + return self.getMetrics(metrics) + + def getOrientation(self) -> int: + return self._rotation + + def getDisplayAdjustments(self) -> Any: + return None + + def getPixelFormat(self) -> int: + return 1 # RGBA_8888 + + def isValid(self) -> bool: + return True + + def toString(self) -> str: + return f"Display({self._display_id}, {self._width}x{self._height})" + + +# ═══════════════════════════════════════════════════════════════ +# Surface +# ═══════════════════════════════════════════════════════════════ + +class Surface(Stub): + """Surface — 表面,用于绘制""" + + ROTATION_0 = 0 + ROTATION_90 = 1 + ROTATION_180 = 2 + ROTATION_270 = 3 + + CHANGE_FRAME_RATE_ALWAYS = 1 + CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS = 0 + + def __init__(self, surface_control: Any = None): + super().__init__() + self._is_valid = True + self._is_locked = False + self._width = 1080 + self._height = 1920 + self._hardware_accelerated = False + + def isValid(self) -> bool: + return self._is_valid + + def isConsumerRunningBehind(self) -> bool: + return False + + def lockCanvas(self, dirty_rect: Any = None) -> Any: + if not self._is_valid: + return None + self._is_locked = True + return None # 返回空 Canvas + + def lockHardwareCanvas(self) -> Any: + if not self._is_valid: + return None + self._is_locked = True + self._hardware_accelerated = True + return None + + def unlockCanvasAndPost(self, canvas: Any): + self._is_locked = False + + def getSurfaceControl(self) -> Any: + return None + + def release(self): + self._is_valid = False + self._is_locked = False + + def destroy(self): + self.release() + + def getWidth(self) -> int: + return self._width + + def getHeight(self) -> int: + return self._height + + def setFrameRate(self, frame_rate: float, compatibility: int): + pass + + def toString(self) -> str: + return f"Surface(valid={self._is_valid}, {self._width}x{self._height})" + + +# ═══════════════════════════════════════════════════════════════ +# Choreographer +# ═══════════════════════════════════════════════════════════════ + +class Choreographer(Stub): + """Choreographer — 帧渲染协调器""" + + CALLBACK_INPUT = 0 + CALLBACK_ANIMATION = 1 + CALLBACK_INSETS_ANIMATION = 2 + CALLBACK_TRAVERSAL = 3 + CALLBACK_COMMIT = 4 + + _instance: Optional["Choreographer"] = None + + class FrameCallback: + def doFrame(self, frame_time_nanos: int): + pass + + class FrameTimeline: + pass + + def __init__(self): + super().__init__() + + @staticmethod + def getInstance() -> "Choreographer": + if Choreographer._instance is None: + Choreographer._instance = Choreographer() + return Choreographer._instance + + def postFrameCallback(self, callback: "FrameCallback"): + callback.doFrame(int(time.time() * 1_000_000_000)) + + def postFrameCallbackDelayed(self, callback: "FrameCallback", delay_millis: int): + callback.doFrame(int((time.time() + delay_millis / 1000.0) * 1_000_000_000)) + + def removeFrameCallback(self, callback: "FrameCallback"): + pass + + def postCallback(self, callback_type: int, action: Callable, token: Any): + action() + + def postCallbackDelayed(self, callback_type: int, action: Callable, token: Any, delay_millis: int): + import threading + timer = threading.Timer(delay_millis / 1000.0, action) + timer.daemon = True + timer.start() + + def removeCallbacks(self, callback_type: int, action: Callable, token: Any): + pass + + def getFrameTimeNanos(self) -> int: + return int(time.time() * 1_000_000_000) + + def getLastFrameTimeNanos(self) -> int: + return int(time.time() * 1_000_000_000) - 16_666_666 + + +# ═══════════════════════════════════════════════════════════════ +# ActionMode +# ═══════════════════════════════════════════════════════════════ + +class ActionMode(Stub): + """ActionMode — 操作模式""" + + TYPE_FLOATING = 1 + TYPE_PRIMARY = 0 + TYPE_TOOLBAR = 2 + + class Callback: + def onCreateActionMode(self, mode: "ActionMode", menu: Any) -> bool: + return False + def onPrepareActionMode(self, mode: "ActionMode", menu: Any) -> bool: + return False + def onActionItemClicked(self, mode: "ActionMode", item: Any) -> bool: + return False + def onDestroyActionMode(self, mode: "ActionMode"): + pass + + class Callback2(Callback): + def onGetContentRect(self, mode: "ActionMode", view: View, out_rect: Any): + pass + + def __init__(self): + super().__init__() + self._tag: Any = None + self._title: str = "" + self._subtitle: str = "" + self._title_optional_hint: str = "" + self._subtitle_optional_hint: str = "" + self._type = ActionMode.TYPE_PRIMARY + self._menu: Any = None + + def setTitle(self, title: str): + self._title = title + + def getTitle(self) -> str: + return self._title + + def setSubtitle(self, subtitle: str): + self._subtitle = subtitle + + def getSubtitle(self) -> str: + return self._subtitle + + def setTitleOptionalHint(self, hint: bool): + self._title_optional_hint = hint + + def getTitleOptionalHint(self) -> bool: + return bool(self._title_optional_hint) + + def setTag(self, tag: Any): + self._tag = tag + + def getTag(self) -> Any: + return self._tag + + def getMenu(self): + return self._menu + + def setType(self, type_: int): + self._type = type_ + + def getType(self) -> int: + return self._type + + def invalidate(self): + pass + + def invalidateContentRect(self): + pass + + def finish(self): + pass + + def getCustomView(self) -> Optional[View]: + return None + + def setCustomView(self, view: View): + pass + + def setImageDrawable(self, drawable: Any): + pass + + def getMenuInflater(self): + return None + + +# ═══════════════════════════════════════════════════════════════ +# ContextThemeWrapper +# ═══════════════════════════════════════════════════════════════ + +class ContextThemeWrapper(Stub): + """ContextThemeWrapper — 上下文主题包装器""" + + def __init__(self, base: Any = None, theme_res_id: int = 0): + super().__init__() + self._base = base if base is not None else android_context + self._theme_res_id = theme_res_id + self._theme: Any = None + + def attachBaseContext(self, new_base: Any): + self._base = new_base + + def getBaseContext(self): + return self._base + + def setTheme(self, resid: int): + self._theme_res_id = resid + + def getThemeResId(self) -> int: + return self._theme_res_id + + def getTheme(self) -> Any: + return self._theme + + def getResources(self): + if hasattr(self._base, "getResources"): + return self._base.getResources() + return None + + def getSystemService(self, name: str) -> Any: + if hasattr(self._base, "getSystemService"): + return self._base.getSystemService(name) + return None + + def getPackageName(self) -> str: + if hasattr(self._base, "getPackageName"): + return self._base.getPackageName() + return "" + + def getApplicationContext(self): + return self._base + + +# ═══════════════════════════════════════════════════════════════ +# ViewOutlineProvider +# ═══════════════════════════════════════════════════════════════ + +class ViewOutlineProvider(Stub): + """ViewOutlineProvider — 视图轮廓提供者""" + + BACKGROUND = None + BOUNDS = None + PADDED_BOUNDS = None + + def getOutline(self, view: View, outline: Any): + pass + + # 预定义实例 + @staticmethod + def _init_constants(): + if ViewOutlineProvider.BACKGROUND is None: + class _Background(ViewOutlineProvider): + def getOutline(self, view, outline): + if hasattr(outline, "setRect"): + outline.setRect(0, 0, view.getWidth(), view.getHeight()) + ViewOutlineProvider.BACKGROUND = _Background() + + if ViewOutlineProvider.BOUNDS is None: + class _Bounds(ViewOutlineProvider): + def getOutline(self, view, outline): + if hasattr(outline, "setRect"): + outline.setRect(0, 0, view.getWidth(), view.getHeight()) + ViewOutlineProvider.BOUNDS = _Bounds() + + if ViewOutlineProvider.PADDED_BOUNDS is None: + class _PaddedBounds(ViewOutlineProvider): + def getOutline(self, view, outline): + if hasattr(outline, "setRect"): + outline.setRect( + view.getPaddingLeft(), view.getPaddingTop(), + view.getWidth() - view.getPaddingRight(), + view.getHeight() - view.getPaddingBottom(), + ) + ViewOutlineProvider.PADDED_BOUNDS = _PaddedBounds() + + +ViewOutlineProvider._init_constants() + +# ═══════════════════════════════════════════════════════════════ +# RenderNode +# ═══════════════════════════════════════════════════════════════ + +class RenderNode(Stub): + """RenderNode — 渲染节点""" + + STATUS_DISPLAY_LIST_INFLATED = 1 + STATUS_DISPLAY_LIST_READY = 2 + STATUS_DISPLAY_LIST_PREPARE = 3 + STATUS_DISPLAY_LIST_UNUSED = 0 + + def __init__(self, name: str = ""): + super().__init__() + self._name = name + self._alpha = 1.0 + self._translation_x = 0.0 + self._translation_y = 0.0 + self._translation_z = 0.0 + self._elevation = 0.0 + self._rotation = 0.0 + self._rotation_x = 0.0 + self._rotation_y = 0.0 + self._scale_x = 1.0 + self._scale_y = 1.0 + self._pivot_x = 0.0 + self._pivot_y = 0.0 + self._left = 0 + self._top = 0 + self._right = 0 + self._bottom = 0 + self._clip_to_bounds = False + self._clip_bounds: Optional[Rect] = None + self._has_display_list = False + self._status = RenderNode.STATUS_DISPLAY_LIST_UNUSED + + def setPosition(self, left: int, top: int, right: int, bottom: int) -> bool: + old_left, old_top, old_right, old_bottom = self._left, self._top, self._right, self._bottom + self._left, self._top, self._right, self._bottom = left, top, right, bottom + return (old_left != left or old_top != top or old_right != right or old_bottom != bottom) + + def getLeft(self) -> int: + return self._left + def getTop(self) -> int: + return self._top + def getRight(self) -> int: + return self._right + def getBottom(self) -> int: + return self._bottom + + def setAlpha(self, alpha: float) -> bool: + changed = self._alpha != alpha + self._alpha = alpha + return changed + + def getAlpha(self) -> float: + return self._alpha + + def setTranslationX(self, tx: float) -> bool: + changed = self._translation_x != tx + self._translation_x = tx + return changed + + def getTranslationX(self) -> float: + return self._translation_x + + def setTranslationY(self, ty: float) -> bool: + changed = self._translation_y != ty + self._translation_y = ty + return changed + + def getTranslationY(self) -> float: + return self._translation_y + + def setElevation(self, elevation: float) -> bool: + changed = self._elevation != elevation + self._elevation = elevation + return changed + + def getElevation(self) -> float: + return self._elevation + + def setRotation(self, rotation: float) -> bool: + changed = self._rotation != rotation + self._rotation = rotation + return changed + + def getRotation(self) -> float: + return self._rotation + + def setScaleX(self, scale_x: float) -> bool: + changed = self._scale_x != scale_x + self._scale_x = scale_x + return changed + + def getScaleX(self) -> float: + return self._scale_x + + def setScaleY(self, scale_y: float) -> bool: + changed = self._scale_y != scale_y + self._scale_y = scale_y + return changed + + def getScaleY(self) -> float: + return self._scale_y + + def setPivotX(self, pivot_x: float) -> bool: + changed = self._pivot_x != pivot_x + self._pivot_x = pivot_x + return changed + + def getPivotX(self) -> float: + return self._pivot_x + + def setPivotY(self, pivot_y: float) -> bool: + changed = self._pivot_y != pivot_y + self._pivot_y = pivot_y + return changed + + def getPivotY(self) -> float: + return self._pivot_y + + def setClipToBounds(self, clip_to_bounds: bool) -> bool: + changed = self._clip_to_bounds != clip_to_bounds + self._clip_to_bounds = clip_to_bounds + return changed + + def getClipToBounds(self) -> bool: + return self._clip_to_bounds + + def setClipBounds(self, clip_rect: Optional[Rect]): + self._clip_bounds = clip_rect + + def getClipBounds(self) -> Optional[Rect]: + return self._clip_bounds + + def setHasDisplayList(self, has_display_list: bool): + self._has_display_list = has_display_list + + def hasDisplayList(self) -> bool: + return self._has_display_list + + def getName(self) -> str: + return self._name + + def discardDisplayList(self): + self._has_display_list = False + self._status = RenderNode.STATUS_DISPLAY_LIST_UNUSED + + def start(self) -> Any: + return None + + def end(self): + pass + + def offsetLeftAndRight(self, offset: int): + self._left += offset + self._right += offset + + def offsetTopAndBottom(self, offset: int): + self._top += offset + self._bottom += offset + + def getMatrix(self, out_matrix: Any): + pass + + def getInverseMatrix(self, out_matrix: Any): + pass + + def useLighting(self) -> bool: + return False + + def setUseLighting(self, use_lighting: bool): + pass + + def setLayerType(self, layer_type: int): + pass + + def getLayerType(self) -> int: + return 0 + + +# ═══════════════════════════════════════════════════════════════ +# ScrollCaptureCallback +# ═══════════════════════════════════════════════════════════════ + +class ScrollCaptureCallback(Stub): + """ScrollCaptureCallback — 滚动捕获回调""" + + def onScrollCaptureSearch(self, l: int, t: int, r: int, b: int) -> Optional[Any]: + return None + + def onScrollCaptureStart(self, session: Any) -> Optional[Any]: + return None + + def onScrollCaptureImageRequest(self, session: Any, request: Any) -> int: + return 0 + + def onScrollCaptureEnd(self, session: Any): + pass + + +# ═══════════════════════════════════════════════════════════════ +# KeyboardShortcutGroup +# ═══════════════════════════════════════════════════════════════ + +class KeyboardShortcutGroup(Stub): + """KeyboardShortcutGroup — 键盘快捷键分组""" + + def __init__(self, label: str, items: Optional[List[Any]] = None): + super().__init__() + self._label = label + self._items: List[Any] = items if items is not None else [] + self._system_group = False + + def getLabel(self) -> str: + return self._label + + def getItems(self) -> List[Any]: + return list(self._items) + + def addItem(self, item: Any): + self._items.append(item) + + def setSystemGroup(self, is_system: bool): + self._system_group = is_system + + def isSystemGroup(self) -> bool: + return self._system_group + + +# ═══════════════════════════════════════════════════════════════ +# SoundEffectConstants +# ═══════════════════════════════════════════════════════════════ + +class SoundEffectConstants: + """SoundEffectConstants — 音效常量""" + + CLICK = 0 + NAVIGATION_LEFT = 1 + NAVIGATION_UP = 2 + NAVIGATION_RIGHT = 3 + NAVIGATION_DOWN = 4 + NAVIGATION_REPEAT_LEFT = 5 + NAVIGATION_REPEAT_UP = 6 + NAVIGATION_REPEAT_RIGHT = 7 + NAVIGATION_REPEAT_DOWN = 8 + + @staticmethod + def getDirectionForNavigation(gravity: int) -> int: + return SoundEffectConstants.NAVIGATION_UP + + +# ═══════════════════════════════════════════════════════════════ +# FocusFinder +# ═══════════════════════════════════════════════════════════════ + +class FocusFinder(Stub): + """FocusFinder — 焦点查找器""" + + _instance: Optional["FocusFinder"] = None + + def __init__(self): + super().__init__() + + @staticmethod + def getInstance() -> "FocusFinder": + if FocusFinder._instance is None: + FocusFinder._instance = FocusFinder() + return FocusFinder._instance + + def findNextFocus(self, root: ViewGroup, focused: View, direction: int) -> Optional[View]: + return None + + def findNearestTouchable(self, root: ViewGroup, x: int, y: int, direction: int, + deltas: List[int]) -> Optional[View]: + return None + + +# ═══════════════════════════════════════════════════════════════ +# ViewGroup override for children list +# ═══════════════════════════════════════════════════════════════ + +# Ensure ViewGroup._children is separate from View._children +ViewGroup._children = [] # Will be overridden in __init__ + + +# ═══════════════════════════════════════════════════════════════ +# __all__ +# ═══════════════════════════════════════════════════════════════ + +__all__ = [ + "View", + "ViewGroup", + "ViewParent", + "ViewManager", + "WindowManager", + "MotionEvent", + "KeyEvent", + "GestureDetector", + "ScaleGestureDetector", + "VelocityTracker", + "DragEvent", + "AccessibilityDelegate", + "ViewTreeObserver", + "ViewConfiguration", + "SurfaceView", + "TextureView", + "Window", + "WindowInsets", + "WindowInsetsController", + "Display", + "Surface", + "Choreographer", + "ActionMode", + "ContextThemeWrapper", + "Gravity", + "ViewOutlineProvider", + "RenderNode", + "ScrollCaptureCallback", + "KeyboardShortcutGroup", + "MeasureSpec", + "ViewPropertyAnimator", + "SurfaceHolder", + "SurfaceTexture", + "FocusFinder", + "SoundEffectConstants", + "Rect", +] \ No newline at end of file diff --git a/android_pyapi/android/view_accessibility.py b/android_pyapi/android/view_accessibility.py new file mode 100644 index 0000000..7e306bc --- /dev/null +++ b/android_pyapi/android/view_accessibility.py @@ -0,0 +1,1488 @@ +""" +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", +] \ No newline at end of file diff --git a/android_pyapi/android/view_animation.py b/android_pyapi/android/view_animation.py new file mode 100644 index 0000000..1c703ea --- /dev/null +++ b/android_pyapi/android/view_animation.py @@ -0,0 +1,935 @@ +""" +Android View Animation 组件 — Animation, AnimationSet, Interpolators, +AnimationUtils, Transformation 等 +方法签名完全对齐 Android SDK +""" + +import math +import sys +import time +from typing import Any, Callable, Dict, List, Optional, Tuple + +from .._core import Stub, AndroidOnlyError, android_context + + +# ═══════════════════════════════════════════════════════════════ +# Interpolator +# ═══════════════════════════════════════════════════════════════ + +class Interpolator(Stub): + """Interpolator — 插值器接口""" + + def getInterpolation(self, input: float) -> float: + return input + + +# ═══════════════════════════════════════════════════════════════ +# AccelerateInterpolator +# ═══════════════════════════════════════════════════════════════ + +class AccelerateInterpolator(Interpolator): + """AccelerateInterpolator — 加速插值器(先慢后快)""" + + def __init__(self, factor: float = 1.0): + super().__init__() + self._factor = factor + self._double_factor = 2.0 * factor + + def getInterpolation(self, input: float) -> float: + if self._factor == 1.0: + return input * input + return input ** (self._double_factor) + + +# ═══════════════════════════════════════════════════════════════ +# DecelerateInterpolator +# ═══════════════════════════════════════════════════════════════ + +class DecelerateInterpolator(Interpolator): + """DecelerateInterpolator — 减速插值器(先快后慢)""" + + def __init__(self, factor: float = 1.0): + super().__init__() + self._factor = factor + + def getInterpolation(self, input: float) -> float: + if self._factor == 1.0: + return 1.0 - (1.0 - input) * (1.0 - input) + return 1.0 - (1.0 - input) ** (2.0 * self._factor) + + +# ═══════════════════════════════════════════════════════════════ +# AccelerateDecelerateInterpolator +# ═══════════════════════════════════════════════════════════════ + +class AccelerateDecelerateInterpolator(Interpolator): + """AccelerateDecelerateInterpolator — 加速减速插值器""" + + def getInterpolation(self, input: float) -> float: + return float(math.cos((input + 1) * math.pi) / 2.0 + 0.5) + + +# ═══════════════════════════════════════════════════════════════ +# LinearInterpolator +# ═══════════════════════════════════════════════════════════════ + +class LinearInterpolator(Interpolator): + """LinearInterpolator — 线性插值器""" + + def getInterpolation(self, input: float) -> float: + return input + + +# ═══════════════════════════════════════════════════════════════ +# OvershootInterpolator +# ═══════════════════════════════════════════════════════════════ + +class OvershootInterpolator(Interpolator): + """OvershootInterpolator — 超出插值器(越过目标再回弹)""" + + def __init__(self, tension: float = 2.0): + super().__init__() + self._tension = tension + + def getInterpolation(self, input: float) -> float: + input -= 1.0 + return input * input * ((self._tension + 1) * input + self._tension) + 1.0 + + +# ═══════════════════════════════════════════════════════════════ +# AnticipateInterpolator +# ═══════════════════════════════════════════════════════════════ + +class AnticipateInterpolator(Interpolator): + """AnticipateInterpolator — 预判插值器(先反向后正向前进)""" + + def __init__(self, tension: float = 2.0): + super().__init__() + self._tension = tension + + def getInterpolation(self, input: float) -> float: + return input * input * ((self._tension + 1) * input - self._tension) + + +# ═══════════════════════════════════════════════════════════════ +# AnticipateOvershootInterpolator +# ═══════════════════════════════════════════════════════════════ + +class AnticipateOvershootInterpolator(Interpolator): + """AnticipateOvershootInterpolator — 预判超出插值器""" + + def __init__(self, tension: float = 2.0, extra_tension: float = 1.5): + super().__init__() + self._tension = tension + self._extra_tension = extra_tension + + def getInterpolation(self, input: float) -> float: + if input < 0.5: + t = input * 2.0 + return 0.5 * t * t * ((self._tension + 1) * t - self._tension) + else: + t = input * 2.0 - 2.0 + return 0.5 * (t * t * ((self._tension + 1) * t + self._tension) + 2.0) + + +# ═══════════════════════════════════════════════════════════════ +# BounceInterpolator +# ═══════════════════════════════════════════════════════════════ + +class BounceInterpolator(Interpolator): + """BounceInterpolator — 弹跳插值器""" + + @staticmethod + def _bounce(t: float) -> float: + t2 = t * t + return t2 * 8.0 + + def getInterpolation(self, input: float) -> float: + if input < 0.3535: + return BounceInterpolator._bounce(input) + elif input < 0.7408: + return BounceInterpolator._bounce(input - 0.54719) + 0.7 + elif input < 0.9644: + return BounceInterpolator._bounce(input - 0.8526) + 0.9 + else: + return BounceInterpolator._bounce(input - 1.0435) + 0.95 + + +# ═══════════════════════════════════════════════════════════════ +# CycleInterpolator +# ═══════════════════════════════════════════════════════════════ + +class CycleInterpolator(Interpolator): + """CycleInterpolator — 周期插值器(正弦循环)""" + + def __init__(self, cycles: float = 1.0): + super().__init__() + self._cycles = cycles + + def getInterpolation(self, input: float) -> float: + return float(math.sin(2.0 * math.pi * self._cycles * input)) + + +# ═══════════════════════════════════════════════════════════════ +# PathInterpolator +# ═══════════════════════════════════════════════════════════════ + +class PathInterpolator(Interpolator): + """PathInterpolator — 路径插值器""" + + def __init__(self, control_x1: float = 0.0, control_y1: float = 0.0, + control_x2: float = 1.0, control_y2: float = 1.0): + super().__init__() + self._control_x1 = control_x1 + self._control_y1 = control_y1 + self._control_x2 = control_x2 + self._control_y2 = control_y2 + + def getInterpolation(self, input: float) -> float: + return self._cubic_bezier(input) + + def _cubic_bezier(self, t: float) -> float: + u = 1.0 - t + return (3.0 * u * u * t * self._control_y1 + + 3.0 * u * t * t * self._control_y2 + + t * t * t) + + +# ═══════════════════════════════════════════════════════════════ +# Transformation +# ═══════════════════════════════════════════════════════════════ + +class Transformation(Stub): + """Transformation — 动画变换矩阵""" + + TYPE_IDENTITY = 0 + TYPE_ALPHA = 1 + TYPE_BOTH = 2 + + def __init__(self): + super().__init__() + self._alpha: float = 1.0 + self._matrix: Any = None # 占位,实际 Android 为 Matrix 对象 + self._transformation_type: int = Transformation.TYPE_IDENTITY + + def getAlpha(self) -> float: + return self._alpha + + def setAlpha(self, alpha: float): + self._alpha = alpha + + def getMatrix(self) -> Any: + return self._matrix + + def setMatrix(self, matrix: Any): + self._matrix = matrix + + def setTransformationType(self, transformation_type: int): + self._transformation_type = transformation_type + + def getTransformationType(self) -> int: + return self._transformation_type + + def clear(self): + self._alpha = 1.0 + self._matrix = None + self._transformation_type = Transformation.TYPE_IDENTITY + + def compose(self, other: "Transformation"): + if other is None: + return + self._alpha *= other._alpha + if self._matrix is None: + self._matrix = other._matrix + elif other._matrix is not None: + pass # 占位:矩阵乘法 + self._transformation_type = max(self._transformation_type, + other._transformation_type) + + def toString(self) -> str: + return (f"Transformation(alpha={self._alpha}, " + f"type={self._transformation_type})") + + +# ═══════════════════════════════════════════════════════════════ +# Animation +# ═══════════════════════════════════════════════════════════════ + +class Animation(Stub): + """Animation — 动画基类""" + + INFINITE = -1 + RESTART = 1 + REVERSE = 2 + + ABSOLUTE = 0 + RELATIVE_TO_SELF = 1 + RELATIVE_TO_PARENT = 2 + + START_ON_FIRST_FRAME = -1 + + class AnimationListener(Stub): + """AnimationListener — 动画监听器""" + + def onAnimationStart(self, animation: "Animation"): + pass + + def onAnimationEnd(self, animation: "Animation"): + pass + + def onAnimationRepeat(self, animation: "Animation"): + pass + + def __init__(self): + super().__init__() + self._duration: int = 0 + self._start_offset: int = 0 + self._repeat_count: int = 0 + self._repeat_mode: int = Animation.RESTART + self._fill_before: bool = False + self._fill_after: bool = False + self._fill_enabled: bool = False + self._z_adjustment: int = 0 + self._interpolator: Optional[Interpolator] = None + self._listener: Optional["Animation.AnimationListener"] = None + self._start_time: Optional[int] = None + self._started: bool = False + self._ended: bool = False + self._cycle: float = 0.0 + self._current_repeat: int = 0 + self._initialized: bool = False + self._scale_factor: float = 1.0 + self._paused: bool = False + self._more: bool = True + + def start(self): + self._start_time = self._current_time_millis() + self._started = True + self._ended = False + self._current_repeat = 0 + self._more = True + if self._listener: + self._listener.onAnimationStart(self) + + def startNow(self): + self.start() + + def cancel(self): + self._ended = True + self._more = False + + def reset(self): + self._start_time = None + self._started = False + self._ended = False + self._current_repeat = 0 + self._cycle = 0.0 + self._more = True + self._initialized = False + + def setDuration(self, duration_millis: int): + self._duration = duration_millis + + def getDuration(self) -> int: + return self._duration + + def setStartOffset(self, start_offset: int): + self._start_offset = start_offset + + def getStartOffset(self) -> int: + return self._start_offset + + def setRepeatCount(self, repeat_count: int): + self._repeat_count = repeat_count + + def getRepeatCount(self) -> int: + return self._repeat_count + + def setRepeatMode(self, repeat_mode: int): + self._repeat_mode = repeat_mode + + def getRepeatMode(self) -> int: + return self._repeat_mode + + def setInterpolator(self, interpolator: Optional[Interpolator]): + self._interpolator = interpolator + + def getInterpolator(self) -> Optional[Interpolator]: + return self._interpolator + + def setAnimationListener(self, listener: "Animation.AnimationListener"): + self._listener = listener + + def getAnimationListener(self) -> Optional["Animation.AnimationListener"]: + return self._listener + + def getTransformation(self, current_time: int, + out_transformation: Transformation) -> bool: + if self._start_time is None: + self._start_time = current_time + + elapsed = current_time - self._start_time - self._start_offset + if elapsed < 0: + out_transformation.clear() + return True + + duration = self._duration + if duration == 0: + duration = 1 + + normalized = elapsed / duration + + if normalized >= 1.0: + if self._current_repeat < self._repeat_count or self._repeat_count == self.INFINITE: + if self._repeat_mode == Animation.REVERSE: + normalized = 0.0 + else: + normalized = 0.0 + self._current_repeat += 1 + if self._listener: + self._listener.onAnimationRepeat(self) + else: + normalized = 1.0 + self._ended = True + self._more = False + self._apply_transformation(1.0, out_transformation) + if self._listener: + self._listener.onAnimationEnd(self) + return False + + if normalized > 1.0: + normalized = 1.0 + + if self._interpolator: + interpolated = self._interpolator.getInterpolation(normalized) + else: + interpolated = normalized + + self._cycle = normalized + self._apply_transformation(interpolated, out_transformation) + return self._more + + def _apply_transformation(self, interpolated_time: float, t: Transformation): + pass + + def hasEnded(self) -> bool: + return self._ended + + def hasStarted(self) -> bool: + return self._started + + def setFillBefore(self, fill_before: bool): + self._fill_before = fill_before + + def getFillBefore(self) -> bool: + return self._fill_before + + def setFillAfter(self, fill_after: bool): + self._fill_after = fill_after + + def getFillAfter(self) -> bool: + return self._fill_after + + def setFillEnabled(self, fill_enabled: bool): + self._fill_enabled = fill_enabled + + def getFillEnabled(self) -> bool: + return self._fill_enabled + + def setZAdjustment(self, z_adjustment: int): + self._z_adjustment = z_adjustment + + def getZAdjustment(self) -> int: + return self._z_adjustment + + def willChangeBounds(self) -> bool: + return True + + def willChangeTransformationMatrix(self) -> bool: + return True + + def initialize(self, width: int, height: int, parent_width: int, parent_height: int): + self._initialized = True + + def restrictDuration(self, duration_millis: int): + if self._duration > duration_millis: + self._duration = duration_millis + + def scaleCurrentDuration(self, scale: float): + self._duration = int(self._duration * scale) + + @staticmethod + def _current_time_millis() -> int: + return int(time.time() * 1000) + + def toString(self) -> str: + return (f"Animation(duration={self._duration}, " + f"repeatCount={self._repeat_count}, " + f"started={self._started})") + + +# ═══════════════════════════════════════════════════════════════ +# AlphaAnimation +# ═══════════════════════════════════════════════════════════════ + +class AlphaAnimation(Animation): + """AlphaAnimation — 透明度动画""" + + def __init__(self, from_alpha: float, to_alpha: float): + super().__init__() + self._from_alpha = from_alpha + self._to_alpha = to_alpha + + def _apply_transformation(self, interpolated_time: float, t: Transformation): + alpha = self._from_alpha + (self._to_alpha - self._from_alpha) * interpolated_time + t.setAlpha(alpha) + + def getFromAlpha(self) -> float: + return self._from_alpha + + def getToAlpha(self) -> float: + return self._to_alpha + + +# ═══════════════════════════════════════════════════════════════ +# ScaleAnimation +# ═══════════════════════════════════════════════════════════════ + +class ScaleAnimation(Animation): + """ScaleAnimation — 缩放动画""" + + def __init__(self, from_x: float, to_x: float, from_y: float, to_y: float, + pivot_x: float = 0.0, pivot_y: float = 0.0, + pivot_x_type: int = Animation.ABSOLUTE, + pivot_y_type: int = Animation.ABSOLUTE): + super().__init__() + self._from_x = from_x + self._to_x = to_x + self._from_y = from_y + self._to_y = to_y + self._pivot_x = pivot_x + self._pivot_y = pivot_y + self._pivot_x_type = pivot_x_type + self._pivot_y_type = pivot_y_type + + def _apply_transformation(self, interpolated_time: float, t: Transformation): + sx = self._from_x + (self._to_x - self._from_x) * interpolated_time + sy = self._from_y + (self._to_y - self._from_y) * interpolated_time + # 占位:Matrix 的缩放操作 + if t.getMatrix() is not None: + pass + + def getFromX(self) -> float: + return self._from_x + + def getToX(self) -> float: + return self._to_x + + def getFromY(self) -> float: + return self._from_y + + def getToY(self) -> float: + return self._to_y + + def getPivotX(self) -> float: + return self._pivot_x + + def getPivotY(self) -> float: + return self._pivot_y + + +# ═══════════════════════════════════════════════════════════════ +# TranslateAnimation +# ═══════════════════════════════════════════════════════════════ + +class TranslateAnimation(Animation): + """TranslateAnimation — 平移动画""" + + def __init__(self, from_x: float, to_x: float, from_y: float, to_y: float, + from_x_type: int = Animation.ABSOLUTE, + to_x_type: int = Animation.ABSOLUTE, + from_y_type: int = Animation.ABSOLUTE, + to_y_type: int = Animation.ABSOLUTE): + super().__init__() + self._from_x = from_x + self._to_x = to_x + self._from_y = from_y + self._to_y = to_y + self._from_x_type = from_x_type + self._to_x_type = to_x_type + self._from_y_type = from_y_type + self._to_y_type = to_y_type + + def _apply_transformation(self, interpolated_time: float, t: Transformation): + dx = self._from_x + (self._to_x - self._from_x) * interpolated_time + dy = self._from_y + (self._to_y - self._from_y) * interpolated_time + if t.getMatrix() is not None: + pass + + def getFromX(self) -> float: + return self._from_x + + def getToX(self) -> float: + return self._to_x + + def getFromY(self) -> float: + return self._from_y + + def getToY(self) -> float: + return self._to_y + + +# ═══════════════════════════════════════════════════════════════ +# RotateAnimation +# ═══════════════════════════════════════════════════════════════ + +class RotateAnimation(Animation): + """RotateAnimation — 旋转动画""" + + def __init__(self, from_degrees: float, to_degrees: float, + pivot_x: float = 0.0, pivot_y: float = 0.0, + pivot_x_type: int = Animation.ABSOLUTE, + pivot_y_type: int = Animation.ABSOLUTE): + super().__init__() + self._from_degrees = from_degrees + self._to_degrees = to_degrees + self._pivot_x = pivot_x + self._pivot_y = pivot_y + self._pivot_x_type = pivot_x_type + self._pivot_y_type = pivot_y_type + + def _apply_transformation(self, interpolated_time: float, t: Transformation): + degrees = self._from_degrees + (self._to_degrees - self._from_degrees) * interpolated_time + if t.getMatrix() is not None: + pass + + def getFromDegrees(self) -> float: + return self._from_degrees + + def getToDegrees(self) -> float: + return self._to_degrees + + def getPivotX(self) -> float: + return self._pivot_x + + def getPivotY(self) -> float: + return self._pivot_y + + +# ═══════════════════════════════════════════════════════════════ +# AnimationSet +# ═══════════════════════════════════════════════════════════════ + +class AnimationSet(Animation): + """AnimationSet — 动画集合""" + + def __init__(self, share_interpolator: bool = True): + super().__init__() + self._share_interpolator = share_interpolator + self._animations: List[Animation] = [] + + def addAnimation(self, animation: Animation): + self._animations.append(animation) + if self._share_interpolator and self._interpolator: + animation.setInterpolator(self._interpolator) + + def getAnimations(self) -> List[Animation]: + return list(self._animations) + + def setDuration(self, duration_millis: int): + super().setDuration(duration_millis) + for anim in self._animations: + anim.setDuration(duration_millis) + + def setFillAfter(self, fill_after: bool): + super().setFillAfter(fill_after) + for anim in self._animations: + anim.setFillAfter(fill_after) + + def setFillBefore(self, fill_before: bool): + super().setFillBefore(fill_before) + for anim in self._animations: + anim.setFillBefore(fill_before) + + def setRepeatMode(self, repeat_mode: int): + super().setRepeatMode(repeat_mode) + for anim in self._animations: + anim.setRepeatMode(repeat_mode) + + def setRepeatCount(self, repeat_count: int): + super().setRepeatCount(repeat_count) + for anim in self._animations: + anim.setRepeatCount(repeat_count) + + def setStartOffset(self, start_offset: int): + super().setStartOffset(start_offset) + for anim in self._animations: + anim.setStartOffset(start_offset) + + def setInterpolator(self, interpolator: Optional[Interpolator]): + super().setInterpolator(interpolator) + if self._share_interpolator: + for anim in self._animations: + anim.setInterpolator(interpolator) + + def start(self): + super().start() + for anim in self._animations: + anim.start() + + def reset(self): + super().reset() + for anim in self._animations: + anim.reset() + + def cancel(self): + super().cancel() + for anim in self._animations: + anim.cancel() + + def getTransformation(self, current_time: int, + out_transformation: Transformation) -> bool: + more = False + for anim in self._animations: + local_t = Transformation() + if anim.getTransformation(current_time, local_t): + more = True + out_transformation.compose(local_t) + return more + + def initialize(self, width: int, height: int, parent_width: int, parent_height: int): + super().initialize(width, height, parent_width, parent_height) + for anim in self._animations: + anim.initialize(width, height, parent_width, parent_height) + + def willChangeBounds(self) -> bool: + for anim in self._animations: + if anim.willChangeBounds(): + return True + return False + + def willChangeTransformationMatrix(self) -> bool: + for anim in self._animations: + if anim.willChangeTransformationMatrix(): + return True + return False + + def getAnimations(self) -> List[Animation]: + return list(self._animations) + + def restrictDuration(self, duration_millis: int): + super().restrictDuration(duration_millis) + for anim in self._animations: + anim.restrictDuration(duration_millis) + + def scaleCurrentDuration(self, scale: float): + super().scaleCurrentDuration(scale) + for anim in self._animations: + anim.scaleCurrentDuration(scale) + + +# ═══════════════════════════════════════════════════════════════ +# Animation.AnimationListener (已定义在 Animation 类中) +# ═══════════════════════════════════════════════════════════════ + +# Animation.AnimationListener is defined inside the Animation class above + + +# ═══════════════════════════════════════════════════════════════ +# AnimationUtils +# ═══════════════════════════════════════════════════════════════ + +class AnimationUtils(Stub): + """AnimationUtils — 动画工具类""" + + @staticmethod + def loadAnimation(context: Any, id: int) -> Animation: + """加载动画资源(占位实现)""" + sys.stderr.write(f"[AnimationUtils] loadAnimation: id={id}\n") + return Animation() + + @staticmethod + def makeInAnimation(context: Any, from_left: bool) -> Animation: + """创建进入动画""" + anim = AlphaAnimation(0.0, 1.0) + anim.setDuration(300) + return anim + + @staticmethod + def makeOutAnimation(context: Any, to_left: bool) -> Animation: + """创建退出动画""" + anim = AlphaAnimation(1.0, 0.0) + anim.setDuration(300) + return anim + + @staticmethod + def makeInChildBottomAnimation(context: Any) -> Animation: + """创建子视图从底部进入动画""" + anim = TranslateAnimation(0, 0, 1.0, 0.0, + from_x_type=Animation.RELATIVE_TO_SELF, + to_x_type=Animation.RELATIVE_TO_SELF, + from_y_type=Animation.RELATIVE_TO_SELF, + to_y_type=Animation.RELATIVE_TO_SELF) + anim.setDuration(300) + return anim + + @staticmethod + def currentAnimationTimeMillis() -> int: + return int(time.time() * 1000) + + +# ═══════════════════════════════════════════════════════════════ +# LayoutAnimationController +# ═══════════════════════════════════════════════════════════════ + +class LayoutAnimationController(Stub): + """LayoutAnimationController — 布局动画控制器""" + + ORDER_NORMAL = 0 + ORDER_REVERSE = 1 + ORDER_RANDOM = 2 + + def __init__(self, animation: Optional[Animation] = None): + super().__init__() + self._animation: Optional[Animation] = animation + self._delay: float = 0.5 + self._order: int = LayoutAnimationController.ORDER_NORMAL + self._initialized: bool = False + self._max_delay: int = 0 + + def setAnimation(self, animation: Animation): + self._animation = animation + + def getAnimation(self) -> Optional[Animation]: + return self._animation + + def setOrder(self, order: int): + self._order = order + + def getOrder(self) -> int: + return self._order + + def setDelay(self, delay: float): + self._delay = delay + + def getDelay(self) -> float: + return self._delay + + def getDelayForView(self, view: Any) -> int: + if self._order == LayoutAnimationController.ORDER_NORMAL: + return 0 + elif self._order == LayoutAnimationController.ORDER_REVERSE: + return 0 + elif self._order == LayoutAnimationController.ORDER_RANDOM: + import random + return int(random.random() * self._max_delay) + return 0 + + def willOverlap(self) -> bool: + return self._delay < 1.0 + + def start(self): + self._initialized = True + + def isDone(self) -> bool: + return True + + def getAnimationForView(self, view: Any) -> Optional[Animation]: + return self._animation + + def setInitialized(self, initialized: bool): + self._initialized = initialized + + +# ═══════════════════════════════════════════════════════════════ +# GridLayoutAnimationController +# ═══════════════════════════════════════════════════════════════ + +class GridLayoutAnimationController(LayoutAnimationController): + """GridLayoutAnimationController — 网格布局动画控制器""" + + DIRECTION_LEFT_TO_RIGHT = 0x0 + DIRECTION_RIGHT_TO_LEFT = 0x1 + DIRECTION_TOP_TO_BOTTOM = 0x2 + DIRECTION_BOTTOM_TO_TOP = 0x3 + DIRECTION_HORIZONTAL_MASK = 0x1 + DIRECTION_VERTICAL_MASK = 0x2 + + def __init__(self, animation: Optional[Animation] = None): + super().__init__(animation) + self._column_delay: float = 0.5 + self._row_delay: float = 0.5 + self._direction: int = GridLayoutAnimationController.DIRECTION_LEFT_TO_RIGHT + self._columns: int = 1 + self._rows: int = 1 + + def setColumnDelay(self, column_delay: float): + self._column_delay = column_delay + + def getColumnDelay(self) -> float: + return self._column_delay + + def setRowDelay(self, row_delay: float): + self._row_delay = row_delay + + def getRowDelay(self) -> float: + return self._row_delay + + def setDirection(self, direction: int): + self._direction = direction + + def getDirection(self) -> int: + return self._direction + + def setColumns(self, columns: int): + self._columns = columns + + def getColumns(self) -> int: + return self._columns + + def setRows(self, rows: int): + self._rows = rows + + def getRows(self) -> int: + return self._rows + + def getDelayForView(self, view: Any) -> int: + duration = 0 + if self._animation: + duration = self._animation.getDuration() + return int(duration * self._column_delay) + + def willOverlap(self) -> bool: + return self._column_delay < 1.0 or self._row_delay < 1.0 + + def getAnimationForView(self, view: Any) -> Optional[Animation]: + return self._animation + + +# ═══════════════════════════════════════════════════════════════ +# __all__ +# ═══════════════════════════════════════════════════════════════ + +__all__ = [ + "Animation", + "AnimationSet", + "AlphaAnimation", + "ScaleAnimation", + "TranslateAnimation", + "RotateAnimation", + "AnimationUtils", + "Interpolator", + "AccelerateInterpolator", + "DecelerateInterpolator", + "AccelerateDecelerateInterpolator", + "LinearInterpolator", + "OvershootInterpolator", + "AnticipateInterpolator", + "AnticipateOvershootInterpolator", + "BounceInterpolator", + "CycleInterpolator", + "PathInterpolator", + "Transformation", + "LayoutAnimationController", + "GridLayoutAnimationController", +] \ No newline at end of file diff --git a/android_pyapi/android/view_inputmethod.py b/android_pyapi/android/view_inputmethod.py new file mode 100644 index 0000000..4da8dab --- /dev/null +++ b/android_pyapi/android/view_inputmethod.py @@ -0,0 +1,859 @@ +""" +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", +] \ No newline at end of file diff --git a/android_pyapi/android/webkit.py b/android_pyapi/android/webkit.py new file mode 100644 index 0000000..e8000c7 --- /dev/null +++ b/android_pyapi/android/webkit.py @@ -0,0 +1,992 @@ +""" +Android WebKit 组件 — WebView, WebSettings, WebViewClient, WebChromeClient 等 +方法签名完全对齐 Android SDK +""" + +import os +import sys +from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union + +from .._core import ( + Stub, AndroidOnlyError, android_context, + resolve_android_path, to_android_path, + _sdcard_root, _app_data_dir, _package_name, +) + + +# ═══════════════════════════════════════════════════════════════ +# WebResourceRequest / WebResourceResponse / WebResourceError +# ═══════════════════════════════════════════════════════════════ + +class WebResourceRequest(Stub): + """WebResourceRequest — 资源请求""" + + def __init__(self, url: str = "", is_for_main_frame: bool = False, + is_redirect: bool = False, method: str = "GET", + request_headers: Optional[Dict[str, str]] = None): + super().__init__() + self._url = url + self._is_for_main_frame = is_for_main_frame + self._is_redirect = is_redirect + self._method = method + self._request_headers = request_headers or {} + + def getUrl(self) -> str: + return self._url + + def isForMainFrame(self) -> bool: + return self._is_for_main_frame + + def isRedirect(self) -> bool: + return self._is_redirect + + def getMethod(self) -> str: + return self._method + + def getRequestHeaders(self) -> Dict[str, str]: + return dict(self._request_headers) + + def toString(self) -> str: + return f"WebResourceRequest(url={self._url})" + + +class WebResourceResponse(Stub): + """WebResourceResponse — 资源响应""" + + def __init__(self, mime_type: str = "", encoding: str = "", + data: Any = None, status_code: int = 200, + reason_phrase: str = "OK", + response_headers: Optional[Dict[str, str]] = None): + super().__init__() + self._mime_type = mime_type + self._encoding = encoding + self._data = data + self._status_code = status_code + self._reason_phrase = reason_phrase + self._response_headers = response_headers or {} + + def getMimeType(self) -> str: + return self._mime_type + + def getEncoding(self) -> str: + return self._encoding + + def getData(self) -> Any: + return self._data + + def getStatusCode(self) -> int: + return self._status_code + + def getReasonPhrase(self) -> str: + return self._reason_phrase + + def getResponseHeaders(self) -> Dict[str, str]: + return dict(self._response_headers) + + +class WebResourceError(Stub): + """WebResourceError — 资源错误""" + + ERROR_UNKNOWN = -1 + ERROR_HOST_LOOKUP = -2 + ERROR_UNSUPPORTED_AUTH_SCHEME = -3 + ERROR_AUTHENTICATION = -4 + ERROR_PROXY_AUTHENTICATION = -5 + ERROR_CONNECT = -6 + ERROR_IO = -7 + ERROR_TIMEOUT = -8 + ERROR_REDIRECT_LOOP = -9 + ERROR_UNSUPPORTED_SCHEME = -10 + ERROR_FAILED_SSL_HANDSHAKE = -11 + ERROR_BAD_URL = -12 + ERROR_FILE = -13 + ERROR_FILE_NOT_FOUND = -14 + ERROR_TOO_MANY_REQUESTS = -15 + + def __init__(self, error_code: int = ERROR_UNKNOWN, description: str = ""): + super().__init__() + self._error_code = error_code + self._description = description + + def getErrorCode(self) -> int: + return self._error_code + + def getDescription(self) -> str: + return self._description + + +# ═══════════════════════════════════════════════════════════════ +# WebMessage / WebMessagePort +# ═══════════════════════════════════════════════════════════════ + +class WebMessage(Stub): + """WebMessage — Web 消息""" + + def __init__(self, data: str = ""): + super().__init__() + self._data = data + + def getData(self) -> str: + return self._data + + +class WebMessagePort(Stub): + """WebMessagePort — Web 消息端口""" + + def postMessage(self, message: WebMessage): + pass + + def close(self): + pass + + def setWebMessageCallback(self, callback: Any): + pass + + +# ═══════════════════════════════════════════════════════════════ +# JavascriptInterface +# ═══════════════════════════════════════════════════════════════ + +class JavascriptInterface(Stub): + """JavascriptInterface — JavaScript 接口注解""" + + def __init__(self, value: str = ""): + super().__init__() + self._value = value + + def getValue(self) -> str: + return self._value + + +# ═══════════════════════════════════════════════════════════════ +# JsResult / JsPromptResult +# ═══════════════════════════════════════════════════════════════ + +class JsResult(Stub): + """JsResult — JS 对话框结果""" + + def __init__(self): + super().__init__() + self._confirmed = False + + def confirm(self): + self._confirmed = True + + def cancel(self): + self._confirmed = False + + +class JsPromptResult(JsResult): + """JsPromptResult — JS 提示结果""" + + def __init__(self): + super().__init__() + self._string_result: str = "" + + def confirm(self, result: str = ""): + self._confirmed = True + self._string_result = result + + def getStringResult(self) -> str: + return self._string_result + + +# ═══════════════════════════════════════════════════════════════ +# HttpAuthHandler / SslErrorHandler +# ═══════════════════════════════════════════════════════════════ + +class HttpAuthHandler(Stub): + """HttpAuthHandler — HTTP 认证处理器""" + + def __init__(self): + super().__init__() + self._proceeded = False + self._cancelled = False + + def proceed(self, username: str, password: str): + self._proceeded = True + + def cancel(self): + self._cancelled = True + + def useHttpAuthUsernamePassword(self) -> bool: + return False + + +class SslErrorHandler(Stub): + """SslErrorHandler — SSL 错误处理器""" + + def __init__(self): + super().__init__() + self._proceeded = False + self._cancelled = False + + def proceed(self): + self._proceeded = True + + def cancel(self): + self._cancelled = True + + +# ═══════════════════════════════════════════════════════════════ +# PermissionRequest +# ═══════════════════════════════════════════════════════════════ + +class PermissionRequest(Stub): + """PermissionRequest — 权限请求""" + + RESOURCE_VIDEO_CAPTURE = "android.webkit.resource.VIDEO_CAPTURE" + RESOURCE_AUDIO_CAPTURE = "android.webkit.resource.AUDIO_CAPTURE" + RESOURCE_PROTECTED_MEDIA_ID = "android.webkit.resource.PROTECTED_MEDIA_ID" + RESOURCE_MIDI_SYSEX = "android.webkit.resource.MIDI_SYSEX" + + def __init__(self): + super().__init__() + self._origin: Any = None + self._resources: List[str] = [] + self._granted_resources: List[str] = [] + + def getOrigin(self) -> Any: + return self._origin + + def getResources(self) -> List[str]: + return list(self._resources) + + def getGrantedResources(self) -> List[str]: + return list(self._granted_resources) + + def grant(self, resources: List[str]): + self._granted_resources = list(resources) + + def deny(self): + self._granted_resources.clear() + + +# ═══════════════════════════════════════════════════════════════ +# GeolocationPermissions / WebStorage +# ═══════════════════════════════════════════════════════════════ + +class GeolocationPermissions(Stub): + """GeolocationPermissions — 地理位置权限""" + + @staticmethod + def getInstance() -> "GeolocationPermissions": + return GeolocationPermissions() + + def allow(self, origin: str): + pass + + def clear(self, origin: str): + pass + + def clearAll(self): + pass + + def getAllowed(self, origin: str, callback: Any): + pass + + def getOrigins(self, callback: Any): + pass + + +class WebStorage(Stub): + """WebStorage — Web 存储""" + + @staticmethod + def getInstance() -> "WebStorage": + return WebStorage() + + def deleteAllData(self): + pass + + def deleteOrigin(self, origin: str): + pass + + def getOrigins(self, callback: Any): + pass + + def getQuotaForOrigin(self, origin: str, callback: Any): + pass + + def getUsageForOrigin(self, origin: str, callback: Any): + pass + + +# ═══════════════════════════════════════════════════════════════ +# WebBackForwardList / WebHistoryItem +# ═══════════════════════════════════════════════════════════════ + +class WebHistoryItem(Stub): + """WebHistoryItem — 历史项""" + + def __init__(self): + super().__init__() + self._url: str = "" + self._original_url: str = "" + self._title: str = "" + self._favicon: Any = None + + def getUrl(self) -> str: + return self._url + + def getOriginalUrl(self) -> str: + return self._original_url + + def getTitle(self) -> str: + return self._title + + def getFavicon(self) -> Any: + return self._favicon + + def toString(self) -> str: + return f"WebHistoryItem(url={self._url})" + + +class WebBackForwardList(Stub): + """WebBackForwardList — 前进后退列表""" + + def __init__(self): + super().__init__() + self._items: List[WebHistoryItem] = [] + self._current_index: int = -1 + + def getCurrentItem(self) -> Optional[WebHistoryItem]: + if 0 <= self._current_index < len(self._items): + return self._items[self._current_index] + return None + + def getCurrentIndex(self) -> int: + return self._current_index + + def getItemAtIndex(self, index: int) -> Optional[WebHistoryItem]: + if 0 <= index < len(self._items): + return self._items[index] + return None + + def getSize(self) -> int: + return len(self._items) + + +# ═══════════════════════════════════════════════════════════════ +# CookieManager / CookieSyncManager +# ═══════════════════════════════════════════════════════════════ + +class CookieManager(Stub): + """CookieManager — Cookie 管理器""" + + _instance: Optional["CookieManager"] = None + + def __init__(self): + super().__init__() + self._accept_cookies: bool = True + self._accept_third_party: bool = True + self._cookies: Dict[str, str] = {} + + @staticmethod + def getInstance() -> "CookieManager": + if CookieManager._instance is None: + CookieManager._instance = CookieManager() + return CookieManager._instance + + def setAcceptCookies(self, accept: bool): + self._accept_cookies = accept + + def acceptCookies(self) -> bool: + return self._accept_cookies + + def setCookie(self, url: str, value: str): + if self._accept_cookies: + self._cookies[url] = value + + def getCookie(self, url: str) -> Optional[str]: + return self._cookies.get(url) + + def setAcceptThirdPartyCookies(self, webview: Any, accept: bool): + self._accept_third_party = accept + + def acceptThirdPartyCookies(self) -> bool: + return self._accept_third_party + + def removeSessionCookie(self): + pass + + def removeAllCookies(self): + self._cookies.clear() + + def removeAllCookiesSync(self): + self._cookies.clear() + + def removeSessionCookies(self): + pass + + def flush(self): + pass + + def hasCookies(self) -> bool: + return bool(self._cookies) + + def removeExpiredCookie(self): + pass + + +class CookieSyncManager(Stub): + """CookieSyncManager — Cookie 同步管理器""" + + _instance: Optional["CookieSyncManager"] = None + + @staticmethod + def createInstance(context: Any) -> "CookieSyncManager": + if CookieSyncManager._instance is None: + CookieSyncManager._instance = CookieSyncManager() + return CookieSyncManager._instance + + @staticmethod + def getInstance() -> "CookieSyncManager": + if CookieSyncManager._instance is None: + CookieSyncManager._instance = CookieSyncManager() + return CookieSyncManager._instance + + def sync(self): + pass + + def startSync(self): + pass + + def stopSync(self): + pass + + def resetSync(self): + pass + + +# ═══════════════════════════════════════════════════════════════ +# WebSettings +# ═══════════════════════════════════════════════════════════════ + +class WebSettings(Stub): + """WebSettings — WebView 设置""" + + LOAD_DEFAULT = -1 + LOAD_CACHE_ELSE_NETWORK = 1 + LOAD_NO_CACHE = 2 + LOAD_CACHE_ONLY = 3 + + MIXED_CONTENT_ALWAYS_ALLOW = 0 + MIXED_CONTENT_NEVER_ALLOW = 1 + MIXED_CONTENT_COMPATIBILITY_MODE = 2 + + class LayoutAlgorithm(Stub): + NORMAL = 0 + SINGLE_COLUMN = 1 + NARROW_COLUMNS = 2 + TEXT_AUTOSIZING = 3 + + def __init__(self): + super().__init__() + self._js_enabled: bool = True + self._dom_storage_enabled: bool = True + self._allow_file_access: bool = True + self._allow_content_access: bool = True + self._allow_file_access_from_urls: bool = False + self._allow_universal_access_from_urls: bool = False + self._built_in_zoom_controls: bool = True + self._display_zoom_controls: bool = True + self._support_zoom: bool = True + self._load_with_overview_mode: bool = False + self._use_wide_viewport: bool = False + self._user_agent_string: str = "Mozilla/5.0 (Linux; Android 14) AppleWebKit/537.36" + self._default_text_encoding: str = "UTF-8" + self._cache_mode: int = self.LOAD_DEFAULT + self._app_cache_enabled: bool = False + self._app_cache_path: str = "" + self._database_enabled: bool = False + self._geolocation_enabled: bool = False + self._js_can_open_windows: bool = False + self._loads_images_automatically: bool = True + self._block_network_image: bool = False + self._block_network_loads: bool = False + self._minimum_font_size: int = 8 + self._default_font_size: int = 16 + self._text_zoom: int = 100 + self._layout_algorithm: int = WebSettings.LayoutAlgorithm.NORMAL + self._mixed_content_mode: int = self.MIXED_CONTENT_NEVER_ALLOW + self._offscreen_pre_raster: bool = False + self._safe_browsing_enabled: bool = True + self._javascript_can_open_windows_automatically: bool = False + + def setJavaScriptEnabled(self, enabled: bool): + self._js_enabled = enabled + + def setDomStorageEnabled(self, enabled: bool): + self._dom_storage_enabled = enabled + + def setAllowFileAccess(self, allow: bool): + self._allow_file_access = allow + + def setAllowContentAccess(self, allow: bool): + self._allow_content_access = allow + + def setAllowFileAccessFromFileURLs(self, allow: bool): + self._allow_file_access_from_urls = allow + + def setAllowUniversalAccessFromFileURLs(self, allow: bool): + self._allow_universal_access_from_urls = allow + + def setBuiltInZoomControls(self, enabled: bool): + self._built_in_zoom_controls = enabled + + def setDisplayZoomControls(self, enabled: bool): + self._display_zoom_controls = enabled + + def setSupportZoom(self, support: bool): + self._support_zoom = support + + def setLoadWithOverviewMode(self, overview: bool): + self._load_with_overview_mode = overview + + def setUseWideViewPort(self, use: bool): + self._use_wide_viewport = use + + def setUserAgentString(self, ua: str): + self._user_agent_string = ua + + def getUserAgentString(self) -> str: + return self._user_agent_string + + def setDefaultTextEncodingName(self, encoding: str): + self._default_text_encoding = encoding + + def getDefaultTextEncodingName(self) -> str: + return self._default_text_encoding + + def setCacheMode(self, mode: int): + self._cache_mode = mode + + def getCacheMode(self) -> int: + return self._cache_mode + + def setAppCacheEnabled(self, enabled: bool): + self._app_cache_enabled = enabled + + def setAppCachePath(self, path: str): + self._app_cache_path = path + + def setDatabaseEnabled(self, enabled: bool): + self._database_enabled = enabled + + def setGeolocationEnabled(self, enabled: bool): + self._geolocation_enabled = enabled + + def setJavaScriptCanOpenWindowsAutomatically(self, auto: bool): + self._javascript_can_open_windows_automatically = auto + + def setLoadsImagesAutomatically(self, auto: bool): + self._loads_images_automatically = auto + + def setBlockNetworkImage(self, block: bool): + self._block_network_image = block + + def setBlockNetworkLoads(self, block: bool): + self._block_network_loads = block + + def setMinimumFontSize(self, size: int): + self._minimum_font_size = size + + def getMinimumFontSize(self) -> int: + return self._minimum_font_size + + def setDefaultFontSize(self, size: int): + self._default_font_size = size + + def getDefaultFontSize(self) -> int: + return self._default_font_size + + def setTextZoom(self, zoom: int): + self._text_zoom = zoom + + def getTextZoom(self) -> int: + return self._text_zoom + + def setLayoutAlgorithm(self, algo: int): + self._layout_algorithm = algo + + def getLayoutAlgorithm(self) -> int: + return self._layout_algorithm + + def setMixedContentMode(self, mode: int): + self._mixed_content_mode = mode + + def getMixedContentMode(self) -> int: + return self._mixed_content_mode + + def setOffscreenPreRaster(self, enabled: bool): + self._offscreen_pre_raster = enabled + + def getOffscreenPreRaster(self) -> bool: + return self._offscreen_pre_raster + + def setSafeBrowsingEnabled(self, enabled: bool): + self._safe_browsing_enabled = enabled + + def getSafeBrowsingEnabled(self) -> bool: + return self._safe_browsing_enabled + + def toString(self) -> str: + return f"WebSettings(js={self._js_enabled})" + + +# ═══════════════════════════════════════════════════════════════ +# WebViewClient +# ═══════════════════════════════════════════════════════════════ + +class WebViewClient(Stub): + """WebViewClient — WebView 客户端""" + + def onPageStarted(self, view: "WebView", url: str, favicon: Any): + pass + + def onPageFinished(self, view: "WebView", url: str): + pass + + def onLoadResource(self, view: "WebView", url: str): + pass + + def shouldOverrideUrlLoading(self, view: "WebView", request: Any) -> bool: + return False + + def shouldOverrideUrlLoading(self, view: "WebView", url: str) -> bool: + return False + + def shouldInterceptRequest(self, view: "WebView", request: WebResourceRequest) -> Optional[WebResourceResponse]: + return None + + def shouldInterceptRequest(self, view: "WebView", url: str) -> Optional[WebResourceResponse]: + return None + + def onReceivedHttpError(self, view: "WebView", request: WebResourceRequest, + error_response: WebResourceResponse): + pass + + def onReceivedSslError(self, view: "WebView", handler: SslErrorHandler, error: Any): + pass + + def onReceivedClientCertRequest(self, view: "WebView", request: Any): + pass + + def onReceivedHttpAuthRequest(self, view: "WebView", handler: HttpAuthHandler, + host: str, realm: str): + pass + + def onFormResubmission(self, view: "WebView", dont_resend: Any, resend: Any): + pass + + def doUpdateVisitedHistory(self, view: "WebView", url: str, is_reload: bool): + pass + + def onScaleChanged(self, view: "WebView", old_scale: float, new_scale: float): + pass + + def onUnhandledKeyEvent(self, view: "WebView", event: Any): + pass + + def shouldOverrideKeyEvent(self, view: "WebView", event: Any) -> bool: + return False + + +# ═══════════════════════════════════════════════════════════════ +# WebChromeClient +# ═══════════════════════════════════════════════════════════════ + +class WebChromeClient(Stub): + """WebChromeClient — WebView Chrome 客户端""" + + def onProgressChanged(self, view: "WebView", new_progress: int): + pass + + def onReceivedTitle(self, view: "WebView", title: str): + pass + + def onReceivedIcon(self, view: "WebView", icon: Any): + pass + + def onReceivedTouchIconUrl(self, view: "WebView", url: str, precomposed: bool): + pass + + def onShowCustomView(self, view: Any, callback: Any): + pass + + def onHideCustomView(self): + pass + + def onCreateWindow(self, view: "WebView", is_dialog: bool, + is_user_gesture: bool, result_msg: Any) -> bool: + return False + + def onRequestFocus(self, view: "WebView"): + pass + + def onCloseWindow(self, window: "WebView"): + pass + + def onJsAlert(self, view: "WebView", url: str, message: str, + result: JsResult) -> bool: + return False + + def onJsConfirm(self, view: "WebView", url: str, message: str, + result: JsResult) -> bool: + return False + + def onJsPrompt(self, view: "WebView", url: str, message: str, + default_value: str, result: JsPromptResult) -> bool: + return False + + def onJsBeforeUnload(self, view: "WebView", url: str, message: str, + result: JsResult) -> bool: + return False + + def onExceededDatabaseQuota(self, url: str, database_identifier: str, + quota: int, estimated_database_size: int, + total_quota: int, quota_updater: Any): + pass + + def onReachedMaxAppCacheSize(self, space_needed: int, total_quota: int, + quota_updater: Any): + pass + + def onGeolocationPermissionsShowPrompt(self, origin: str, + callback: Any): + pass + + def onGeolocationPermissionsHidePrompt(self): + pass + + def onPermissionRequest(self, request: PermissionRequest): + pass + + def onPermissionRequestCanceled(self, request: PermissionRequest): + pass + + def onConsoleMessage(self, message: Any) -> bool: + return True + + +# ═══════════════════════════════════════════════════════════════ +# WebView +# ═══════════════════════════════════════════════════════════════ + +class WebView(Stub): + """WebView — Web 视图""" + + _instance_count = 0 + + def __init__(self, context: Any = None, attrs: Any = None): + super().__init__() + WebView._instance_count += 1 + self._context = context + self._settings = WebSettings() + self._webview_client: Optional[WebViewClient] = None + self._chrome_client: Optional[WebChromeClient] = None + self._current_url: str = "" + self._original_url: str = "" + self._title: str = "" + self._progress: int = 100 + self._content_height: int = 0 + self._scale: float = 1.0 + self._background_color: int = 0xFFFFFFFF + self._back_forward_list = WebBackForwardList() + self._javascript_interfaces: Dict[str, Any] = {} + self._paused: bool = False + self._timers_paused: bool = False + self._network_available: bool = True + + def loadUrl(self, url: str, additional_headers: Optional[Dict[str, str]] = None): + self._current_url = url + self._original_url = url + self._title = url + self._progress = 0 + if self._webview_client: + try: + self._webview_client.onPageStarted(self, url, None) + self._webview_client.onPageFinished(self, url) + except Exception: + pass + self._progress = 100 + + def loadData(self, data: str, mime_type: str = "text/html", + encoding: str = "utf-8"): + self._current_url = "data:text/html;charset=utf-8," + self._progress = 100 + + def loadDataWithBaseURL(self, base_url: str, data: str, + mime_type: str, encoding: str, + history_url: str): + self._current_url = history_url or base_url + self._progress = 100 + + def setWebViewClient(self, client: WebViewClient): + self._webview_client = client + + def setWebChromeClient(self, client: WebChromeClient): + self._chrome_client = client + + def getSettings(self) -> WebSettings: + return self._settings + + def goBack(self): + pass + + def goForward(self): + pass + + def canGoBack(self) -> bool: + return False + + def canGoForward(self) -> bool: + return False + + def goBackOrForward(self, steps: int): + pass + + def reload(self): + pass + + def stopLoading(self): + pass + + def clearHistory(self): + pass + + def clearCache(self, include_disk_files: bool): + pass + + def clearView(self): + pass + + def destroy(self): + WebView._instance_count -= 1 + + def getUrl(self) -> Optional[str]: + return self._current_url or None + + def getTitle(self) -> Optional[str]: + return self._title or None + + def getProgress(self) -> int: + return self._progress + + def getContentHeight(self) -> int: + return self._content_height + + def getScale(self) -> float: + return self._scale + + def addJavascriptInterface(self, obj: Any, interface_name: str): + self._javascript_interfaces[interface_name] = obj + + def removeJavascriptInterface(self, interface_name: str): + if interface_name in self._javascript_interfaces: + del self._javascript_interfaces[interface_name] + + def evaluateJavascript(self, script: str, callback: Any = None): + pass + + def setInitialScale(self, scale_in_percent: int): + self._scale = scale_in_percent / 100.0 + + def setBackgroundColor(self, color: int): + self._background_color = color + + def scrollTo(self, x: int, y: int): + pass + + def scrollBy(self, x: int, y: int): + pass + + def pageUp(self, top: bool) -> bool: + return True + + def pageDown(self, bottom: bool) -> bool: + return True + + def findNext(self, forward: bool): + pass + + def findAll(self, find: str) -> int: + return 0 + + def findAllAsync(self, find: str): + pass + + def clearMatches(self): + pass + + def zoomIn(self): + self._scale *= 1.25 + + def zoomOut(self): + self._scale /= 1.25 + + def setNetworkAvailable(self, available: bool): + self._network_available = available + + def pauseTimers(self): + self._timers_paused = True + + def resumeTimers(self): + self._timers_paused = False + + def onPause(self): + self._paused = True + + def onResume(self): + self._paused = False + + def onWindowFocusChanged(self, has_focus: bool): + pass + + def setLayoutParams(self, params: Any): + pass + + def requestFocus(self, direction: int = 0): + pass + + def getOriginalUrl(self) -> Optional[str]: + return self._original_url or None + + def canZoomIn(self) -> bool: + return True + + def canZoomOut(self) -> bool: + return self._scale > 0.25 + + def toString(self) -> str: + return f"WebView(url={self._current_url})" + + +# ═══════════════════════════════════════════════════════════════ +# __all__ +# ═══════════════════════════════════════════════════════════════ + +__all__ = [ + "WebView", + "WebSettings", + "WebViewClient", + "WebChromeClient", + "WebResourceRequest", + "WebResourceResponse", + "WebResourceError", + "WebMessage", + "WebMessagePort", + "JavascriptInterface", + "CookieManager", + "CookieSyncManager", + "JsResult", + "JsPromptResult", + "HttpAuthHandler", + "SslErrorHandler", + "PermissionRequest", + "GeolocationPermissions", + "WebStorage", + "WebBackForwardList", + "WebHistoryItem", +] \ No newline at end of file diff --git a/android_pyapi/android/widget.py b/android_pyapi/android/widget.py new file mode 100644 index 0000000..98c2544 --- /dev/null +++ b/android_pyapi/android/widget.py @@ -0,0 +1,4777 @@ +""" +Android Widget 组件 — TextView, Button, RecyclerView, ListView, WebView 等 +方法签名完全对齐 Android SDK +""" + +import math +import time +from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union + +from .._core import Stub, AndroidOnlyError, android_context +from .view import ( + View, ViewGroup, ViewTreeObserver, + MotionEvent, KeyEvent, Gravity, SurfaceView, + ViewGroup as VG, +) + + +# ═══════════════════════════════════════════════════════════════ +# TextView +# ═══════════════════════════════════════════════════════════════ + +class TextView(View): + """TextView — 文本显示视图""" + + # 自动链接 + AUTO_LINK_NONE = 0x00 + AUTO_LINK_WEB_URLS = 0x01 + AUTO_LINK_EMAIL_ADDRESSES = 0x02 + AUTO_LINK_PHONE_NUMBERS = 0x04 + AUTO_LINK_MAP_ADDRESSES = 0x08 + AUTO_LINK_ALL = 0x0F + + # Buffer类型 + BUFFER_TYPE_NORMAL = 0 + BUFFER_TYPE_SPANNABLE = 1 + BUFFER_TYPE_EDITABLE = 2 + + # 截断位置 + TRUNCATE_AT_END = 0 + TRUNCATE_AT_MIDDLE = 1 + TRUNCATE_AT_START = 2 + + # 文本方向 + TEXT_DIRECTION_INHERIT = 0 + TEXT_DIRECTION_FIRST_STRONG = 1 + TEXT_DIRECTION_ANY_RTL = 2 + TEXT_DIRECTION_LTR = 3 + TEXT_DIRECTION_RTL = 4 + TEXT_DIRECTION_LOCALE = 5 + TEXT_DIRECTION_FIRST_STRONG_LTR = 6 + TEXT_DIRECTION_FIRST_STRONG_RTL = 7 + + # 文本对齐 + TEXT_ALIGNMENT_INHERIT = 0 + TEXT_ALIGNMENT_GRAVITY = 1 + TEXT_ALIGNMENT_TEXT_START = 2 + TEXT_ALIGNMENT_TEXT_END = 3 + TEXT_ALIGNMENT_CENTER = 4 + TEXT_ALIGNMENT_VIEW_START = 5 + TEXT_ALIGNMENT_VIEW_END = 6 + + def __init__(self, context=None): + super().__init__(context) + self._text: str = "" + self._hint: str = "" + self._text_size: float = 14.0 + self._text_color: int = 0xFF000000 + self._hint_text_color: int = 0xFF808080 + self._link_text_color: int = 0xFFFF0000 + self._highlight_color: int = 0x556633CC + self._gravity = Gravity.NO_GRAVITY + self._typeface: Any = None + self._input_type: int = 0 + self._lines: int = 0 + self._max_lines: int = 0 + self._min_lines: int = 0 + self._single_line: bool = False + self._ellipsize: Optional[str] = None # "end", "start", "middle", "marquee" + self._movement_method: Any = None + self._auto_link_mask: int = TextView.AUTO_LINK_NONE + self._transformation_method: Optional[Callable] = None + self._shadow_radius: float = 0.0 + self._shadow_dx: float = 0.0 + self._shadow_dy: float = 0.0 + self._shadow_color: int = 0 + self._line_spacing_extra: float = 0.0 + self._line_spacing_mult: float = 1.0 + self._compound_drawables: List[Any] = [None, None, None, None] + self._compound_drawable_padding: int = 0 + self._include_font_padding: bool = True + self._elegant_text_height: bool = False + self._letter_spacing: float = 0.0 + self._font_feature_settings: str = "" + self._key_listener: Any = None + self._filters: List[Any] = [] + self._select_all_on_focus: bool = False + self._horizontally_scrolling: bool = False + self._on_editor_action_listener: Optional[Callable] = None + self._paint: Any = None + self._line_count: int = 1 + self._layout: Any = None + self._links_clickable: bool = False + self._cursor_visible: bool = True + self._editable: bool = False + self._selection_start: int = 0 + self._selection_end: int = 0 + self._scroll_horizontally: bool = False + + # ─── text ────────────────────────────────────────────────── + + def setText(self, text: str, buffer_type: int = BUFFER_TYPE_NORMAL): + self._text = str(text) if text is not None else "" + self._line_count = self._text.count("\n") + 1 if self._text else 0 + + def getText(self) -> str: + return self._text + + def append(self, text: str): + self._text += str(text) if text is not None else "" + self._line_count = self._text.count("\n") + 1 if self._text else 0 + + # ─── text size / color ───────────────────────────────────── + + def setTextSize(self, size: float, unit: int = 2): # unit=2 means SP + self._text_size = size + + def getTextSize(self) -> float: + return self._text_size + + def setTextColor(self, color: int): + self._text_color = color + + def getCurrentTextColor(self) -> int: + return self._text_color + + def setHintTextColor(self, color: int): + self._hint_text_color = color + + def getHintTextColor(self) -> int: + return self._hint_text_color + + def setLinkTextColor(self, color: int): + self._link_text_color = color + + def getLinkTextColor(self) -> int: + return self._link_text_color + + def setHighlightColor(self, color: int): + self._highlight_color = color + + def getHighlightColor(self) -> int: + return self._highlight_color + + # ─── gravity ────────────────────────────────────────────── + + def setGravity(self, gravity: int): + self._gravity = gravity + + def getGravity(self) -> int: + return self._gravity + + # ─── typeface ────────────────────────────────────────────── + + def setTypeface(self, typeface: Any, style: int = 0): + self._typeface = typeface + + def getTypeface(self) -> Any: + return self._typeface + + # ─── hint ────────────────────────────────────────────────── + + def setHint(self, hint: str): + self._hint = str(hint) if hint is not None else "" + + def getHint(self) -> str: + return self._hint + + # ─── input type ──────────────────────────────────────────── + + def setInputType(self, input_type: int): + self._input_type = input_type + + def getInputType(self) -> int: + return self._input_type + + # ─── lines ───────────────────────────────────────────────── + + def setLines(self, lines: int): + self._lines = lines + + def setMaxLines(self, max_lines: int): + self._max_lines = max_lines + + def getMaxLines(self) -> int: + return self._max_lines + + def setMinLines(self, min_lines: int): + self._min_lines = min_lines + + def getMinLines(self) -> int: + return self._min_lines + + def getLineCount(self) -> int: + return self._line_count + + def setSingleLine(self, single_line: bool = True): + self._single_line = single_line + + # ─── ellipsize ───────────────────────────────────────────── + + def setEllipsize(self, where: Optional[str]): + self._ellipsize = where + + def getEllipsize(self) -> Optional[str]: + return self._ellipsize + + # ─── movement method ────────────────────────────────────── + + def setMovementMethod(self, movement: Any): + self._movement_method = movement + + def getMovementMethod(self) -> Any: + return self._movement_method + + # ─── auto link ───────────────────────────────────────────── + + def setAutoLinkMask(self, mask: int): + self._auto_link_mask = mask + + def getAutoLinkMask(self) -> int: + return self._auto_link_mask + + # ─── transformation ─────────────────────────────────────── + + def setTransformationMethod(self, method: Optional[Callable]): + self._transformation_method = method + + def getTransformationMethod(self) -> Optional[Callable]: + return self._transformation_method + + # ─── shadow ──────────────────────────────────────────────── + + def setShadowLayer(self, radius: float, dx: float, dy: float, color: int): + self._shadow_radius = radius + self._shadow_dx = dx + self._shadow_dy = dy + self._shadow_color = color + + # ─── line spacing ───────────────────────────────────────── + + def setLineSpacing(self, extra: float, multiplier: float): + self._line_spacing_extra = extra + self._line_spacing_mult = multiplier + + # ─── compound drawables ─────────────────────────────────── + + def setCompoundDrawables(self, left: Any, top: Any, right: Any, bottom: Any): + self._compound_drawables = [left, top, right, bottom] + + def setCompoundDrawablesWithIntrinsicBounds(self, left: Any, top: Any, + right: Any, bottom: Any): + self._compound_drawables = [left, top, right, bottom] + + def setCompoundDrawablePadding(self, pad: int): + self._compound_drawable_padding = pad + + def getCompoundDrawablePadding(self) -> int: + return self._compound_drawable_padding + + def getCompoundDrawables(self) -> List[Any]: + return list(self._compound_drawables) + + # ─── editor action ──────────────────────────────────────── + + def setOnEditorActionListener(self, listener: Optional[Callable]): + self._on_editor_action_listener = listener + + # ─── key listener ───────────────────────────────────────── + + def setKeyListener(self, key_listener: Any): + self._key_listener = key_listener + + def getKeyListener(self) -> Any: + return self._key_listener + + # ─── filters ─────────────────────────────────────────────── + + def setFilters(self, filters: List[Any]): + self._filters = list(filters) + + def getFilters(self) -> List[Any]: + return list(self._filters) + + # ─── misc ────────────────────────────────────────────────── + + def setSelectAllOnFocus(self, select_all_on_focus: bool): + self._select_all_on_focus = select_all_on_focus + + def setHorizontallyScrolling(self, horizontally_scrolling: bool): + self._horizontally_scrolling = horizontally_scrolling + + def setIncludeFontPadding(self, include_font_padding: bool): + self._include_font_padding = include_font_padding + + def setElegantTextHeight(self, elegant: bool): + self._elegant_text_height = elegant + + def setLetterSpacing(self, letter_spacing: float): + self._letter_spacing = letter_spacing + + def getLetterSpacing(self) -> float: + return self._letter_spacing + + def setFontFeatureSettings(self, settings: str): + self._font_feature_settings = settings + + def getFontFeatureSettings(self) -> str: + return self._font_feature_settings + + def getPaint(self) -> Any: + return self._paint + + def getLayout(self) -> Any: + return self._layout + + def setLinksClickable(self, links_clickable: bool): + self._links_clickable = links_clickable + + def getLinksClickable(self) -> bool: + return self._links_clickable + + def setCursorVisible(self, visible: bool): + self._cursor_visible = visible + + def isCursorVisible(self) -> bool: + return self._cursor_visible + + def setTextCursorDrawable(self, drawable: Any): + pass + + def setFreezesText(self, freezes_text: bool): + pass + + def getFreezesText(self) -> bool: + return False + + def setEditableFactory(self, factory: Any): + pass + + def setSpannableFactory(self, factory: Any): + pass + + def getTextSize(self) -> float: + return self._text_size + + def setBackgroundColor(self, color: int): + super().setBackgroundColor(color) + + def setPadding(self, left: int, top: int, right: int, bottom: int): + super().setPadding(left, top, right, bottom) + + def onMeasure(self, width_measure_spec: int, height_measure_spec: int): + from .view import MeasureSpec + w = MeasureSpec.getSize(width_measure_spec) + h = MeasureSpec.getSize(height_measure_spec) + if MeasureSpec.getMode(width_measure_spec) == MeasureSpec.AT_MOST: + text_len = len(self._text) * 4 + w = min(w, max(100, text_len)) + if MeasureSpec.getMode(height_measure_spec) == MeasureSpec.AT_MOST: + text_lines = self._line_count + h = min(h, max(20, text_lines * int(self._text_size) + 10)) + self.setMeasuredDimension(w, h) + + def onDraw(self, canvas: Any): + pass + + +# ═══════════════════════════════════════════════════════════════ +# EditText +# ═══════════════════════════════════════════════════════════════ + +class EditText(TextView): + """EditText — 可编辑文本""" + + def __init__(self, context=None): + super().__init__(context) + self._editable = True + self._selection_start = 0 + self._selection_end = 0 + + def setSelection(self, start: int, stop: Optional[int] = None): + if stop is None: + self._selection_start = start + self._selection_end = start + else: + self._selection_start = start + self._selection_end = stop + + def getSelectionStart(self) -> int: + return self._selection_start + + def getSelectionEnd(self) -> int: + return self._selection_end + + def selectAll(self): + self._selection_start = 0 + self._selection_end = len(self._text) + + def extendSelection(self, index: int): + self._selection_end = index + + def setText(self, text: str, buffer_type: int = TextView.BUFFER_TYPE_EDITABLE): + super().setText(text, buffer_type) + + def getEditableText(self): + return self._text + + +# ═══════════════════════════════════════════════════════════════ +# Button +# ═══════════════════════════════════════════════════════════════ + +class Button(TextView): + """Button — 按钮""" + + def __init__(self, context=None): + super().__init__(context) + self._clickable = True + self._focusable = True + + def onTouchEvent(self, event: MotionEvent) -> bool: + if event.getAction() == MotionEvent.ACTION_UP: + self.performClick() + return super().onTouchEvent(event) + + +class CheckBox(Button): + """CheckBox — 复选框""" + + def __init__(self, context=None): + super().__init__(context) + self._checked = False + + def setChecked(self, checked: bool): + self._checked = checked + + def isChecked(self) -> bool: + return self._checked + + def toggle(self): + self._checked = not self._checked + + def performClick(self) -> bool: + self.toggle() + return super().performClick() + + +class RadioButton(Button): + """RadioButton — 单选按钮""" + + def __init__(self, context=None): + super().__init__(context) + self._checked = False + + def setChecked(self, checked: bool): + self._checked = checked + + def isChecked(self) -> bool: + return self._checked + + def toggle(self): + self._checked = not self._checked + + def performClick(self) -> bool: + self.toggle() + return super().performClick() + + +class ToggleButton(Button): + """ToggleButton — 开关按钮""" + + def __init__(self, context=None): + super().__init__(context) + self._checked = False + self._text_on = "ON" + self._text_off = "OFF" + self._disabled_alpha = 0.5 + + def setChecked(self, checked: bool): + self._checked = checked + if self._checked: + self.setText(self._text_on) + else: + self.setText(self._text_off) + + def isChecked(self) -> bool: + return self._checked + + def toggle(self): + self.setChecked(not self._checked) + + def setTextOn(self, text_on: str): + self._text_on = text_on + if self._checked: + self.setText(text_on) + + def getTextOn(self) -> str: + return self._text_on + + def setTextOff(self, text_off: str): + self._text_off = text_off + if not self._checked: + self.setText(text_off) + + def getTextOff(self) -> str: + return self._text_off + + def performClick(self) -> bool: + self.toggle() + return super().performClick() + + +class Switch(Button): + """Switch — 开关组件""" + + def __init__(self, context=None): + super().__init__(context) + self._checked = False + self._show_text = True + self._split_track = False + self._thumb_text_padding: int = 0 + self._switch_min_width: int = 0 + self._switch_padding: int = 0 + self._thumb_drawable: Any = None + self._track_drawable: Any = None + self._text_on = "ON" + self._text_off = "OFF" + self._thumb_width = 40 + self._switch_width = 100 + + def isChecked(self) -> bool: + return self._checked + + def setChecked(self, checked: bool): + self._checked = checked + + def toggle(self): + self._checked = not self._checked + + def setShowText(self, show_text: bool): + self._show_text = show_text + + def getShowText(self) -> bool: + return self._show_text + + def setSplitTrack(self, split_track: bool): + self._split_track = split_track + + def getSplitTrack(self) -> bool: + return self._split_track + + def setThumbTextPadding(self, padding: int): + self._thumb_text_padding = padding + + def getThumbTextPadding(self) -> int: + return self._thumb_text_padding + + def setSwitchMinWidth(self, min_width: int): + self._switch_min_width = min_width + + def getSwitchMinWidth(self) -> int: + return self._switch_min_width + + def setSwitchPadding(self, padding: int): + self._switch_padding = padding + + def getSwitchPadding(self) -> int: + return self._switch_padding + + def getThumbDrawable(self) -> Any: + return self._thumb_drawable + + def setThumbDrawable(self, drawable: Any): + self._thumb_drawable = drawable + + def getTrackDrawable(self) -> Any: + return self._track_drawable + + def setTrackDrawable(self, drawable: Any): + self._track_drawable = drawable + + def setTextOn(self, text_on: str): + self._text_on = text_on + + def getTextOn(self) -> str: + return self._text_on + + def setTextOff(self, text_off: str): + self._text_off = text_off + + def getTextOff(self) -> str: + return self._text_off + + def performClick(self) -> bool: + self.toggle() + return super().performClick() + + def onMeasure(self, width_measure_spec: int, height_measure_spec: int): + from .view import MeasureSpec + w = MeasureSpec.getSize(width_measure_spec) + h = MeasureSpec.getSize(height_measure_spec) + self.setMeasuredDimension(w, max(30, h)) + + +# ═══════════════════════════════════════════════════════════════ +# ImageView +# ═══════════════════════════════════════════════════════════════ + +class ImageView(View): + """ImageView — 图片视图""" + + # ScaleType常量 + ScaleType = type("ScaleType", (), { + "MATRIX": 0, + "FIT_XY": 1, + "FIT_START": 2, + "FIT_CENTER": 3, + "FIT_END": 4, + "CENTER": 5, + "CENTER_CROP": 6, + "CENTER_INSIDE": 7, + }) + + def __init__(self, context=None): + super().__init__(context) + self._drawable: Any = None + self._image_resource: int = 0 + self._uri: Any = None + self._scale_type: int = ImageView.ScaleType.FIT_CENTER + self._max_width: int = 0 + self._max_height: int = 0 + self._adjust_view_bounds: bool = False + self._crop_to_padding: bool = False + self._baseline: int = 0 + self._baseline_align_bottom: bool = False + self._image_matrix: Any = None + self._image_bitmap: Any = None + + def setImageDrawable(self, drawable: Any): + self._drawable = drawable + + def getDrawable(self) -> Any: + return self._drawable + + def setImageResource(self, res_id: int): + self._image_resource = res_id + + def setImageURI(self, uri: Any): + self._uri = uri + + def setImageBitmap(self, bitmap: Any): + self._image_bitmap = bitmap + + def getImageBitmap(self) -> Any: + return self._image_bitmap + + def setScaleType(self, scale_type: int): + self._scale_type = scale_type + + def getScaleType(self) -> int: + return self._scale_type + + def setMaxWidth(self, max_width: int): + self._max_width = max_width + + def setMaxHeight(self, max_height: int): + self._max_height = max_height + + def setAdjustViewBounds(self, adjust: bool): + self._adjust_view_bounds = adjust + + def getAdjustViewBounds(self) -> bool: + return self._adjust_view_bounds + + def setCropToPadding(self, crop: bool): + self._crop_to_padding = crop + + def getCropToPadding(self) -> bool: + return self._crop_to_padding + + def setBaseline(self, baseline: int): + self._baseline = baseline + + def getBaseline(self) -> int: + return self._baseline + + def setBaselineAlignBottom(self, aligned: bool): + self._baseline_align_bottom = aligned + + def getBaselineAlignBottom(self) -> bool: + return self._baseline_align_bottom + + def setImageMatrix(self, matrix: Any): + self._image_matrix = matrix + + def getImageMatrix(self) -> Any: + return self._image_matrix + + def invalidateDrawable(self, dr: Any): + pass + + def onMeasure(self, width_measure_spec: int, height_measure_spec: int): + from .view import MeasureSpec + w = MeasureSpec.getSize(width_measure_spec) + h = MeasureSpec.getSize(height_measure_spec) + if w == 0: + w = 100 + if h == 0: + h = 100 + self.setMeasuredDimension(w, h) + + +class ImageButton(ImageView): + """ImageButton — 图片按钮""" + + def __init__(self, context=None): + super().__init__(context) + self._clickable = True + self._focusable = True + + def onTouchEvent(self, event: MotionEvent) -> bool: + if event.getAction() == MotionEvent.ACTION_UP: + self.performClick() + return super().onTouchEvent(event) + + +# ═══════════════════════════════════════════════════════════════ +# ProgressBar +# ═══════════════════════════════════════════════════════════════ + +class ProgressBar(View): + """ProgressBar — 进度条""" + + def __init__(self, context=None, attrs: Any = None, def_style_attr: int = 0, + def_style_res: int = 0): + super().__init__(context) + self._progress: int = 0 + self._max: int = 100 + self._indeterminate: bool = False + self._indeterminate_drawable: Any = None + self._progress_drawable: Any = None + self._secondary_progress: int = 0 + self._min: int = 0 + self._interpolator: Any = None + self._animation: Any = None + + def setProgress(self, progress: int): + self._progress = max(self._min, min(self._max, progress)) + + def getProgress(self) -> int: + return self._progress + + def setMax(self, max_val: int): + self._max = max_val + if self._progress > self._max: + self._progress = self._max + + def getMax(self) -> int: + return self._max + + def setMin(self, min_val: int): + self._min = min_val + + def getMin(self) -> int: + return self._min + + def incrementProgressBy(self, diff: int): + self.setProgress(self._progress + diff) + + def setIndeterminate(self, indeterminate: bool): + self._indeterminate = indeterminate + + def isIndeterminate(self) -> bool: + return self._indeterminate + + def getIndeterminateDrawable(self) -> Any: + return self._indeterminate_drawable + + def setIndeterminateDrawable(self, drawable: Any): + self._indeterminate_drawable = drawable + + def setProgressDrawable(self, drawable: Any): + self._progress_drawable = drawable + + def getProgressDrawable(self) -> Any: + return self._progress_drawable + + def setSecondaryProgress(self, secondary_progress: int): + self._secondary_progress = max(self._min, min(self._max, secondary_progress)) + + def getSecondaryProgress(self) -> int: + return self._secondary_progress + + def setInterpolator(self, interpolator: Any): + self._interpolator = interpolator + + def getInterpolator(self) -> Any: + return self._interpolator + + def setAnimation(self, animation: Any): + self._animation = animation + + def onMeasure(self, width_measure_spec: int, height_measure_spec: int): + from .view import MeasureSpec + w = MeasureSpec.getSize(width_measure_spec) + h = MeasureSpec.getSize(height_measure_spec) + if h == 0: + h = 20 + self.setMeasuredDimension(w, h) + + +class SeekBar(ProgressBar): + """SeekBar — 可拖动进度条""" + + def __init__(self, context=None, attrs: Any = None, + def_style_attr: int = 0, def_style_res: int = 0): + super().__init__(context, attrs, def_style_attr, def_style_res) + self._on_seek_bar_change_listener: Optional[Callable] = None + + class OnSeekBarChangeListener: + def onProgressChanged(self, seek_bar: "SeekBar", progress: int, from_user: bool): + pass + def onStartTrackingTouch(self, seek_bar: "SeekBar"): + pass + def onStopTrackingTouch(self, seek_bar: "SeekBar"): + pass + + def setOnSeekBarChangeListener(self, listener: Optional[OnSeekBarChangeListener]): + self._on_seek_bar_change_listener = listener + + def getOnSeekBarChangeListener(self) -> Optional[OnSeekBarChangeListener]: + return self._on_seek_bar_change_listener + + def setThumb(self, thumb: Any): + pass + + def getThumb(self) -> Any: + return None + + def setThumbOffset(self, thumb_offset: int): + pass + + def getThumbOffset(self) -> int: + return 0 + + def setKeyProgressIncrement(self, increment: int): + pass + + def getKeyProgressIncrement(self) -> int: + return 1 + + +class RatingBar(ProgressBar): + """RatingBar — 评分条""" + + def __init__(self, context=None, attrs: Any = None, + def_style_attr: int = 0, def_style_res: int = 0): + super().__init__(context, attrs, def_style_attr, def_style_res) + self._num_stars: int = 5 + self._rating: float = 0.0 + self._step_size: float = 0.5 + self._is_indicator: bool = False + self._on_rating_bar_change_listener: Optional[Callable] = None + + class OnRatingBarChangeListener: + def onRatingChanged(self, rating_bar: "RatingBar", rating: float, from_user: bool): + pass + + def setOnRatingBarChangeListener(self, listener: Optional[OnRatingBarChangeListener]): + self._on_rating_bar_change_listener = listener + + def getOnRatingBarChangeListener(self) -> Optional[OnRatingBarChangeListener]: + return self._on_rating_bar_change_listener + + def setNumStars(self, num_stars: int): + self._num_stars = max(1, num_stars) + + def getNumStars(self) -> int: + return self._num_stars + + def setRating(self, rating: float): + self._rating = max(0.0, min(float(self._num_stars), rating)) + + def getRating(self) -> float: + return self._rating + + def setStepSize(self, step_size: float): + self._step_size = max(0.1, step_size) + + def getStepSize(self) -> float: + return self._step_size + + def setIsIndicator(self, is_indicator: bool): + self._is_indicator = is_indicator + + def isIndicator(self) -> bool: + return self._is_indicator + + +# ═══════════════════════════════════════════════════════════════ +# AdapterView +# ═══════════════════════════════════════════════════════════════ + +class AdapterView(ViewGroup): + """AdapterView — 适配器视图基类""" + + CHOICE_MODE_NONE = 0 + CHOICE_MODE_SINGLE = 1 + CHOICE_MODE_MULTIPLE = 2 + CHOICE_MODE_MULTIPLE_MODAL = 3 + + INVALID_POSITION = -1 + INVALID_ROW_ID = -9223372036854775808 # Long.MIN_VALUE + + def __init__(self, context=None): + super().__init__(context) + self._adapter: Any = None + self._on_item_click_listener: Optional[Callable] = None + self._on_item_long_click_listener: Optional[Callable] = None + self._on_item_selected_listener: Optional[Callable] = None + self._choice_mode = AdapterView.CHOICE_MODE_NONE + self._checked_item_positions: Dict[int, bool] = {} + self._checked_id_positions: Dict[int, bool] = {} + self._selected_position = AdapterView.INVALID_POSITION + self._empty_view: Optional[View] = None + self._selection_notifier: Any = None + + def setAdapter(self, adapter: Any): + self._adapter = adapter + + def getAdapter(self) -> Any: + return self._adapter + + def setOnItemClickListener(self, listener: Optional[Callable]): + self._on_item_click_listener = listener + + def getOnItemClickListener(self) -> Optional[Callable]: + return self._on_item_click_listener + + def setOnItemLongClickListener(self, listener: Optional[Callable]): + self._on_item_long_click_listener = listener + + def getOnItemLongClickListener(self) -> Optional[Callable]: + return self._on_item_long_click_listener + + def setOnItemSelectedListener(self, listener: Optional[Callable]): + self._on_item_selected_listener = listener + + def getOnItemSelectedListener(self) -> Optional[Callable]: + return self._on_item_selected_listener + + def getSelectedItemPosition(self) -> int: + return self._selected_position + + def getSelectedItemId(self) -> int: + return 0 + + def getSelectedItem(self) -> Any: + if self._adapter is not None and 0 <= self._selected_position < self._adapter.getCount(): + return self._adapter.getItem(self._selected_position) + return None + + def setSelection(self, position: int): + self._selected_position = position + + def setEmptyView(self, empty_view: View): + self._empty_view = empty_view + + def getEmptyView(self) -> Optional[View]: + return self._empty_view + + def setChoiceMode(self, choice_mode: int): + self._choice_mode = choice_mode + + def getChoiceMode(self) -> int: + return self._choice_mode + + def setItemChecked(self, position: int, checked: bool): + if self._choice_mode == AdapterView.CHOICE_MODE_SINGLE: + self._checked_item_positions.clear() + self._checked_item_positions[position] = checked + elif self._choice_mode == AdapterView.CHOICE_MODE_MULTIPLE: + self._checked_item_positions[position] = checked + + def isItemChecked(self, position: int) -> bool: + return self._checked_item_positions.get(position, False) + + def getCheckedItemPositions(self) -> Dict[int, bool]: + return dict(self._checked_item_positions) + + def getCheckedItemCount(self) -> int: + return len(self._checked_item_positions) + + def getCheckedItemIds(self) -> List[int]: + return list(self._checked_id_positions.keys()) + + def performItemClick(self, view: View, position: int, id_: int) -> bool: + if self._on_item_click_listener: + self._on_item_click_listener(self, view, position, id_) + return True + return False + + +# ═══════════════════════════════════════════════════════════════ +# ListView +# ═══════════════════════════════════════════════════════════════ + +class ListView(AdapterView): + """ListView — 列表视图""" + + CHOICE_MODE_NONE = 0 + CHOICE_MODE_SINGLE = 1 + CHOICE_MODE_MULTIPLE = 2 + CHOICE_MODE_MULTIPLE_MODAL = 3 + + TRANSCRIPT_MODE_DISABLED = 0 + TRANSCRIPT_MODE_NORMAL = 1 + TRANSCRIPT_MODE_ALWAYS_SCROLL = 2 + + def __init__(self, context=None): + super().__init__(context) + self._divider: Any = None + self._divider_height: int = 0 + self._footer_divider: Any = None + self._header_divider: Any = None + self._footer_views: List[View] = [] + self._header_views: List[View] = [] + self._transcript_mode = ListView.TRANSCRIPT_MODE_DISABLED + self._items_can_focus = True + self._smooth_scroll_position = 0 + self._stack_from_bottom = False + self._selection_division = 0 + + def setAdapter(self, adapter: Any): + super().setAdapter(adapter) + + def getAdapter(self) -> Any: + return super().getAdapter() + + def setSelection(self, position: int): + super().setSelection(position) + + def smoothScrollToPosition(self, position: int): + self._smooth_scroll_position = position + + def setDivider(self, divider: Any): + self._divider = divider + + def setDividerHeight(self, height: int): + self._divider_height = height + + def getDivider(self) -> Any: + return self._divider + + def getDividerHeight(self) -> int: + return self._divider_height + + def addFooterView(self, v: View, data: Any = None, is_selectable: bool = True): + self._footer_views.append(v) + + def removeFooterView(self, v: View) -> bool: + if v in self._footer_views: + self._footer_views.remove(v) + return True + return False + + def addHeaderView(self, v: View, data: Any = None, is_selectable: bool = True): + self._header_views.append(v) + + def removeHeaderView(self, v: View) -> bool: + if v in self._header_views: + self._header_views.remove(v) + return True + return False + + def getHeaderViewsCount(self) -> int: + return len(self._header_views) + + def getFooterViewsCount(self) -> int: + return len(self._footer_views) + + def setTranscriptMode(self, mode: int): + self._transcript_mode = mode + + def getTranscriptMode(self) -> int: + return self._transcript_mode + + def setItemsCanFocus(self, items_can_focus: bool): + self._items_can_focus = items_can_focus + + def getItemsCanFocus(self) -> bool: + return self._items_can_focus + + def setStackFromBottom(self, stack_from_bottom: bool): + self._stack_from_bottom = stack_from_bottom + + def isStackFromBottom(self) -> bool: + return self._stack_from_bottom + + def getCheckItemIds(self) -> List[int]: + return list(self._checked_id_positions.keys()) + + +class GridView(AdapterView): + """GridView — 网格视图""" + + def __init__(self, context=None): + super().__init__(context) + self._num_columns: int = 1 + self._column_width: int = 0 + self._horizontal_spacing: int = 0 + self._vertical_spacing: int = 0 + self._stretch_mode: int = 0 # NO_STRETCH + self._gravity = Gravity.LEFT + + STRETCH_NO_STRETCH = 0 + STRETCH_STRETCH_COLUMN_WIDTH = 1 + STRETCH_STRETCH_SPACING = 2 + STRETCH_STRETCH_SPACING_UNIFORM = 3 + STRETCH_STRETCH_COLUMN_WIDTH_UNIFORM = 4 + + def setNumColumns(self, num_columns: int): + self._num_columns = max(1, num_columns) + + def getNumColumns(self) -> int: + return self._num_columns + + def setColumnWidth(self, column_width: int): + self._column_width = column_width + + def getColumnWidth(self) -> int: + return self._column_width + + def setHorizontalSpacing(self, horizontal_spacing: int): + self._horizontal_spacing = horizontal_spacing + + def getHorizontalSpacing(self) -> int: + return self._horizontal_spacing + + def setVerticalSpacing(self, vertical_spacing: int): + self._vertical_spacing = vertical_spacing + + def getVerticalSpacing(self) -> int: + return self._vertical_spacing + + def setStretchMode(self, stretch_mode: int): + self._stretch_mode = stretch_mode + + def getStretchMode(self) -> int: + return self._stretch_mode + + def setGravity(self, gravity: int): + self._gravity = gravity + + def getGravity(self) -> int: + return self._gravity + + def setAdapter(self, adapter: Any): + super().setAdapter(AdapterView) + + +# ═══════════════════════════════════════════════════════════════ +# RecyclerView +# ═══════════════════════════════════════════════════════════════ + +class RecyclerView(ViewGroup): + """RecyclerView — 可回收视图列表""" + + HORIZONTAL = 0 + VERTICAL = 1 + NO_POSITION = -1 + INVALID_TYPE = -1 + TOUCH_SLOP_DEFAULT = 0 + TOUCH_SLOP_PAGING = 1 + + def __init__(self, context=None): + super().__init__(context) + self._adapter: Optional["RecyclerView.Adapter"] = None + self._layout_manager: Optional["RecyclerView.LayoutManager"] = None + self._item_decorations: List[Any] = [] + self._recycler: Any = None + self._child_attached_listener: Optional[Callable] = None + self._child_detached_listener: Optional[Callable] = None + self._item_animator: Any = None + self._on_scroll_listener: Optional[Callable] = None + self._nested_scrolling_enabled = True + self._scroll_state = RecyclerView.SCROLL_STATE_IDLE + self._layout_frozen = False + self._suppress_layout = False + self._clip_to_padding = True + + SCROLL_STATE_IDLE = 0 + SCROLL_STATE_DRAGGING = 1 + SCROLL_STATE_SETTLING = 2 + + # ─── Adapter ───────────────────────────────────────────── + + class Adapter(Stub): + """RecyclerView.Adapter — 适配器""" + + def __init__(self): + super().__init__() + self._observable: Any = None + self._has_stable_ids = False + self._state_restoration_policy = 0 + + def onCreateViewHolder(self, parent: "RecyclerView", view_type: int) -> "RecyclerView.ViewHolder": + raise NotImplementedError + + def onBindViewHolder(self, holder: "RecyclerView.ViewHolder", position: int): + raise NotImplementedError + + def getItemCount(self) -> int: + return 0 + + def getItemViewType(self, position: int) -> int: + return 0 + + def getItemId(self, position: int) -> int: + return position + + def hasStableIds(self) -> bool: + return self._has_stable_ids + + def setHasStableIds(self, has_stable_ids: bool): + self._has_stable_ids = has_stable_ids + + def onAttachedToRecyclerView(self, recycler_view: "RecyclerView"): + pass + + def onDetachedFromRecyclerView(self, recycler_view: "RecyclerView"): + pass + + def onViewAttachedToWindow(self, holder: "RecyclerView.ViewHolder"): + pass + + def onViewDetachedFromWindow(self, holder: "RecyclerView.ViewHolder"): + pass + + def onViewRecycled(self, holder: "RecyclerView.ViewHolder"): + pass + + def onFailedToRecycleView(self, holder: "RecyclerView.ViewHolder") -> bool: + return False + + def notifyDataSetChanged(self): + pass + + def notifyItemChanged(self, position: int): + pass + + def notifyItemInserted(self, position: int): + pass + + def notifyItemRemoved(self, position: int): + pass + + def notifyItemMoved(self, from_position: int, to_position: int): + pass + + def notifyItemRangeChanged(self, position_start: int, item_count: int): + pass + + def notifyItemRangeInserted(self, position_start: int, item_count: int): + pass + + def notifyItemRangeRemoved(self, position_start: int, item_count: int): + pass + + def getStateRestorationPolicy(self) -> int: + return 0 + + # ─── ViewHolder ─────────────────────────────────────────── + + class ViewHolder(Stub): + """RecyclerView.ViewHolder — 视图持有者""" + + FLAG_BOUND = 1 + FLAG_NOT_RECYCLABLE = 2 + FLAG_RETURNED_FROM_SCRAP = 4 + FLAG_CHANGED = 8 + FLAG_IGNORE = 16 + FLAG_TMP_DETACHED = 32 + FLAG_UPDATE = 64 + FLAG_ADAPTER_POSITION_UNKNOWN = 128 + FLAG_ADAPTER_FULLUPDATE = 256 + FLAG_MOVED = 512 + FLAG_APPEARED_IN_PRE_LAYOUT = 1024 + + def __init__(self, item_view: View): + super().__init__() + self.itemView = item_view + self._position = RecyclerView.NO_POSITION + self._old_position = RecyclerView.NO_POSITION + self._item_id = RecyclerView.NO_ID + self._item_view_type = RecyclerView.INVALID_TYPE + self._flags = 0 + self._pre_layout_position = RecyclerView.NO_POSITION + self._is_recyclable = True + self._scrap_container: Optional[RecyclerView] = None + self._shadowed_holder: Optional["RecyclerView.ViewHolder"] = None + self._shadowing_holder: Optional["RecyclerView.ViewHolder"] = None + + NO_ID = -1 + + def getAdapterPosition(self) -> int: + return self._position + + def getLayoutPosition(self) -> int: + return self._position + + def getOldPosition(self) -> int: + return self._old_position + + def getItemId(self) -> int: + return self._item_id + + def getItemViewType(self) -> int: + return self._item_view_type + + def setIsRecyclable(self, recyclable: bool): + self._is_recyclable = recyclable + + def isRecyclable(self) -> bool: + return self._is_recyclable + + def getBindingAdapterPosition(self) -> int: + return self._position + + # ─── LayoutManager ──────────────────────────────────────── + + class LayoutManager(Stub): + """RecyclerView.LayoutManager — 布局管理器""" + + def __init__(self): + super().__init__() + self._recycler_view: Optional["RecyclerView"] = None + self._child_count = 0 + self._width = 0 + self._height = 0 + self._auto_measure = True + self._item_prefetch_enabled = True + + def onLayoutChildren(self, recycler: Any, state: Any): + pass + + def getChildCount(self) -> int: + return self._child_count + + def getPosition(self, view: View) -> int: + if isinstance(view, View): + return 0 + return RecyclerView.NO_POSITION + + def scrollToPosition(self, position: int): + pass + + def smoothScrollToPosition(self, recycler_view: "RecyclerView", state: Any, position: int): + pass + + def onAdapterChanged(self, old_adapter: Any, new_adapter: Any): + pass + + def generateDefaultLayoutParams(self) -> ViewGroup.LayoutParams: + return ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT) + + def getWidth(self) -> int: + return self._width + + def getHeight(self) -> int: + return self._height + + def setAutoMeasureEnabled(self, enabled: bool): + self._auto_measure = enabled + + def isAutoMeasureEnabled(self) -> bool: + return self._auto_measure + + def requestLayout(self): + pass + + def requestSimpleAnimationsInNextLayout(self): + pass + + def assertNotInLayoutOrScroll(self, tag: str = ""): + pass + + def getPaddingLeft(self) -> int: + return 0 + + def getPaddingTop(self) -> int: + return 0 + + def getPaddingRight(self) -> int: + return 0 + + def getPaddingBottom(self) -> int: + return 0 + + # ─── ItemDecoration ────────────────────────────────────── + + class ItemDecoration(Stub): + """RecyclerView.ItemDecoration — 条目装饰""" + + def onDraw(self, c: Any, parent: "RecyclerView", state: Any): + pass + + def onDrawOver(self, c: Any, parent: "RecyclerView", state: Any): + pass + + def getItemOffsets(self, out_rect: Any, view: View, parent: "RecyclerView", state: Any): + if hasattr(out_rect, "set"): + out_rect.set(0, 0, 0, 0) + + # ─── RecycledViewPool ───────────────────────────────────── + + class RecycledViewPool(Stub): + """RecyclerView.RecycledViewPool — 回收视图池""" + + def __init__(self): + super().__init__() + self._max_recycled_views = 5 + + def getRecycledView(self, view_type: int) -> Optional["RecyclerView.ViewHolder"]: + return None + + def putRecycledView(self, scrap: "RecyclerView.ViewHolder"): + pass + + def setMaxRecycledViews(self, view_type: int, max_count: int): + self._max_recycled_views = max_count + + def clear(self): + pass + + # ─── Recycler ───────────────────────────────────────────── + + class Recycler(Stub): + """RecyclerView.Recycler — 回收器""" + + def __init__(self): + super().__init__() + self._view_cache: Dict[int, List["RecyclerView.ViewHolder"]] = {} + + def getViewForPosition(self, position: int) -> View: + raise AndroidOnlyError("RecyclerView.Recycler.getViewForPosition") + + def recycleView(self, view: View): + pass + + def getRecycledViewPool(self) -> "RecyclerView.RecycledViewPool": + return RecyclerView.RecycledViewPool() + + def setViewCacheSize(self, view_count: int): + pass + + def clear(self): + self._view_cache.clear() + + # ─── RecyclerView Instance Methods ─────────────────────── + + def setAdapter(self, adapter: Optional["RecyclerView.Adapter"]): + self._adapter = adapter + + def getAdapter(self) -> Optional["RecyclerView.Adapter"]: + return self._adapter + + def setLayoutManager(self, layout_manager: Optional["RecyclerView.LayoutManager"]): + self._layout_manager = layout_manager + if layout_manager: + layout_manager._recycler_view = self + + def getLayoutManager(self) -> Optional["RecyclerView.LayoutManager"]: + return self._layout_manager + + def addItemDecoration(self, decoration: "RecyclerView.ItemDecoration", index: int = -1): + if index < 0 or index >= len(self._item_decorations): + self._item_decorations.append(decoration) + else: + self._item_decorations.insert(index, decoration) + + def removeItemDecoration(self, decoration: "RecyclerView.ItemDecoration"): + if decoration in self._item_decorations: + self._item_decorations.remove(decoration) + + def getItemDecorationAt(self, index: int) -> "RecyclerView.ItemDecoration": + return self._item_decorations[index] + + def getItemDecorationCount(self) -> int: + return len(self._item_decorations) + + def swapAdapter(self, adapter: Optional["RecyclerView.Adapter"], remove_and_recycle: bool): + self._adapter = adapter + + def getRecycledViewPool(self) -> "RecyclerView.RecycledViewPool": + return RecyclerView.RecycledViewPool() + + def setRecycledViewPool(self, pool: "RecyclerView.RecycledViewPool"): + pass + + def getRecycler(self) -> "RecyclerView.Recycler": + return RecyclerView.Recycler() + + def setItemAnimator(self, animator: Any): + self._item_animator = animator + + def getItemAnimator(self) -> Any: + return self._item_animator + + def setOnScrollListener(self, listener: Optional[Callable]): + self._on_scroll_listener = listener + + def addOnScrollListener(self, listener: Callable): + pass + + def removeOnScrollListener(self, listener: Callable): + pass + + def clearOnScrollListeners(self): + pass + + def addOnChildAttachStateChangeListener(self, listener: Callable): + self._child_attached_listener = listener + + def removeOnChildAttachStateChangeListener(self, listener: Callable): + pass + + def setHasFixedSize(self, has_fixed_size: bool): + pass + + def hasFixedSize(self) -> bool: + return False + + def setNestedScrollingEnabled(self, enabled: bool): + self._nested_scrolling_enabled = enabled + + def isNestedScrollingEnabled(self) -> bool: + return self._nested_scrolling_enabled + + def startNestedScroll(self, axes: int) -> bool: + return False + + def stopNestedScroll(self): + pass + + def hasNestedScrollingParent(self) -> bool: + return False + + def dispatchNestedPreScroll(self, dx: int, dy: int, consumed: List[int], offset: List[int]) -> bool: + return False + + def dispatchNestedScroll(self, dx_consumed: int, dy_consumed: int, + dx_unconsumed: int, dy_unconsumed: int, + offset: List[int]) -> bool: + return False + + def dispatchNestedPreFling(self, velocity_x: float, velocity_y: float) -> bool: + return False + + def dispatchNestedFling(self, velocity_x: float, velocity_y: float, consumed: bool) -> bool: + return False + + def stopScroll(self): + pass + + def getScrollState(self) -> int: + return self._scroll_state + + def setLayoutFrozen(self, frozen: bool): + self._layout_frozen = frozen + + def isLayoutFrozen(self) -> bool: + return self._layout_frozen + + def smoothScrollBy(self, dx: int, dy: int): + pass + + def scrollBy(self, x: int, y: int): + pass + + def computeHorizontalScrollOffset(self) -> int: + return 0 + + def computeHorizontalScrollRange(self) -> int: + return 0 + + def computeVerticalScrollOffset(self) -> int: + return 0 + + def computeVerticalScrollRange(self) -> int: + return 0 + + def findViewHolderForAdapterPosition(self, position: int) -> Optional["RecyclerView.ViewHolder"]: + return None + + def findViewHolderForLayoutPosition(self, position: int) -> Optional["RecyclerView.ViewHolder"]: + return None + + def findViewHolderForItemId(self, id_: int) -> Optional["RecyclerView.ViewHolder"]: + return None + + NO_ID = -1 + + +# 为 __all__ 导出创建别名 +RecyclerViewAdapter = RecyclerView.Adapter +RecyclerViewViewHolder = RecyclerView.ViewHolder +RecyclerViewLayoutManager = RecyclerView.LayoutManager + + +class LinearLayoutManager(RecyclerView.LayoutManager): + """LinearLayoutManager — 线性布局管理器""" + + HORIZONTAL = 0 + VERTICAL = 1 + + def __init__(self, context: Any = None, orientation: int = VERTICAL, reverse_layout: bool = False): + super().__init__() + self._orientation = orientation + self._reverse_layout = reverse_layout + self._stack_from_end = False + self._item_count = 0 + + def setOrientation(self, orientation: int): + self._orientation = orientation + + def getOrientation(self) -> int: + return self._orientation + + def setReverseLayout(self, reverse_layout: bool): + self._reverse_layout = reverse_layout + + def getReverseLayout(self) -> bool: + return self._reverse_layout + + def setStackFromEnd(self, stack_from_end: bool): + self._stack_from_end = stack_from_end + + def getStackFromEnd(self) -> bool: + return self._stack_from_end + + def generateDefaultLayoutParams(self) -> ViewGroup.LayoutParams: + return ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT) + + def onLayoutChildren(self, recycler: Any, state: Any): + pass + + +class GridLayoutManager(LinearLayoutManager): + """GridLayoutManager — 网格布局管理器""" + + def __init__(self, context: Any = None, span_count: int = 1, + orientation: int = LinearLayoutManager.VERTICAL, + reverse_layout: bool = False): + super().__init__(context, orientation, reverse_layout) + self._span_count = max(1, span_count) + + class SpanSizeLookup(Stub): + """GridLayoutManager.SpanSizeLookup — 跨度大小查找""" + + def __init__(self): + super().__init__() + self._span_index_cache = {} + + def getSpanSize(self, position: int) -> int: + return 1 + + def getSpanIndex(self, position: int, span_count: int) -> int: + return position % span_count + + def getSpanGroupIndex(self, position: int, span_count: int) -> int: + return position // span_count + + def invalidateSpanIndexCache(self): + self._span_index_cache.clear() + + def setSpanCount(self, span_count: int): + self._span_count = max(1, span_count) + + def getSpanCount(self) -> int: + return self._span_count + + def setSpanSizeLookup(self, span_size_lookup: SpanSizeLookup): + pass + + def getSpanSizeLookup(self) -> SpanSizeLookup: + return GridLayoutManager.SpanSizeLookup() + + +class StaggeredGridLayoutManager(RecyclerView.LayoutManager): + """StaggeredGridLayoutManager — 瀑布流布局管理器""" + + HORIZONTAL = 0 + VERTICAL = 1 + + GAP_HANDLING_NONE = 0 + GAP_HANDLING_LAZY = 1 + GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS = 2 + + def __init__(self, span_count: int = 1, orientation: int = VERTICAL): + super().__init__() + self._span_count = max(1, span_count) + self._orientation = orientation + self._gap_strategy = StaggeredGridLayoutManager.GAP_HANDLING_LAZY + + def setSpanCount(self, span_count: int): + self._span_count = max(1, span_count) + + def getSpanCount(self) -> int: + return self._span_count + + def setOrientation(self, orientation: int): + self._orientation = orientation + + def getOrientation(self) -> int: + return self._orientation + + def setGapStrategy(self, gap_strategy: int): + self._gap_strategy = gap_strategy + + def getGapStrategy(self) -> int: + return self._gap_strategy + + +# ═══════════════════════════════════════════════════════════════ +# LinearLayout +# ═══════════════════════════════════════════════════════════════ + +class LinearLayout(ViewGroup): + """LinearLayout — 线性布局""" + + HORIZONTAL = 0 + VERTICAL = 1 + SHOW_DIVIDER_NONE = 0 + SHOW_DIVIDER_BEGINNING = 1 + SHOW_DIVIDER_MIDDLE = 2 + SHOW_DIVIDER_END = 4 + + def __init__(self, context=None): + super().__init__(context) + self._orientation = LinearLayout.VERTICAL + self._gravity = Gravity.NO_GRAVITY + self._weight_sum: float = 0.0 + self._baseline_aligned = True + self._baseline_aligned_child_index = -1 + self._divider: Any = None + self._divider_padding: int = 0 + self._divider_show: int = LinearLayout.SHOW_DIVIDER_NONE + self._total_length: int = 0 + + def setOrientation(self, orientation: int): + self._orientation = orientation + + def getOrientation(self) -> int: + return self._orientation + + def setGravity(self, gravity: int): + self._gravity = gravity + + def getGravity(self) -> int: + return self._gravity + + def setWeightSum(self, weight_sum: float): + self._weight_sum = weight_sum + + def getWeightSum(self) -> float: + return self._weight_sum + + def setBaselineAligned(self, baseline_aligned: bool): + self._baseline_aligned = baseline_aligned + + def isBaselineAligned(self) -> bool: + return self._baseline_aligned + + def setDividerDrawable(self, divider: Any): + self._divider = divider + + def setShowDividers(self, show_dividers: int): + self._divider_show = show_dividers + + def getShowDividers(self) -> int: + return self._divider_show + + def getDividerDrawable(self) -> Any: + return self._divider + + def setDividerPadding(self, padding: int): + self._divider_padding = padding + + def getDividerPadding(self) -> int: + return self._divider_padding + + def getBaseline(self) -> int: + return -1 + + def onMeasure(self, width_measure_spec: int, height_measure_spec: int): + from .view import MeasureSpec + w = MeasureSpec.getSize(width_measure_spec) + h = MeasureSpec.getSize(height_measure_spec) + self.setMeasuredDimension(w, h) + + def onLayout(self, changed: bool, l: int, t: int, r: int, b: int): + pass + + class LayoutParams(ViewGroup.MarginLayoutParams): + """LinearLayout.LayoutParams — 线性布局参数""" + + def __init__(self, width: int = ViewGroup.LayoutParams.WRAP_CONTENT, + height: int = ViewGroup.LayoutParams.WRAP_CONTENT, + weight: float = 0.0): + super().__init__(width, height) + self.weight = weight + self.gravity = -1 + + def setLayoutWeight(self, weight: float): + self.weight = weight + + +# ═══════════════════════════════════════════════════════════════ +# RelativeLayout +# ═══════════════════════════════════════════════════════════════ + +class RelativeLayout(ViewGroup): + """RelativeLayout — 相对布局""" + + TRUE = -1 + FALSE = 0 + + # 规则常量 + ALIGN_PARENT_LEFT = 0 + ALIGN_PARENT_TOP = 1 + ALIGN_PARENT_RIGHT = 2 + ALIGN_PARENT_BOTTOM = 3 + ALIGN_PARENT_START = 4 + ALIGN_PARENT_END = 5 + CENTER_IN_PARENT = 6 + CENTER_HORIZONTAL = 7 + CENTER_VERTICAL = 8 + ABOVE = 9 + BELOW = 10 + LEFT_OF = 11 + RIGHT_OF = 12 + ALIGN_LEFT = 13 + ALIGN_TOP = 14 + ALIGN_RIGHT = 15 + ALIGN_BOTTOM = 16 + ALIGN_BASELINE = 17 + ALIGN_WITH_PARENT_IF_MISSING = 18 + START_OF = 19 + END_OF = 20 + ALIGN_START = 21 + ALIGN_END = 22 + + def __init__(self, context=None): + super().__init__(context) + self._gravity = Gravity.NO_GRAVITY + self._ignore_gravity_of_view_ids: List[int] = [] + self._has_left_weight = False + self._has_right_weight = False + self._has_top_weight = False + self._has_bottom_weight = False + + def setGravity(self, gravity: int): + self._gravity = gravity + + def getGravity(self) -> int: + return self._gravity + + def setHorizontalGravity(self, horizontal_gravity: int): + pass + + def setVerticalGravity(self, vertical_gravity: int): + pass + + def onMeasure(self, width_measure_spec: int, height_measure_spec: int): + from .view import MeasureSpec + w = MeasureSpec.getSize(width_measure_spec) + h = MeasureSpec.getSize(height_measure_spec) + self.setMeasuredDimension(w, h) + + def onLayout(self, changed: bool, l: int, t: int, r: int, b: int): + pass + + class LayoutParams(ViewGroup.MarginLayoutParams): + """RelativeLayout.LayoutParams — 相对布局参数""" + + def __init__(self, width: int = ViewGroup.LayoutParams.WRAP_CONTENT, + height: int = ViewGroup.LayoutParams.WRAP_CONTENT): + super().__init__(width, height) + self._rules: Dict[int, int] = {} + self.align_with_parent = False + + def addRule(self, verb: int, subject: int = -1): + self._rules[verb] = subject + + def removeRule(self, verb: int): + self._rules.pop(verb, None) + + def getRule(self, verb: int) -> int: + return self._rules.get(verb, 0) + + def getRules(self) -> Dict[int, int]: + return dict(self._rules) + + def addLayoutRules(self, layout_rules: int): + pass + + def removeLayoutRule(self, layout_rule: int): + pass + + +# ═══════════════════════════════════════════════════════════════ +# FrameLayout +# ═══════════════════════════════════════════════════════════════ + +class FrameLayout(ViewGroup): + """FrameLayout — 帧布局""" + + def __init__(self, context=None): + super().__init__(context) + self._foreground: Any = None + self._foreground_gravity = Gravity.FILL + self._measure_all_children = False + self._foreground_bounds_padding: int = 0 + self._foreground_in_padding = False + + def setForeground(self, drawable: Any): + self._foreground = drawable + + def getForeground(self) -> Any: + return self._foreground + + def setForegroundGravity(self, gravity: int): + self._foreground_gravity = gravity + + def getForegroundGravity(self) -> int: + return self._foreground_gravity + + def setMeasureAllChildren(self, measure_all: bool): + self._measure_all_children = measure_all + + def getMeasureAllChildren(self) -> bool: + return self._measure_all_children + + def setForegroundBoundsPadding(self, padding: int): + self._foreground_bounds_padding = padding + + def onMeasure(self, width_measure_spec: int, height_measure_spec: int): + from .view import MeasureSpec + w = MeasureSpec.getSize(width_measure_spec) + h = MeasureSpec.getSize(height_measure_spec) + if self._measure_all_children: + for child in self._children: + child.measure(width_measure_spec, height_measure_spec) + self.setMeasuredDimension(w, h) + + def onLayout(self, changed: bool, l: int, t: int, r: int, b: int): + pass + + def gatherTransparentRegion(self, region: Any) -> bool: + return False + + +# ═══════════════════════════════════════════════════════════════ +# TableLayout / TableRow +# ═══════════════════════════════════════════════════════════════ + +class TableLayout(LinearLayout): + """TableLayout — 表格布局""" + + def __init__(self, context=None): + super().__init__(context) + self._stretchable_columns: List[bool] = [] + self._shrinkable_columns: List[bool] = [] + self._collapsed_columns: List[bool] = [] + self._max_columns: int = 0 + + def setStretchAllColumns(self, stretch_all: bool): + pass + + def setShrinkAllColumns(self, shrink_all: bool): + pass + + def setColumnStretchable(self, column_index: int, stretchable: bool): + pass + + def setColumnShrinkable(self, column_index: int, shrinkable: bool): + pass + + def setColumnCollapsed(self, column_index: int, collapsed: bool): + pass + + def isColumnStretchable(self, column_index: int) -> bool: + return False + + def isColumnShrinkable(self, column_index: int) -> bool: + return False + + def isColumnCollapsed(self, column_index: int) -> bool: + return False + + +class TableRow(LinearLayout): + """TableRow — 表格行""" + + def __init__(self, context=None): + super().__init__(context) + self._layout_params: Dict[int, Any] = {} + + class LayoutParams(LinearLayout.LayoutParams): + """TableRow.LayoutParams — 表格行布局参数""" + + def __init__(self, width: int = ViewGroup.LayoutParams.WRAP_CONTENT, + height: int = ViewGroup.LayoutParams.WRAP_CONTENT): + super().__init__(width, height) + self.column = -1 + self.span = 1 + + +# ═══════════════════════════════════════════════════════════════ +# ScrollView / HorizontalScrollView +# ═══════════════════════════════════════════════════════════════ + +class ScrollView(FrameLayout): + """ScrollView — 滚动视图""" + + def __init__(self, context=None): + super().__init__(context) + self._fill_viewport = False + self._smooth_scrolling_enabled = True + self._max_scroll_amount = 0 + self._scroll_position = 0 + self._is_layout_after_scroll = False + + def setFillViewport(self, fill_viewport: bool): + self._fill_viewport = fill_viewport + + def isFillViewport(self) -> bool: + return self._fill_viewport + + def setSmoothScrollingEnabled(self, enabled: bool): + self._smooth_scrolling_enabled = enabled + + def isSmoothScrollingEnabled(self) -> bool: + return self._smooth_scrolling_enabled + + def fullScroll(self, direction: int) -> bool: + return True + + def arrowScroll(self, direction: int) -> bool: + return True + + def smoothScrollTo(self, x: int, y: int): + self._scroll_position = y + + def smoothScrollBy(self, dx: int, dy: int): + self._scroll_position += dy + + def pageScroll(self, direction: int) -> bool: + return True + + def getMaxScrollAmount(self) -> int: + return self._max_scroll_amount + + def computeScrollDeltaToGetChildRectOnScreen(self, rect: Any) -> int: + return 0 + + def isLayoutAfterScroll(self) -> bool: + return self._is_layout_after_scroll + + +class HorizontalScrollView(FrameLayout): + """HorizontalScrollView — 水平滚动视图""" + + def __init__(self, context=None): + super().__init__(context) + self._fill_viewport = False + + def setFillViewport(self, fill_viewport: bool): + self._fill_viewport = fill_viewport + + def isFillViewport(self) -> bool: + return self._fill_viewport + + def fullScroll(self, direction: int) -> bool: + return True + + def arrowScroll(self, direction: int) -> bool: + return True + + def smoothScrollTo(self, x: int, y: int): + pass + + def smoothScrollBy(self, dx: int, dy: int): + pass + + def pageScroll(self, direction: int) -> bool: + return True + + +# ═══════════════════════════════════════════════════════════════ +# WebView +# ═══════════════════════════════════════════════════════════════ + +class WebView(ViewGroup): + """WebView — 网页视图""" + + # 布局模式 + NORMAL = 0 + SMALL_SCREEN = 1 + LARGE_SCREEN = 2 + + def __init__(self, context=None): + super().__init__(context) + self._settings: "WebSettings" = WebSettings() + self._web_view_client: Optional[Callable] = None + self._web_chrome_client: Optional[Callable] = None + self._can_go_back: bool = False + self._can_go_forward: bool = False + self._current_url: str = "" + self._history: List[str] = [] + self._current_index: int = -1 + self._download_listener: Optional[Callable] = None + self._find_listener: Optional[Callable] = None + self._picture_listener: Optional[Callable] = None + + def loadUrl(self, url: str, additional_headers: Optional[Dict[str, str]] = None): + self._current_url = url + self._history = self._history[:self._current_index + 1] + self._history.append(url) + self._current_index = len(self._history) - 1 + self._can_go_back = self._current_index > 0 + self._can_go_forward = False + + def loadData(self, data: str, mime_type: str = "text/html", + encoding: str = "utf-8"): + self._current_url = "about:blank" + + def loadDataWithBaseURL(self, base_url: str, data: str, mime_type: str, + encoding: str, history_url: str): + self._current_url = base_url or "about:blank" + + def getSettings(self) -> "WebSettings": + return self._settings + + def setWebViewClient(self, client: Any): + self._web_view_client = client + + def getWebViewClient(self) -> Any: + return self._web_view_client + + def setWebChromeClient(self, client: Any): + self._web_chrome_client = client + + def getWebChromeClient(self) -> Any: + return self._web_chrome_client + + def goBack(self): + if self._can_go_back: + self._current_index -= 1 + self._current_url = self._history[self._current_index] + self._can_go_back = self._current_index > 0 + self._can_go_forward = True + + def goForward(self): + if self._can_go_forward: + self._current_index += 1 + self._current_url = self._history[self._current_index] + self._can_go_forward = self._current_index < len(self._history) - 1 + self._can_go_back = True + + def reload(self): + pass + + def canGoBack(self) -> bool: + return self._can_go_back + + def canGoForward(self) -> bool: + return self._can_go_forward + + def clearHistory(self): + self._history.clear() + self._current_index = -1 + self._can_go_back = False + self._can_go_forward = False + + def destroy(self): + pass + + def onPause(self): + pass + + def onResume(self): + pass + + def pauseTimers(self): + pass + + def resumeTimers(self): + pass + + def getUrl(self) -> Optional[str]: + return self._current_url if self._current_url else None + + def getTitle(self) -> Optional[str]: + return None + + def evaluateJavascript(self, script: str, callback: Optional[Callable] = None): + if callback: + callback("") + + def addJavascriptInterface(self, obj: Any, name: str): + pass + + def removeJavascriptInterface(self, name: str): + pass + + def canGoBackOrForward(self, steps: int) -> bool: + target = self._current_index + steps + return 0 <= target < len(self._history) + + def goBackOrForward(self, steps: int): + target = self._current_index + steps + if 0 <= target < len(self._history): + self._current_index = target + self._current_url = self._history[target] + self._can_go_back = target > 0 + self._can_go_forward = target < len(self._history) - 1 + + def setDownloadListener(self, listener: Optional[Callable]): + self._download_listener = listener + + def setFindListener(self, listener: Optional[Callable]): + self._find_listener = listener + + class WebViewTransport: + def setWebView(self, web_view: "WebView"): + pass + def getWebView(self) -> Optional["WebView"]: + return None + + class HitTestResult: + UNKNOWN_TYPE = 0 + ANCHOR_TYPE = 1 + EDIT_TEXT_TYPE = 2 + IMAGE_TYPE = 3 + SRC_ANCHOR_TYPE = 4 + SRC_IMAGE_ANCHOR_TYPE = 5 + PHONE_TYPE = 6 + GEO_TYPE = 7 + EMAIL_TYPE = 8 + + def __init__(self): + self._type = WebView.HitTestResult.UNKNOWN_TYPE + self._extra = "" + + def getType(self) -> int: + return self._type + def getExtra(self) -> str: + return self._extra + + def getHitTestResult(self) -> HitTestResult: + return WebView.HitTestResult() + + def findAllAsync(self, find_str: str): + pass + + def findNext(self, forward: bool): + pass + + def clearMatches(self): + pass + + def zoomIn(self) -> bool: + return False + + def zoomOut(self) -> bool: + return False + + def setInitialScale(self, scale_in_percent: int): + pass + + def getScale(self) -> float: + return 1.0 + + def setPictureListener(self, listener: Optional[Callable]): + self._picture_listener = listener + + +class WebSettings(Stub): + """WebSettings — WebView 设置""" + + # 渲染模式 + RENDER_GOES_BROWSER = 0 + RENDER_GOES_SURFACE_VIEW = 1 + RENDER_GOES_DISPLAY_LIST = 2 + RENDER_GOES_GPU = 3 + RENDER_GOES_SURFACE = 4 + + # 布局算法 + LAYOUT_ALGORITHM_NORMAL = 0 + LAYOUT_ALGORITHM_SINGLE_COLUMN = 1 + LAYOUT_ALGORITHM_NARROW_COLUMNS = 2 + LAYOUT_ALGORITHM_TEXT_AUTOSIZING = 3 + + def __init__(self): + super().__init__() + self._javascript_enabled = False + self._dom_storage_enabled = False + self._allow_file_access = True + self._built_in_zoom_controls = True + self._display_zoom_controls = True + self._load_with_overview_mode = False + self._support_zoom = True + self._use_wide_view_port = False + self._database_enabled = False + self._allow_content_access = True + self._cache_mode = -1 # LOAD_DEFAULT + self._user_agent_string: str = "Mozilla/5.0 (Linux; Android 13)" + self._default_font_size = 16 + self._minimum_font_size = 8 + self._text_zoom = 100 + self._standard_font_family = "sans-serif" + self._serif_font_family = "serif" + self._sans_serif_font_family = "sans-serif" + self._fixed_font_family = "monospace" + self._cursive_font_family = "cursive" + self._fantasy_font_family = "fantasy" + self._default_encoding = "utf-8" + self._layout_algorithm = WebSettings.LAYOUT_ALGORITHM_NORMAL + self._block_network_image = False + self._block_network_loads = False + self._mixed_content_mode = 0 # MIXED_CONTENT_ALWAYS_ALLOW + self._offscreen_pre_raster = False + self._safe_browsing_enabled = True + + def setJavaScriptEnabled(self, enabled: bool): + self._javascript_enabled = enabled + + def getJavaScriptEnabled(self) -> bool: + return self._javascript_enabled + + def setDomStorageEnabled(self, enabled: bool): + self._dom_storage_enabled = enabled + + def getDomStorageEnabled(self) -> bool: + return self._dom_storage_enabled + + def setAllowFileAccess(self, allow: bool): + self._allow_file_access = allow + + def getAllowFileAccess(self) -> bool: + return self._allow_file_access + + def setBuiltInZoomControls(self, enabled: bool): + self._built_in_zoom_controls = enabled + + def getBuiltInZoomControls(self) -> bool: + return self._built_in_zoom_controls + + def setDisplayZoomControls(self, enabled: bool): + self._display_zoom_controls = enabled + + def getDisplayZoomControls(self) -> bool: + return self._display_zoom_controls + + def setSupportZoom(self, support: bool): + self._support_zoom = support + + def supportZoom(self) -> bool: + return self._support_zoom + + def setLoadWithOverviewMode(self, overview: bool): + self._load_with_overview_mode = overview + + def getLoadWithOverviewMode(self) -> bool: + return self._load_with_overview_mode + + def setUseWideViewPort(self, use: bool): + self._use_wide_view_port = use + + def getUseWideViewPort(self) -> bool: + return self._use_wide_view_port + + def setDatabaseEnabled(self, enabled: bool): + self._database_enabled = enabled + + def getDatabaseEnabled(self) -> bool: + return self._database_enabled + + def setAllowContentAccess(self, allow: bool): + self._allow_content_access = allow + + def getAllowContentAccess(self) -> bool: + return self._allow_content_access + + def setCacheMode(self, mode: int): + self._cache_mode = mode + + def getCacheMode(self) -> int: + return self._cache_mode + + LOAD_DEFAULT = -1 + LOAD_CACHE_ELSE_NETWORK = 1 + LOAD_NO_CACHE = 2 + LOAD_CACHE_ONLY = 3 + + def setUserAgentString(self, ua: str): + self._user_agent_string = ua + + def getUserAgentString(self) -> str: + return self._user_agent_string + + def setDefaultFontSize(self, size: int): + self._default_font_size = size + + def getDefaultFontSize(self) -> int: + return self._default_font_size + + def setMinimumFontSize(self, size: int): + self._minimum_font_size = size + + def getMinimumFontSize(self) -> int: + return self._minimum_font_size + + def setTextZoom(self, text_zoom: int): + self._text_zoom = text_zoom + + def getTextZoom(self) -> int: + return self._text_zoom + + def setStandardFontFamily(self, font: str): + self._standard_font_family = font + + def setSerifFontFamily(self, font: str): + self._serif_font_family = font + + def setSansSerifFontFamily(self, font: str): + self._sans_serif_font_family = font + + def setFixedFontFamily(self, font: str): + self._fixed_font_family = font + + def setCursiveFontFamily(self, font: str): + self._cursive_font_family = font + + def setFantasyFontFamily(self, font: str): + self._fantasy_font_family = font + + def setDefaultTextEncodingName(self, encoding: str): + self._default_encoding = encoding + + def getDefaultTextEncodingName(self) -> str: + return self._default_encoding + + def setLayoutAlgorithm(self, algorithm: int): + self._layout_algorithm = algorithm + + def getLayoutAlgorithm(self) -> int: + return self._layout_algorithm + + def setBlockNetworkImage(self, block: bool): + self._block_network_image = block + + def getBlockNetworkImage(self) -> bool: + return self._block_network_image + + def setBlockNetworkLoads(self, block: bool): + self._block_network_loads = block + + def getBlockNetworkLoads(self) -> bool: + return self._block_network_loads + + def setMixedContentMode(self, mode: int): + self._mixed_content_mode = mode + + def getMixedContentMode(self) -> int: + return self._mixed_content_mode + + MIXED_CONTENT_ALWAYS_ALLOW = 0 + MIXED_CONTENT_NEVER_ALLOW = 1 + MIXED_CONTENT_COMPATIBILITY_MODE = 2 + + def setOffscreenPreRaster(self, enabled: bool): + self._offscreen_pre_raster = enabled + + def getOffscreenPreRaster(self) -> bool: + return self._offscreen_pre_raster + + def setSafeBrowsingEnabled(self, enabled: bool): + self._safe_browsing_enabled = enabled + + def getSafeBrowsingEnabled(self) -> bool: + return self._safe_browsing_enabled + + def setNeedInitialFocus(self, flag: bool): + pass + + +# ═══════════════════════════════════════════════════════════════ +# ViewAnimator / ViewFlipper / ViewSwitcher etc. +# ═══════════════════════════════════════════════════════════════ + +class ViewAnimator(FrameLayout): + """ViewAnimator — 视图动画容器""" + + def __init__(self, context=None): + super().__init__(context) + self._which_child: int = 0 + self._animate_first_view: bool = False + self._in_animation: Any = None + self._out_animation: Any = None + + def setDisplayedChild(self, which_child: int): + if 0 <= which_child < self.getChildCount(): + self._which_child = which_child + + def getDisplayedChild(self) -> int: + return self._which_child + + def showNext(self): + if self._which_child < self.getChildCount() - 1: + self._which_child += 1 + + def showPrevious(self): + if self._which_child > 0: + self._which_child -= 1 + + def setInAnimation(self, in_animation: Any): + self._in_animation = in_animation + + def getInAnimation(self) -> Any: + return self._in_animation + + def setOutAnimation(self, out_animation: Any): + self._out_animation = out_animation + + def getOutAnimation(self) -> Any: + return self._out_animation + + def setAnimateFirstView(self, animate: bool): + self._animate_first_view = animate + + def getAnimateFirstView(self) -> bool: + return self._animate_first_view + + def getBaseline(self) -> int: + return -1 + + +class ViewFlipper(ViewAnimator): + """ViewFlipper — 视图翻转器""" + + def __init__(self, context=None): + super().__init__(context) + self._flip_interval: int = 3000 + self._auto_start: bool = False + self._running: bool = False + self._flip_timer: Any = None + + def setFlipInterval(self, milliseconds: int): + self._flip_interval = milliseconds + + def getFlipInterval(self) -> int: + return self._flip_interval + + def setAutoStart(self, auto_start: bool): + self._auto_start = auto_start + + def isAutoStart(self) -> bool: + return self._auto_start + + def startFlipping(self): + self._running = True + + def stopFlipping(self): + self._running = False + + def isFlipping(self) -> bool: + return self._running + + def onDetachedFromWindow(self): + super().onDetachedFromWindow() + self._running = False + + def onAttachedToWindow(self): + super().onAttachedToWindow() + if self._auto_start: + self._running = True + + +class ViewSwitcher(ViewAnimator): + """ViewSwitcher — 视图切换器""" + + def __init__(self, context=None): + super().__init__(context) + self._factory: Optional[Callable] = None + + class ViewFactory: + def makeView(self) -> View: + return View() + + def setFactory(self, factory: ViewFactory): + self._factory = factory + + def getCurrentView(self) -> Optional[View]: + return self.getChildAt(self._which_child) + + def getNextView(self) -> Optional[View]: + next_index = self._which_child + 1 + if next_index < self.getChildCount(): + return self.getChildAt(next_index) + return None + + def setInAnimation(self, in_animation: Any): + super().setInAnimation(in_animation) + + def setOutAnimation(self, out_animation: Any): + super().setOutAnimation(out_animation) + + +class ImageSwitcher(ViewSwitcher): + """ImageSwitcher — 图片切换器""" + + def setImageDrawable(self, drawable: Any): + pass + + def setImageResource(self, resid: int): + pass + + def setImageURI(self, uri: Any): + pass + + def setImageBitmap(self, bitmap: Any): + pass + + +class TextSwitcher(ViewSwitcher): + """TextSwitcher — 文本切换器""" + + def setCurrentText(self, text: str): + if self.getChildCount() > 0: + child = self.getChildAt(0) + if hasattr(child, "setText"): + child.setText(text) + + def setText(self, text: str): + self.setCurrentText(text) + + +class AdapterViewAnimator(AdapterView): + """AdapterViewAnimator — 适配器视图动画器""" + + def __init__(self, context=None): + super().__init__(context) + self._in_animation: Any = None + self._out_animation: Any = None + self._displayed_child: int = 0 + self._loop_views: bool = False + + def setDisplayedChild(self, which_child: int): + self._displayed_child = which_child + + def getDisplayedChild(self) -> int: + return self._displayed_child + + def showNext(self): + self._displayed_child = (self._displayed_child + 1) % max(1, self.getCount()) + + def showPrevious(self): + last = max(1, self.getCount()) + self._displayed_child = (self._displayed_child - 1) % last + + def setInAnimation(self, in_animation: Any): + self._in_animation = in_animation + + def getInAnimation(self) -> Any: + return self._in_animation + + def setOutAnimation(self, out_animation: Any): + self._out_animation = out_animation + + def getOutAnimation(self) -> Any: + return self._out_animation + + def getCurrentView(self) -> Optional[View]: + return self.getChildAt(self._displayed_child) + + def getBaseline(self) -> int: + return -1 + + def getCount(self) -> int: + if self._adapter: + return self._adapter.getCount() + return 0 + + +class AdapterViewFlipper(AdapterViewAnimator): + """AdapterViewFlipper — 适配器视图翻转器""" + + def __init__(self, context=None): + super().__init__(context) + self._flip_interval: int = 10000 + self._auto_start: bool = False + self._running: bool = False + + def setFlipInterval(self, milliseconds: int): + self._flip_interval = milliseconds + + def getFlipInterval(self) -> int: + return self._flip_interval + + def startFlipping(self): + self._running = True + + def stopFlipping(self): + self._running = False + + def isFlipping(self) -> bool: + return self._running + + def setAutoStart(self, auto_start: bool): + self._auto_start = auto_start + + def isAutoStart(self) -> bool: + return self._auto_start + + +class StackView(AdapterViewAnimator): + """StackView — 栈视图""" + pass + + +# ═══════════════════════════════════════════════════════════════ +# Adapters +# ═══════════════════════════════════════════════════════════════ + +class BaseAdapter(Stub): + """BaseAdapter — 基础适配器""" + + def __init__(self): + super().__init__() + self._data_set_observable: Any = None + + def getCount(self) -> int: + return 0 + + def getItem(self, position: int) -> Any: + return None + + def getItemId(self, position: int) -> int: + return position + + def getView(self, position: int, convert_view: Optional[View], parent: Optional[ViewGroup]) -> View: + return View() + + def getItemViewType(self, position: int) -> int: + return 0 + + def getViewTypeCount(self) -> int: + return 1 + + def hasStableIds(self) -> bool: + return False + + def isEmpty(self) -> bool: + return self.getCount() == 0 + + def areAllItemsEnabled(self) -> bool: + return True + + def isEnabled(self, position: int) -> bool: + return True + + def getDropDownView(self, position: int, convert_view: Optional[View], + parent: Optional[ViewGroup]) -> View: + return self.getView(position, convert_view, parent) + + def notifyDataSetChanged(self): + pass + + def notifyDataSetInvalidated(self): + pass + + def registerDataSetObserver(self, observer: Any): + pass + + def unregisterDataSetObserver(self, observer: Any): + pass + + +class ArrayAdapter(BaseAdapter): + """ArrayAdapter — 数组适配器""" + + def __init__(self, context: Any, resource: int, objects: Optional[List[Any]] = None): + super().__init__() + self._context = context + self._resource = resource + self._drop_down_resource = resource + self._objects: List[Any] = list(objects) if objects is not None else [] + self._field_id = 0 + self._notify_on_change = True + + def getCount(self) -> int: + return len(self._objects) + + def getItem(self, position: int) -> Any: + if 0 <= position < len(self._objects): + return self._objects[position] + return None + + def getPosition(self, item: Any) -> int: + try: + return self._objects.index(item) + except ValueError: + return -1 + + def getView(self, position: int, convert_view: Optional[View], + parent: Optional[ViewGroup]) -> View: + if convert_view is not None: + tv = convert_view + else: + tv = TextView(self._context) + if hasattr(tv, "setText"): + tv.setText(str(self.getItem(position) if self.getItem(position) is not None else "")) + return tv + + def add(self, obj: Any): + self._objects.append(obj) + if self._notify_on_change: + self.notifyDataSetChanged() + + def addAll(self, *items: Any): + for item in items: + self._objects.append(item) + if self._notify_on_change: + self.notifyDataSetChanged() + + def insert(self, obj: Any, index: int): + self._objects.insert(index, obj) + if self._notify_on_change: + self.notifyDataSetChanged() + + def remove(self, obj: Any): + if obj in self._objects: + self._objects.remove(obj) + if self._notify_on_change: + self.notifyDataSetChanged() + + def clear(self): + self._objects.clear() + if self._notify_on_change: + self.notifyDataSetChanged() + + def sort(self, comparator: Callable): + self._objects.sort(key=comparator) + if self._notify_on_change: + self.notifyDataSetChanged() + + def setNotifyOnChange(self, notify_on_change: bool): + self._notify_on_change = notify_on_change + + def getContext(self): + return self._context + + def getCount(self) -> int: + return len(self._objects) + + def setDropDownViewResource(self, resource: int): + self._drop_down_resource = resource + + def getDropDownViewResource(self) -> int: + return self._drop_down_resource + + +class SimpleAdapter(BaseAdapter): + """SimpleAdapter — 简单适配器""" + + def __init__(self, context: Any, data: List[Dict[str, Any]], resource: int, + from_cols: List[str], to_ids: List[int]): + super().__init__() + self._context = context + self._data = data + self._resource = resource + self._from = from_cols + self._to = to_ids + self._drop_down_resource = resource + + def getCount(self) -> int: + return len(self._data) + + def getItem(self, position: int) -> Any: + if 0 <= position < len(self._data): + return self._data[position] + return None + + def getView(self, position: int, convert_view: Optional[View], + parent: Optional[ViewGroup]) -> View: + if convert_view is not None: + v = convert_view + else: + v = View(self._context) + return v + + def setDropDownViewResource(self, resource: int): + self._drop_down_resource = resource + + +class CursorAdapter(BaseAdapter): + """CursorAdapter — 游标适配器""" + + FLAG_AUTO_REQUERY = 0x01 + FLAG_REGISTER_CONTENT_OBSERVER = 0x02 + + def __init__(self, context: Any, c: Any, flags: int = FLAG_AUTO_REQUERY): + super().__init__() + self._context = context + self._cursor = c + self._flags = flags + + def getCount(self) -> int: + return 0 + + def getItem(self, position: int) -> Any: + return None + + def getView(self, position: int, convert_view: Optional[View], + parent: Optional[ViewGroup]) -> View: + return View(self._context) + + def bindView(self, view: View, context: Any, cursor: Any): + pass + + def newView(self, context: Any, cursor: Any, parent: ViewGroup) -> View: + return View(context) + + def getCursor(self) -> Any: + return self._cursor + + def changeCursor(self, cursor: Any): + self._cursor = cursor + self.notifyDataSetChanged() + + def swapCursor(self, new_cursor: Any) -> Any: + old = self._cursor + self._cursor = new_cursor + self.notifyDataSetChanged() + return old + + +# ═══════════════════════════════════════════════════════════════ +# Spinner +# ═══════════════════════════════════════════════════════════════ + +class Spinner(AdapterView): + """Spinner — 下拉选择器""" + + MODE_DIALOG = 0 + MODE_DROPDOWN = 1 + + def __init__(self, context=None, mode: int = MODE_DROPDOWN): + super().__init__(context) + self._mode = mode + self._prompt: str = "" + self._popup: Any = None + self._on_item_selected_listener: Optional[Callable] = None + + class OnItemSelectedListener: + def onItemSelected(self, parent: AdapterView, view: View, position: int, id_: int): + pass + def onNothingSelected(self, parent: AdapterView): + pass + + def setOnItemSelectedListener(self, listener: Optional[OnItemSelectedListener]): + self._on_item_selected_listener = listener + + def getOnItemSelectedListener(self) -> Optional[OnItemSelectedListener]: + return self._on_item_selected_listener + + def setSelection(self, position: int, animate: bool = True): + super().setSelection(position) + + def getSelectedItem(self) -> Any: + return super().getSelectedItem() + + def getSelectedItemPosition(self) -> int: + return self._selected_position + + def getSelectedView(self) -> Optional[View]: + return None + + def setPrompt(self, prompt: str): + self._prompt = prompt + + def getPrompt(self) -> str: + return self._prompt + + def setPopupBackgroundDrawable(self, drawable: Any): + pass + + def getPopupBackgroundDrawable(self) -> Any: + return None + + def performClick(self) -> bool: + return True + + +# ═══════════════════════════════════════════════════════════════ +# AutoCompleteTextView +# ═══════════════════════════════════════════════════════════════ + +class AutoCompleteTextView(EditText): + """AutoCompleteTextView — 自动完成文本""" + + def __init__(self, context=None): + super().__init__(context) + self._threshold: int = 2 + self._completion_hint: str = "" + self._drop_down_anchor: Optional[View] = None + self._drop_down_horizontal_offset: int = 0 + self._drop_down_vertical_offset: int = 0 + self._drop_down_width: int = 0 + self._drop_down_height: int = 0 + self._on_item_click_listener: Optional[Callable] = None + self._on_dismiss_listener: Optional[Callable] = None + self._validator: Optional[Callable] = None + + def setThreshold(self, threshold: int): + self._threshold = max(1, threshold) + + def getThreshold(self) -> int: + return self._threshold + + def setAdapter(self, adapter: Any): + self._adapter = adapter + + def getAdapter(self) -> Any: + return self._adapter + + def setOnItemClickListener(self, listener: Optional[Callable]): + self._on_item_click_listener = listener + + def getOnItemClickListener(self) -> Optional[Callable]: + return self._on_item_click_listener + + def setCompletionHint(self, hint: str): + self._completion_hint = hint + + def getCompletionHint(self) -> str: + return self._completion_hint + + def setDropDownAnchor(self, anchor_id: int): + pass + + def setDropDownHorizontalOffset(self, offset: int): + self._drop_down_horizontal_offset = offset + + def getDropDownHorizontalOffset(self) -> int: + return self._drop_down_horizontal_offset + + def setDropDownVerticalOffset(self, offset: int): + self._drop_down_vertical_offset = offset + + def getDropDownVerticalOffset(self) -> int: + return self._drop_down_vertical_offset + + def setDropDownWidth(self, width: int): + self._drop_down_width = width + + def getDropDownWidth(self) -> int: + return self._drop_down_width + + def showDropDown(self): + pass + + def dismissDropDown(self): + pass + + def isDropDownShowing(self) -> bool: + return False + + def enoughToFilter(self) -> bool: + return len(self._text) >= self._threshold + + def setValidator(self, validator: Optional[Callable]): + self._validator = validator + + def getValidator(self) -> Optional[Callable]: + return self._validator + + def performValidation(self): + pass + + def setOnDismissListener(self, listener: Optional[Callable]): + self._on_dismiss_listener = listener + + def performFiltering(self, text: str, key_code: int): + pass + + def onFilterComplete(self, count: int): + pass + + +class MultiAutoCompleteTextView(AutoCompleteTextView): + """MultiAutoCompleteTextView — 多值自动完成文本""" + + def __init__(self, context=None): + super().__init__(context) + self._tokenizer: Optional[Callable] = None + + class CommaTokenizer: + def tokenize(self, text: str) -> List[str]: + return text.split(",") + def findTokenStart(self, text: str, cursor: int) -> int: + return text.rfind(",", 0, cursor) + 1 + def findTokenEnd(self, text: str, cursor: int) -> int: + next_comma = text.find(",", cursor) + return next_comma if next_comma >= 0 else len(text) + def terminateToken(self, text: str) -> str: + return text + "," + + def setTokenizer(self, tokenizer: Any): + self._tokenizer = tokenizer + + def getTokenizer(self) -> Any: + return self._tokenizer + + +# ═══════════════════════════════════════════════════════════════ +# DatePicker / TimePicker / CalendarView / NumberPicker +# ═══════════════════════════════════════════════════════════════ + +class DatePicker(FrameLayout): + """DatePicker — 日期选择器""" + + def __init__(self, context=None): + super().__init__(context) + self._year: int = 2024 + self._month: int = 0 # 0-based + self._day_of_month: int = 1 + self._min_date: int = 0 + self._max_date: int = 0 + self._spinners_shown: bool = True + self._calendar_view_shown: bool = False + self._on_date_changed_listener: Optional[Callable] = None + self._calendar_view: Optional[CalendarView] = None + + class OnDateChangedListener: + def onDateChanged(self, view: "DatePicker", year: int, month_of_year: int, day_of_month: int): + pass + + def init(self, year: int, month: int, day_of_month: int, + listener: Optional[OnDateChangedListener] = None): + self._year = year + self._month = month + self._day_of_month = day_of_month + self._on_date_changed_listener = listener + if listener: + listener.onDateChanged(self, year, month, day_of_month) + + def getYear(self) -> int: + return self._year + + def getMonth(self) -> int: + return self._month + + def getDayOfMonth(self) -> int: + return self._day_of_month + + def setMinDate(self, min_date: int): + self._min_date = min_date + + def getMinDate(self) -> int: + return self._min_date + + def setMaxDate(self, max_date: int): + self._max_date = max_date + + def getMaxDate(self) -> int: + return self._max_date + + def setSpinnersShown(self, shown: bool): + self._spinners_shown = shown + + def setCalendarViewShown(self, shown: bool): + self._calendar_view_shown = shown + + def getCalendarView(self) -> Optional["CalendarView"]: + return self._calendar_view + + def setOnDateChangedListener(self, listener: Optional[OnDateChangedListener]): + self._on_date_changed_listener = listener + + def updateDate(self, year: int, month: int, day_of_month: int): + self._year = year + self._month = month + self._day_of_month = day_of_month + if self._on_date_changed_listener: + self._on_date_changed_listener.onDateChanged(self, year, month, day_of_month) + + +class TimePicker(FrameLayout): + """TimePicker — 时间选择器""" + + MODE_CLOCK = 0 + MODE_SPINNER = 1 + + def __init__(self, context=None, mode: int = MODE_CLOCK): + super().__init__(context) + self._current_hour: int = 12 + self._current_minute: int = 0 + self._is24_hour_view: Optional[bool] = None # None = system default + self._mode = mode + self._on_time_changed_listener: Optional[Callable] = None + + class OnTimeChangedListener: + def onTimeChanged(self, view: "TimePicker", hour_of_day: int, minute: int): + pass + + def setOnTimeChangedListener(self, listener: Optional[OnTimeChangedListener]): + self._on_time_changed_listener = listener + + def getOnTimeChangedListener(self) -> Optional[OnTimeChangedListener]: + return self._on_time_changed_listener + + def getCurrentHour(self) -> int: + return self._current_hour + + def setCurrentHour(self, current_hour: int): + self._current_hour = current_hour % 24 + if self._on_time_changed_listener: + self._on_time_changed_listener.onTimeChanged(self, self._current_hour, self._current_minute) + + def getCurrentMinute(self) -> int: + return self._current_minute + + def setCurrentMinute(self, current_minute: int): + self._current_minute = current_minute % 60 + if self._on_time_changed_listener: + self._on_time_changed_listener.onTimeChanged(self, self._current_hour, self._current_minute) + + def setIs24HourView(self, is24_hour_view: Optional[bool]): + self._is24_hour_view = is24_hour_view + + def is24HourView(self) -> bool: + if self._is24_hour_view is not None: + return self._is24_hour_view + return False + + def getMode(self) -> int: + return self._mode + + def setHour(self, hour: int): + self.setCurrentHour(hour) + + def getHour(self) -> int: + return self.getCurrentHour() + + def setMinute(self, minute: int): + self.setCurrentMinute(minute) + + def getMinute(self) -> int: + return self.getCurrentMinute() + + +class CalendarView(FrameLayout): + """CalendarView — 日历视图""" + + def __init__(self, context=None): + super().__init__(context) + self._date: int = 0 + self._min_date: int = 0 + self._max_date: int = 0 + self._first_day_of_week: int = 1 # Sunday=1 + self._week_separator: bool = False + self._on_date_change_listener: Optional[Callable] = None + self._shown_week_count: int = 6 + + class OnDateChangeListener: + def onSelectedDayChange(self, view: "CalendarView", year: int, month: int, day_of_month: int): + pass + + def setOnDateChangeListener(self, listener: Optional[OnDateChangeListener]): + self._on_date_change_listener = listener + + def getDate(self) -> int: + return self._date + + def setDate(self, date: int, animate: bool = True, center: bool = True): + self._date = date + + def getMinDate(self) -> int: + return self._min_date + + def setMinDate(self, min_date: int): + self._min_date = min_date + + def getMaxDate(self) -> int: + return self._max_date + + def setMaxDate(self, max_date: int): + self._max_date = max_date + + def getFirstDayOfWeek(self) -> int: + return self._first_day_of_week + + def setFirstDayOfWeek(self, first_day_of_week: int): + self._first_day_of_week = first_day_of_week + + def setWeekSeparatorDrawable(self, drawable: Any): + self._week_separator = True + + def getShownWeekCount(self) -> int: + return self._shown_week_count + + def setShownWeekCount(self, count: int): + self._shown_week_count = count + + +class NumberPicker(ViewGroup): + """NumberPicker — 数字选择器""" + + def __init__(self, context=None): + super().__init__(context) + self._min_value: int = 0 + self._max_value: int = 100 + self._value: int = 0 + self._displayed_values: Optional[List[str]] = None + self._on_value_change_listener: Optional[Callable] = None + self._on_scroll_listener: Optional[Callable] = None + self._wrap_selector_wheel: bool = True + self._selection_divider: bool = True + + class OnValueChangeListener: + def onValueChange(self, picker: "NumberPicker", old_val: int, new_val: int): + pass + + class OnScrollListener: + def onScrollStateChange(self, picker: "NumberPicker", state: int): + pass + + SCROLL_STATE_IDLE = 0 + SCROLL_STATE_TOUCH_SCROLL = 1 + SCROLL_STATE_FLING = 2 + + def setMinValue(self, min_value: int): + self._min_value = min_value + if self._value < self._min_value: + self._value = self._min_value + + def getMinValue(self) -> int: + return self._min_value + + def setMaxValue(self, max_value: int): + self._max_value = max_value + if self._value > self._max_value: + self._value = self._max_value + + def getMaxValue(self) -> int: + return self._max_value + + def setValue(self, value: int): + old_value = self._value + self._value = max(self._min_value, min(self._max_value, value)) + if self._on_value_change_listener and old_value != self._value: + self._on_value_change_listener.onValueChange(self, old_value, self._value) + + def getValue(self) -> int: + return self._value + + def setDisplayedValues(self, displayed_values: Optional[List[str]]): + self._displayed_values = displayed_values + + def getDisplayedValues(self) -> Optional[List[str]]: + return self._displayed_values + + def setOnValueChangedListener(self, listener: Optional[OnValueChangeListener]): + self._on_value_change_listener = listener + + def setOnScrollListener(self, listener: Optional[OnScrollListener]): + self._on_scroll_listener = listener + + def setWrapSelectorWheel(self, wrap: bool): + self._wrap_selector_wheel = wrap + + def getWrapSelectorWheel(self) -> bool: + return self._wrap_selector_wheel + + def setSelectionDivider(self, divider: Any): + self._selection_divider = True + + +# ═══════════════════════════════════════════════════════════════ +# VideoView / MediaController +# ═══════════════════════════════════════════════════════════════ + +class VideoView(SurfaceView): + """VideoView — 视频播放视图""" + + def __init__(self, context=None): + super().__init__(context) + self._video_path: Optional[str] = None + self._uri: Any = None + self._media_controller: Optional["MediaController"] = None + self._on_prepared_listener: Optional[Callable] = None + self._on_completion_listener: Optional[Callable] = None + self._on_error_listener: Optional[Callable] = None + self._is_playing: bool = False + self._current_position: int = 0 + self._duration: int = 0 + self._can_seek_back: bool = True + self._can_seek_forward: bool = True + self._can_pause: bool = True + self._video_width: int = 0 + self._video_height: int = 0 + + def setVideoPath(self, path: str): + self._video_path = path + + def setVideoURI(self, uri: Any): + self._uri = uri + + def setMediaController(self, controller: "MediaController"): + self._media_controller = controller + + def setOnPreparedListener(self, listener: Optional[Callable]): + self._on_prepared_listener = listener + + def setOnCompletionListener(self, listener: Optional[Callable]): + self._on_completion_listener = listener + + def setOnErrorListener(self, listener: Optional[Callable]): + self._on_error_listener = listener + + def start(self): + self._is_playing = True + + def stopPlayback(self): + self._is_playing = False + self._current_position = 0 + + def pause(self): + self._is_playing = False + + def resume(self): + self._is_playing = True + + def seekTo(self, msec: int): + self._current_position = max(0, min(msec, self._duration)) + + def getCurrentPosition(self) -> int: + return self._current_position + + def getDuration(self) -> int: + return self._duration + + def isPlaying(self) -> bool: + return self._is_playing + + def canSeekForward(self) -> bool: + return self._can_seek_forward + + def canSeekBackward(self) -> bool: + return self._can_seek_back + + def canPause(self) -> bool: + return self._can_pause + + def getBufferPercentage(self) -> int: + return 100 + + def resolveAdjustedSize(self, desired_size: int, measure_spec: int) -> int: + from .view import MeasureSpec + return MeasureSpec.getSize(measure_spec) + + def setVideoWidth(self, video_width: int): + self._video_width = video_width + + def setVideoHeight(self, video_height: int): + self._video_height = video_height + + @property + def videoWidth(self) -> int: + return self._video_width + + @property + def videoHeight(self) -> int: + return self._video_height + + +class MediaController(FrameLayout): + """MediaController — 媒体控制器""" + + def __init__(self, context=None, target: Optional[VideoView] = None): + super().__init__(context) + self._target = target + self._anchor_view: Optional[View] = None + self._is_showing: bool = False + self._set_up_listener: Optional[Callable] = None + + def setMediaPlayer(self, player: "MediaController.MediaPlayerControl"): + pass + + def setAnchorView(self, view: View): + self._anchor_view = view + + def show(self, timeout: int = 3000): + self._is_showing = True + + def hide(self): + self._is_showing = False + + def isShowing(self) -> bool: + return self._is_showing + + def setEnabled(self, enabled: bool): + super().setEnabled(enabled) + + def setOnPrepareListener(self, listener: Optional[Callable]): + self._set_up_listener = listener + + class MediaPlayerControl: + def start(self): + pass + def pause(self): + pass + def getDuration(self) -> int: + return 0 + def getCurrentPosition(self) -> int: + return 0 + def seekTo(self, pos: int): + pass + def isPlaying(self) -> bool: + return False + def getBufferPercentage(self) -> int: + return 0 + def canPause(self) -> bool: + return False + def canSeekBackward(self) -> bool: + return False + def canSeekForward(self) -> bool: + return False + def getAudioSessionId(self) -> int: + return 0 + + +# ═══════════════════════════════════════════════════════════════ +# Toast +# ═══════════════════════════════════════════════════════════════ + +class Toast(Stub): + """Toast — 提示消息""" + + LENGTH_SHORT = 0 + LENGTH_LONG = 1 + + def __init__(self, context=None): + super().__init__() + if context is None: + context = android_context + self._context = context + self._duration = Toast.LENGTH_SHORT + self._gravity = Gravity.NO_GRAVITY + self._x_offset: int = 0 + self._y_offset: int = 0 + self._view: Optional[View] = None + self._text: str = "" + self._horizontal_margin: float = 0.0 + self._vertical_margin: float = 0.0 + self._next_view: Optional[View] = None + + @staticmethod + def makeText(context: Any, text: str, duration: int) -> "Toast": + toast = Toast(context) + toast._text = text + toast._duration = duration + return toast + + def show(self): + pass + + def cancel(self): + pass + + def setGravity(self, gravity: int, x_offset: int, y_offset: int): + self._gravity = gravity + self._x_offset = x_offset + self._y_offset = y_offset + + def setDuration(self, duration: int): + self._duration = duration + + def getDuration(self) -> int: + return self._duration + + def setView(self, view: View): + self._view = view + + def getView(self) -> Optional[View]: + return self._view + + def setMargin(self, horizontal_margin: float, vertical_margin: float): + self._horizontal_margin = horizontal_margin + self._vertical_margin = vertical_margin + + def getHorizontalMargin(self) -> float: + return self._horizontal_margin + + def getVerticalMargin(self) -> float: + return self._vertical_margin + + def setText(self, text: str): + self._text = text + + def setNextAnnouncementView(self, view: View): + self._next_view = view + + def getNextAnnouncementView(self) -> Optional[View]: + return self._next_view + + def isTouchable(self) -> bool: + return False + + +# ═══════════════════════════════════════════════════════════════ +# PopupMenu / PopupWindow +# ═══════════════════════════════════════════════════════════════ + +class PopupMenu(Stub): + """PopupMenu — 弹出菜单""" + + def __init__(self, context: Any, anchor: View, gravity: int = Gravity.NO_GRAVITY): + super().__init__() + self._context = context + self._anchor = anchor + self._gravity = gravity + self._menu: "PopupMenu.Menu" = PopupMenu.Menu() + self._on_menu_item_click_listener: Optional[Callable] = None + self._on_dismiss_listener: Optional[Callable] = None + + class Menu(Stub): + """PopupMenu.Menu — 弹出菜单项列表""" + + def __init__(self): + super().__init__() + self._items: List[Dict] = [] + + def add(self, title: str) -> Any: + item_id = len(self._items) + self._items.append({"id": item_id, "title": title, "enabled": True}) + return item_id + + def findItem(self, id_: int) -> Optional[Dict]: + for item in self._items: + if item["id"] == id_: + return item + return None + + def removeItem(self, id_: int): + for i, item in enumerate(self._items): + if item["id"] == id_: + self._items.pop(i) + break + + def size(self) -> int: + return len(self._items) + + def getItem(self, index: int) -> Optional[Dict]: + if 0 <= index < len(self._items): + return self._items[index] + return None + + def clear(self): + self._items.clear() + + def setOnMenuItemClickListener(self, listener: Optional[Callable]): + pass + + def getMenu(self) -> "PopupMenu.Menu": + return self._menu + + def inflate(self, menu_res_id: int): + pass + + def show(self): + pass + + def dismiss(self): + pass + + def setOnMenuItemClickListener(self, listener: Optional[Callable]): + self._on_menu_item_click_listener = listener + + def setOnDismissListener(self, listener: Optional[Callable]): + self._on_dismiss_listener = listener + + def getDragToOpenListener(self) -> Optional[Callable]: + return None + + def setForceShowIcon(self, force_show: bool): + pass + + +class PopupWindow(Stub): + """PopupWindow — 弹出窗口""" + + def __init__(self, content_view: Optional[View] = None, + width: int = ViewGroup.LayoutParams.WRAP_CONTENT, + height: int = ViewGroup.LayoutParams.WRAP_CONTENT, + focusable: bool = True): + super().__init__() + self._content_view = content_view + self._width = width + self._height = height + self._focusable = focusable + self._is_showing: bool = False + self._outside_touchable: bool = False + self._touchable: bool = True + self._background_drawable: Any = None + self._animation_style: int = -1 + self._elevation: float = 0.0 + self._input_method_mode: int = -1 + self._soft_input_mode: int = -1 + self._on_dismiss_listener: Optional[Callable] = None + self._window_layout_type: int = 1000 # TYPE_APPLICATION_PANEL + self._overlap_anchor: bool = True + self._clip_to_screen: bool = True + self._split_enabled: bool = False + self._width_mode: int = 0 + self._height_mode: int = 0 + + def showAsDropDown(self, anchor: View, x_off: int = 0, y_off: int = 0, + gravity: int = Gravity.NO_GRAVITY): + self._is_showing = True + + def showAtLocation(self, parent: View, gravity: int, x: int, y: int): + self._is_showing = True + + def dismiss(self): + self._is_showing = False + if self._on_dismiss_listener: + self._on_dismiss_listener() + + def isShowing(self) -> bool: + return self._is_showing + + def setContentView(self, content_view: View): + self._content_view = content_view + + def getContentView(self) -> Optional[View]: + return self._content_view + + def setWidth(self, width: int): + self._width = width + + def getWidth(self) -> int: + return self._width + + def setHeight(self, height: int): + self._height = height + + def getHeight(self) -> int: + return self._height + + def setFocusable(self, focusable: bool): + self._focusable = focusable + + def isFocusable(self) -> bool: + return self._focusable + + def setOutsideTouchable(self, touchable: bool): + self._outside_touchable = touchable + + def isOutsideTouchable(self) -> bool: + return self._outside_touchable + + def setTouchable(self, touchable: bool): + self._touchable = touchable + + def isTouchable(self) -> bool: + return self._touchable + + def setBackgroundDrawable(self, background: Any): + self._background_drawable = background + + def getBackground(self) -> Any: + return self._background_drawable + + def setAnimationStyle(self, anim_style: int): + self._animation_style = anim_style + + def getAnimationStyle(self) -> int: + return self._animation_style + + def setElevation(self, elevation: float): + self._elevation = elevation + + def getElevation(self) -> float: + return self._elevation + + def setOnDismissListener(self, listener: Optional[Callable]): + self._on_dismiss_listener = listener + + def setTouchInterceptor(self, interceptor: Optional[Callable]): + pass + + def setClippingEnabled(self, enabled: bool): + self._clip_to_screen = enabled + + def isClippingEnabled(self) -> bool: + return self._clip_to_screen + + def setSplitTouchEnabled(self, enabled: bool): + self._split_enabled = enabled + + def isSplitTouchEnabled(self) -> bool: + return self._split_enabled + + def setWindowLayoutType(self, layout_type: int): + self._window_layout_type = layout_type + + def getWindowLayoutType(self) -> int: + return self._window_layout_type + + def setSoftInputMode(self, mode: int): + self._soft_input_mode = mode + + def getSoftInputMode(self) -> int: + return self._soft_input_mode + + def setInputMethodMode(self, mode: int): + self._input_method_mode = mode + + def getInputMethodMode(self) -> int: + return self._input_method_mode + + INPUT_METHOD_FROM_FOCUSABLE = 0 + INPUT_METHOD_NEEDED = 1 + INPUT_METHOD_NOT_NEEDED = 2 + + +# ═══════════════════════════════════════════════════════════════ +# Toolbar +# ═══════════════════════════════════════════════════════════════ + +class Toolbar(ViewGroup): + """Toolbar — 工具栏""" + + def __init__(self, context=None): + super().__init__(context) + self._title: str = "" + self._subtitle: str = "" + self._navigation_icon: Any = None + self._navigation_content_description: str = "" + self._logo: Any = None + self._logo_description: str = "" + self._title_text_color: int = 0 + self._subtitle_text_color: int = 0 + self._collapse_icon: Any = None + self._menu: Any = None + self._on_menu_item_click_listener: Optional[Callable] = None + self._navigation_on_click_listener: Optional[Callable] = None + self._popup_theme: int = 0 + self._content_inset_start: int = 16 + self._content_inset_end: int = 16 + self._title_margin_start: int = 0 + + def setTitle(self, title: str): + self._title = title + + def getTitle(self) -> str: + return self._title + + def setSubtitle(self, subtitle: str): + self._subtitle = subtitle + + def getSubtitle(self) -> str: + return self._subtitle + + def setNavigationIcon(self, icon: Any): + self._navigation_icon = icon + + def getNavigationIcon(self) -> Any: + return self._navigation_icon + + def setNavigationContentDescription(self, description: str): + self._navigation_content_description = description + + def getNavigationContentDescription(self) -> str: + return self._navigation_content_description + + def setLogo(self, logo: Any): + self._logo = logo + + def getLogo(self) -> Any: + return self._logo + + def setLogoDescription(self, description: str): + self._logo_description = description + + def getLogoDescription(self) -> str: + return self._logo_description + + def setTitleTextColor(self, color: int): + self._title_text_color = color + + def setSubtitleTextColor(self, color: int): + self._subtitle_text_color = color + + def inflateMenu(self, menu_res_id: int): + pass + + def getMenu(self) -> Any: + return self._menu + + def setOnMenuItemClickListener(self, listener: Optional[Callable]): + self._on_menu_item_click_listener = listener + + def setNavigationOnClickListener(self, listener: Optional[Callable]): + self._navigation_on_click_listener = listener + + def setCollapseIcon(self, icon: Any): + self._collapse_icon = icon + + def getCollapseIcon(self) -> Any: + return self._collapse_icon + + def setPopupTheme(self, res_id: int): + self._popup_theme = res_id + + def getPopupTheme(self) -> int: + return self._popup_theme + + def setContentInsetsRelative(self, content_inset_start: int, content_inset_end: int): + self._content_inset_start = content_inset_start + self._content_inset_end = content_inset_end + + def getContentInsetStart(self) -> int: + return self._content_inset_start + + def getContentInsetEnd(self) -> int: + return self._content_inset_end + + def collapseActionView(self): + pass + + def dismissPopupMenus(self): + pass + + def isOverflowMenuShowing(self) -> bool: + return False + + def showOverflowMenu(self) -> bool: + return False + + def hideOverflowMenu(self) -> bool: + return False + + def getOverflowIcon(self) -> Any: + return None + + def setOverflowIcon(self, icon: Any): + pass + + +class ActionMenuView(LinearLayout): + """ActionMenuView — 操作菜单视图""" + pass + + +# ═══════════════════════════════════════════════════════════════ +# TabHost / TabWidget +# ═══════════════════════════════════════════════════════════════ + +class TabHost(FrameLayout): + """TabHost — 标签主机""" + + def __init__(self, context=None): + super().__init__(context) + self._tab_widget: Optional["TabWidget"] = None + self._tab_content: Optional[FrameLayout] = None + self._tabs: List["TabHost.TabSpec"] = [] + self._current_tab: int = 0 + self._on_tab_changed_listener: Optional[Callable] = None + self._local_activity_manager: Any = None + + class TabSpec(Stub): + """TabHost.TabSpec — 标签规格""" + + def __init__(self, tag: str): + super().__init__() + self._tag = tag + self._indicator: Any = None + self._content: Any = None + self._content_view: Optional[View] = None + + def setIndicator(self, indicator: Any) -> "TabHost.TabSpec": + self._indicator = indicator + return self + + def setContent(self, content: Any) -> "TabHost.TabSpec": + self._content = content + return self + + def setContent(self, view_id: int) -> "TabHost.TabSpec": + return self + + def setContent(self, view: View) -> "TabHost.TabSpec": + self._content_view = view + return self + + def getTag(self) -> str: + return self._tag + + def getIndicator(self) -> Any: + return self._indicator + + class OnTabChangeListener: + def onTabChanged(self, tab_id: str): + pass + + def setup(self): + pass + + def newTabSpec(self, tag: str) -> "TabHost.TabSpec": + spec = TabHost.TabSpec(tag) + self._tabs.append(spec) + return spec + + def addTab(self, tab_spec: "TabHost.TabSpec"): + if tab_spec not in self._tabs: + self._tabs.append(tab_spec) + + def setCurrentTab(self, index: int): + if 0 <= index < len(self._tabs): + self._current_tab = index + + def setCurrentTabByTag(self, tag: str): + for i, tab in enumerate(self._tabs): + if tab.getTag() == tag: + self._current_tab = i + break + + def getCurrentTab(self) -> int: + return self._current_tab + + def getCurrentTabTag(self) -> str: + if 0 <= self._current_tab < len(self._tabs): + return self._tabs[self._current_tab].getTag() + return "" + + def getTabWidget(self) -> "TabWidget": + return self._tab_widget + + def getTabContentView(self) -> FrameLayout: + return self._tab_content + + def setOnTabChangedListener(self, listener: Optional[OnTabChangeListener]): + self._on_tab_changed_listener = listener + + def clearAllTabs(self): + self._tabs.clear() + self._current_tab = 0 + + +class TabWidget(LinearLayout): + """TabWidget — 标签部件""" + + def __init__(self, context=None): + super().__init__(context) + self._strip: Any = None + self._left_strip: Any = None + self._right_strip: Any = None + self._selected_tab: int = -1 + self._draw_full_strip: bool = False + + def setStripEnabled(self, strip_enabled: bool): + self._draw_full_strip = strip_enabled + + def isStripEnabled(self) -> bool: + return self._draw_full_strip + + def setLeftStripDrawable(self, drawable: Any): + self._left_strip = drawable + + def setRightStripDrawable(self, drawable: Any): + self._right_strip = drawable + + def setDividerDrawable(self, drawable: Any): + pass + + def focusCurrentTab(self, index: int): + self._selected_tab = index + + def isEnabled(self) -> bool: + return True + + +# ═══════════════════════════════════════════════════════════════ +# ExpandableListView +# ═══════════════════════════════════════════════════════════════ + +class ExpandableListView(ListView): + """ExpandableListView — 可展开列表视图""" + + PACKED_POSITION_TYPE_GROUP = 0 + PACKED_POSITION_TYPE_CHILD = 1 + PACKED_POSITION_TYPE_NULL = 2 + PACKED_POSITION_VALUE_NULL = 0xFFFFFFFF + + def __init__(self, context=None): + super().__init__(context) + self._expandable_adapter: Optional["ExpandableListAdapter"] = None + self._on_group_collapse_listener: Optional[Callable] = None + self._on_group_expand_listener: Optional[Callable] = None + self._on_child_click_listener: Optional[Callable] = None + self._on_group_click_listener: Optional[Callable] = None + + def setAdapter(self, adapter: "ExpandableListAdapter"): + self._expandable_adapter = adapter + self._adapter = adapter + + def getExpandableListAdapter(self) -> Optional["ExpandableListAdapter"]: + return self._expandable_adapter + + def setOnGroupCollapseListener(self, listener: Optional[Callable]): + self._on_group_collapse_listener = listener + + def setOnGroupExpandListener(self, listener: Optional[Callable]): + self._on_group_expand_listener = listener + + def setOnChildClickListener(self, listener: Optional[Callable]): + self._on_child_click_listener = listener + + def setOnGroupClickListener(self, listener: Optional[Callable]): + self._on_group_click_listener = listener + + def expandGroup(self, group_pos: int, animate: bool = True) -> bool: + return True + + def collapseGroup(self, group_pos: int) -> bool: + return True + + def setOnItemClickListener(self, listener: Optional[Callable]): + self._on_item_click_listener = listener + + def getOnItemClickListener(self) -> Optional[Callable]: + return self._on_item_click_listener + + def setOnItemLongClickListener(self, listener: Optional[Callable]): + self._on_item_long_click_listener = listener + + def getOnItemLongClickListener(self) -> Optional[Callable]: + return self._on_item_long_click_listener + + def getPackedPositionType(self, packed_position: int) -> int: + if packed_position == ExpandableListView.PACKED_POSITION_VALUE_NULL: + return ExpandableListView.PACKED_POSITION_TYPE_NULL + return ExpandableListView.PACKED_POSITION_TYPE_GROUP + + def getPackedPositionGroup(self, packed_position: int) -> int: + return packed_position & 0xFFFF + + def getPackedPositionChild(self, packed_position: int) -> int: + return (packed_position >> 16) & 0xFFFF + + @staticmethod + def getPackedPositionForChild(group_position: int, child_position: int) -> int: + return (child_position << 16) | group_position + + @staticmethod + def getPackedPositionForGroup(group_position: int) -> int: + return group_position + + def isGroupExpanded(self, group_position: int) -> bool: + return False + + def setSelectedGroup(self, group_position: int): + pass + + def setSelectedChild(self, group_position: int, child_position: int, should_expand_group: bool): + pass + + def smoothScrollToPosition(self, position: int): + pass + + +# ═══════════════════════════════════════════════════════════════ +# ExpandableListAdapter +# ═══════════════════════════════════════════════════════════════ + +class ExpandableListAdapter(Stub): + """ExpandableListAdapter — 可扩展列表适配器接口""" + + def __init__(self): + super().__init__() + + def getGroupCount(self) -> int: + raise AndroidOnlyError("ExpandableListAdapter.getGroupCount: not implemented") + + def getChildrenCount(self, group_position: int) -> int: + raise AndroidOnlyError("ExpandableListAdapter.getChildrenCount: not implemented") + + def getGroup(self, group_position: int) -> Any: + raise AndroidOnlyError("ExpandableListAdapter.getGroup: not implemented") + + def getChild(self, group_position: int, child_position: int) -> Any: + raise AndroidOnlyError("ExpandableListAdapter.getChild: not implemented") + + def getGroupId(self, group_position: int) -> int: + raise AndroidOnlyError("ExpandableListAdapter.getGroupId: not implemented") + + def getChildId(self, group_position: int, child_position: int) -> int: + raise AndroidOnlyError("ExpandableListAdapter.getChildId: not implemented") + + def hasStableIds(self) -> bool: + return False + + def getGroupView(self, group_position: int, is_expanded: bool, + convert_view: Optional[Any], parent: Optional[Any]) -> Any: + raise AndroidOnlyError("ExpandableListAdapter.getGroupView: not implemented") + + def getChildView(self, group_position: int, child_position: int, + is_last_child: bool, convert_view: Optional[Any], + parent: Optional[Any]) -> Any: + raise AndroidOnlyError("ExpandableListAdapter.getChildView: not implemented") + + def isChildSelectable(self, group_position: int, child_position: int) -> bool: + return True + + def areAllItemsEnabled(self) -> bool: + return True + + def isEmpty(self) -> bool: + return self.getGroupCount() == 0 + + def onGroupCollapsed(self, group_position: int): + pass + + def onGroupExpanded(self, group_position: int): + pass + + def getCombinedGroupId(self, group_id: int) -> int: + return group_id + + def getCombinedChildId(self, group_id: int, child_id: int) -> int: + return (group_id << 32) | child_id + + +# ═══════════════════════════════════════════════════════════════ +# SimpleExpandableListAdapter +# ═══════════════════════════════════════════════════════════════ + +class SimpleExpandableListAdapter(ExpandableListAdapter, BaseAdapter): + """SimpleExpandableListAdapter — 简单的可展开列表适配器""" + + def __init__(self, context: Any, + group_data: List[Dict[str, Any]], + group_layout: int, + group_from: List[str], + group_to: List[int], + child_data: List[List[Dict[str, Any]]], + child_layout: int, + child_from: List[str], + child_to: List[int]): + super().__init__() + self._context = context + self._group_data = group_data + self._group_layout = group_layout + self._group_from = group_from + self._group_to = group_to + self._child_data = child_data + self._child_layout = child_layout + self._child_from = child_from + self._child_to = child_to + + def getGroupCount(self) -> int: + return len(self._group_data) + + def getChildrenCount(self, group_position: int) -> int: + if 0 <= group_position < len(self._child_data): + return len(self._child_data[group_position]) + return 0 + + def getGroup(self, group_position: int) -> Any: + if 0 <= group_position < len(self._group_data): + return self._group_data[group_position] + return None + + def getChild(self, group_position: int, child_position: int) -> Any: + if 0 <= group_position < len(self._child_data): + children = self._child_data[group_position] + if 0 <= child_position < len(children): + return children[child_position] + return None + + def getGroupId(self, group_position: int) -> int: + return group_position + + def getChildId(self, group_position: int, child_position: int) -> int: + return child_position + + def getGroupView(self, group_position: int, is_expanded: bool, + convert_view: Optional[Any], parent: Optional[Any]) -> Any: + if convert_view is not None: + view = convert_view + else: + view = TextView(self._context) + view.setText(str(self.getGroup(group_position))) + return view + + def getChildView(self, group_position: int, child_position: int, + is_last_child: bool, convert_view: Optional[Any], + parent: Optional[Any]) -> Any: + if convert_view is not None: + view = convert_view + else: + view = TextView(self._context) + child = self.getChild(group_position, child_position) + view.setText(str(child) if child else "") + return view + + def isChildSelectable(self, group_position: int, child_position: int) -> bool: + return True + + def getCount(self) -> int: + return self.getGroupCount() + + def getItem(self, position: int) -> Any: + return self.getGroup(position) + + def getItemId(self, position: int) -> int: + return position + + def getView(self, position: int, convert_view: Optional[Any], + parent: Optional[Any]) -> Any: + return self.getGroupView(position, False, convert_view, parent) + + +# ═══════════════════════════════════════════════════════════════ +# BaseExpandableListAdapter +# ═══════════════════════════════════════════════════════════════ + +class BaseExpandableListAdapter(ExpandableListAdapter, BaseAdapter): + """BaseExpandableListAdapter — 可展开列表适配器基类""" + + def __init__(self): + super().__init__() + + def getGroupCount(self) -> int: + return 0 + + def getChildrenCount(self, group_position: int) -> int: + return 0 + + def getGroup(self, group_position: int) -> Any: + return None + + def getChild(self, group_position: int, child_position: int) -> Any: + return None + + def getGroupId(self, group_position: int) -> int: + return group_position + + def getChildId(self, group_position: int, child_position: int) -> int: + return child_position + + def getGroupView(self, group_position: int, is_expanded: bool, + convert_view: Optional[Any], parent: Optional[Any]) -> Any: + if convert_view is not None: + view = convert_view + else: + view = TextView(android_context) + view.setText(f"Group {group_position}") + return view + + def getChildView(self, group_position: int, child_position: int, + is_last_child: bool, convert_view: Optional[Any], + parent: Optional[Any]) -> Any: + if convert_view is not None: + view = convert_view + else: + view = TextView(android_context) + view.setText(f"Child {group_position}:{child_position}") + return view + + def isChildSelectable(self, group_position: int, child_position: int) -> bool: + return True + + def areAllItemsEnabled(self) -> bool: + return True + + def onGroupCollapsed(self, group_position: int): + pass + + def onGroupExpanded(self, group_position: int): + pass + + def notifyDataSetChanged(self): + pass + + def notifyDataSetInvalidated(self): + pass + + def getCount(self) -> int: + return self.getGroupCount() + + def getItem(self, position: int) -> Any: + return self.getGroup(position) + + def getItemId(self, position: int) -> int: + return position + + def getView(self, position: int, convert_view: Optional[Any], + parent: Optional[Any]) -> Any: + return self.getGroupView(position, False, convert_view, parent) + + +# ═══════════════════════════════════════════════════════════════ +# SearchView +# ═══════════════════════════════════════════════════════════════ + +class SearchView(LinearLayout): + """SearchView — 搜索视图""" + + def __init__(self, context=None): + super().__init__(context) + self._query: str = "" + self._query_hint: Optional[str] = None + self._iconified: bool = True + self._iconified_by_default: bool = True + self._submit_button_enabled: bool = False + self._query_refinement_enabled: bool = False + self._searchable_info: Any = None + self._on_query_text_listener: Optional[Callable] = None + self._on_close_listener: Optional[Callable] = None + self._on_suggestion_listener: Optional[Callable] = None + self._on_query_text_focus_change_listener: Optional[Callable] = None + self._suggestion_adapter: Optional["CursorAdapter"] = None + self._max_width: int = sys.maxsize + self._on_search_click_listener: Optional[Callable] = None + + class OnQueryTextListener: + def onQueryTextSubmit(self, query: str) -> bool: + return False + + def onQueryTextChange(self, new_text: str) -> bool: + return False + + class OnCloseListener: + def onClose(self) -> bool: + return False + + class OnSuggestionListener: + def onSuggestionSelect(self, position: int) -> bool: + return False + + def onSuggestionClick(self, position: int) -> bool: + return False + + def setQuery(self, query: str, submit: bool = False): + self._query = query + if self._on_query_text_listener: + self._on_query_text_listener.onQueryTextChange(query) + if submit and self._on_query_text_listener: + self._on_query_text_listener.onQueryTextSubmit(query) + + def getQuery(self) -> str: + return self._query + + def setQueryHint(self, hint: Optional[str]): + self._query_hint = hint + + def getQueryHint(self) -> Optional[str]: + return self._query_hint + + def setIconified(self, iconified: bool): + self._iconified = iconified + + def isIconified(self) -> bool: + return self._iconified + + def setIconifiedByDefault(self, iconified: bool): + self._iconified_by_default = iconified + + def isIconifiedByDefault(self) -> bool: + return self._iconified_by_default + + def setSubmitButtonEnabled(self, enabled: bool): + self._submit_button_enabled = enabled + + def isSubmitButtonEnabled(self) -> bool: + return self._submit_button_enabled + + def setQueryRefinementEnabled(self, enable: bool): + self._query_refinement_enabled = enable + + def isQueryRefinementEnabled(self) -> bool: + return self._query_refinement_enabled + + def setOnQueryTextListener(self, listener: Optional["SearchView.OnQueryTextListener"]): + self._on_query_text_listener = listener + + def getOnQueryTextListener(self) -> Optional["SearchView.OnQueryTextListener"]: + return self._on_query_text_listener + + def setOnCloseListener(self, listener: Optional["SearchView.OnCloseListener"]): + self._on_close_listener = listener + + def setOnSuggestionListener(self, listener: Optional["SearchView.OnSuggestionListener"]): + self._on_suggestion_listener = listener + + def setOnSearchClickListener(self, listener: Optional[Callable]): + self._on_search_click_listener = listener + + def setSuggestionsAdapter(self, adapter: Optional["CursorAdapter"]): + self._suggestion_adapter = adapter + + def getSuggestionsAdapter(self) -> Optional["CursorAdapter"]: + return self._suggestion_adapter + + def setMaxWidth(self, max_width: int): + self._max_width = max_width + + def getMaxWidth(self) -> int: + return self._max_width + + def onActionViewExpanded(self): + self._iconified = False + + def onActionViewCollapsed(self): + self._iconified = True + + def onClose(self) -> bool: + if self._on_close_listener: + return self._on_close_listener.onClose() + self._iconified = self._iconified_by_default + return True + + def clearFocus(self): + pass + + +# ═══════════════════════════════════════════════════════════════ +# ZoomControls +# ═══════════════════════════════════════════════════════════════ + +class ZoomControls(LinearLayout): + """ZoomControls — 缩放控件""" + + def __init__(self, context=None): + super().__init__(context) + self._zoom_in_enabled: bool = True + self._zoom_out_enabled: bool = True + self._zoom_speed: int = 300 + self._on_zoom_in_listener: Optional[Callable] = None + self._on_zoom_out_listener: Optional[Callable] = None + + def show(self): + self.setVisibility(View.VISIBLE) + + def hide(self): + self.setVisibility(View.GONE) + + def setIsZoomInEnabled(self, is_enabled: bool): + self._zoom_in_enabled = is_enabled + + def setIsZoomOutEnabled(self, is_enabled: bool): + self._zoom_out_enabled = is_enabled + + def setOnZoomInClickListener(self, listener: Optional[Callable]): + self._on_zoom_in_listener = listener + + def setOnZoomOutClickListener(self, listener: Optional[Callable]): + self._on_zoom_out_listener = listener + + def setZoomSpeed(self, speed: int): + self._zoom_speed = speed + + def getZoomSpeed(self) -> int: + return self._zoom_speed + + def hasFocus(self) -> bool: + return False + + +# ═══════════════════════════════════════════════════════════════ +# DigitalClock +# ═══════════════════════════════════════════════════════════════ + +class DigitalClock(TextView): + """DigitalClock — 数字时钟(API 14+ 已弃用)""" + + def __init__(self, context=None): + super().__init__(context) + self._format: str = "%H:%M:%S" + self._update_interval: int = 1000 + self._running: bool = False + self._timer: Optional[Any] = None + + def start(self): + self._running = True + + def stop(self): + self._running = False + + def setFormat(self, fmt: str): + self._format = fmt + + def getFormat(self) -> str: + return self._format + + def onTimeChanged(self): + now = datetime.now().strftime(self._format) if hasattr(datetime, 'now') else "00:00:00" + self.setText(now) + + +# ═══════════════════════════════════════════════════════════════ +# AnalogClock +# ═══════════════════════════════════════════════════════════════ + +class AnalogClock(View): + """AnalogClock — 模拟时钟(API 14+ 已弃用)""" + + def __init__(self, context=None): + super().__init__(context) + self._dial: Optional[Any] = None + self._hand_hour: Optional[Any] = None + self._hand_minute: Optional[Any] = None + self._running: bool = False + self._update_interval: int = 1000 + + def start(self): + self._running = True + self.invalidate() + + def stop(self): + self._running = False + + def onDraw(self, canvas: Any): + super().onDraw(canvas) + + def setDialDrawable(self, drawable: Any): + self._dial = drawable + + def setHourDrawable(self, drawable: Any): + self._hand_hour = drawable + + def setMinuteDrawable(self, drawable: Any): + self._hand_minute = drawable + + +# ═══════════════════════════════════════════════════════════════ +# Chronometer +# ═══════════════════════════════════════════════════════════════ + +class Chronometer(TextView): + """Chronometer — 计时器""" + + def __init__(self, context=None): + super().__init__(context) + self._base: int = 0 + self._format: Optional[str] = None + self._running: bool = False + self._visible: bool = True + self._on_chronometer_tick_listener: Optional[Callable] = None + self._count_down: bool = False + self._elapsed: float = 0.0 + + def start(self): + self._running = True + + def stop(self): + self._running = False + + def setBase(self, base: int): + self._base = base + + def getBase(self) -> int: + return self._base + + def setFormat(self, fmt: str): + self._format = fmt + + def getFormat(self) -> Optional[str]: + return self._format + + def setOnChronometerTickListener(self, listener: Optional[Callable]): + self._on_chronometer_tick_listener = listener + + def getOnChronometerTickListener(self) -> Optional[Callable]: + return self._on_chronometer_tick_listener + + def isCountDown(self) -> bool: + return self._count_down + + def setCountDown(self, count_down: bool): + self._count_down = count_down + + def getElapsedRealtime(self) -> float: + return self._elapsed + + def setVisibility(self, visibility: int): + self._visible = (visibility == View.VISIBLE) + super().setVisibility(visibility) + + def onDraw(self, canvas: Any): + super().onDraw(canvas) + if self._running and self._on_chronometer_tick_listener: + try: + self._on_chronometer_tick_listener(self) + except Exception: + pass + + +# ═══════════════════════════════════════════════════════════════ +# __all__ +# ═══════════════════════════════════════════════════════════════ + +__all__ = [ + # TextView hierarchy + "TextView", "EditText", "Button", "ImageButton", "CheckBox", + "RadioButton", "ToggleButton", "Switch", + # ImageView + "ImageView", + # Progress + "ProgressBar", "SeekBar", "RatingBar", + # AdapterView hierarchy + "AdapterView", "ListView", "GridView", "ExpandableListView", + "Spinner", "AutoCompleteTextView", "MultiAutoCompleteTextView", + # RecyclerView + "RecyclerView", "RecyclerViewAdapter", "RecyclerViewViewHolder", + "RecyclerViewLayoutManager", "LinearLayoutManager", + "GridLayoutManager", "StaggeredGridLayoutManager", + # Layouts + "LinearLayout", "RelativeLayout", "FrameLayout", + "TableLayout", "TableRow", + # Scrolling + "ScrollView", "HorizontalScrollView", + # WebView + "WebView", "WebSettings", + # Adapters + "BaseAdapter", "ArrayAdapter", "SimpleAdapter", "CursorAdapter", + "ExpandableListAdapter", "SimpleExpandableListAdapter", + "BaseExpandableListAdapter", + # ViewAnimator + "ViewAnimator", "ViewFlipper", "ViewSwitcher", + "ImageSwitcher", "TextSwitcher", + "AdapterViewAnimator", "StackView", "AdapterViewFlipper", + # Date/Time + "DatePicker", "TimePicker", "CalendarView", "NumberPicker", + "DigitalClock", "AnalogClock", "Chronometer", + # Media + "VideoView", "MediaController", + # Toast/Popup + "Toast", "PopupMenu", "PopupWindow", + # Toolbar + "Toolbar", "ActionMenuView", + # Tabs + "TabHost", "TabWidget", + # Search + "SearchView", + # Zoom + "ZoomControls", +] \ No newline at end of file diff --git a/android_pyapi/java/__init__.py b/android_pyapi/java/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/android_pyapi/java/__pycache__/__init__.cpython-310.pyc b/android_pyapi/java/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..1a1a495 Binary files /dev/null and b/android_pyapi/java/__pycache__/__init__.cpython-310.pyc differ diff --git a/android_pyapi/java/__pycache__/crypto.cpython-310.pyc b/android_pyapi/java/__pycache__/crypto.cpython-310.pyc new file mode 100644 index 0000000..228599e Binary files /dev/null and b/android_pyapi/java/__pycache__/crypto.cpython-310.pyc differ diff --git a/android_pyapi/java/__pycache__/io.cpython-310.pyc b/android_pyapi/java/__pycache__/io.cpython-310.pyc new file mode 100644 index 0000000..902ec88 Binary files /dev/null and b/android_pyapi/java/__pycache__/io.cpython-310.pyc differ diff --git a/android_pyapi/java/__pycache__/lang.cpython-310.pyc b/android_pyapi/java/__pycache__/lang.cpython-310.pyc new file mode 100644 index 0000000..18e3dd7 Binary files /dev/null and b/android_pyapi/java/__pycache__/lang.cpython-310.pyc differ diff --git a/android_pyapi/java/__pycache__/math.cpython-310.pyc b/android_pyapi/java/__pycache__/math.cpython-310.pyc new file mode 100644 index 0000000..5233e35 Binary files /dev/null and b/android_pyapi/java/__pycache__/math.cpython-310.pyc differ diff --git a/android_pyapi/java/__pycache__/net.cpython-310.pyc b/android_pyapi/java/__pycache__/net.cpython-310.pyc new file mode 100644 index 0000000..32d63eb Binary files /dev/null and b/android_pyapi/java/__pycache__/net.cpython-310.pyc differ diff --git a/android_pyapi/java/__pycache__/nio.cpython-310.pyc b/android_pyapi/java/__pycache__/nio.cpython-310.pyc new file mode 100644 index 0000000..d8dbd65 Binary files /dev/null and b/android_pyapi/java/__pycache__/nio.cpython-310.pyc differ diff --git a/android_pyapi/java/__pycache__/nio_file.cpython-310.pyc b/android_pyapi/java/__pycache__/nio_file.cpython-310.pyc new file mode 100644 index 0000000..0b75e61 Binary files /dev/null and b/android_pyapi/java/__pycache__/nio_file.cpython-310.pyc differ diff --git a/android_pyapi/java/__pycache__/security.cpython-310.pyc b/android_pyapi/java/__pycache__/security.cpython-310.pyc new file mode 100644 index 0000000..83345d4 Binary files /dev/null and b/android_pyapi/java/__pycache__/security.cpython-310.pyc differ diff --git a/android_pyapi/java/__pycache__/text.cpython-310.pyc b/android_pyapi/java/__pycache__/text.cpython-310.pyc new file mode 100644 index 0000000..d4d9266 Binary files /dev/null and b/android_pyapi/java/__pycache__/text.cpython-310.pyc differ diff --git a/android_pyapi/java/__pycache__/time.cpython-310.pyc b/android_pyapi/java/__pycache__/time.cpython-310.pyc new file mode 100644 index 0000000..075b376 Binary files /dev/null and b/android_pyapi/java/__pycache__/time.cpython-310.pyc differ diff --git a/android_pyapi/java/__pycache__/util.cpython-310.pyc b/android_pyapi/java/__pycache__/util.cpython-310.pyc new file mode 100644 index 0000000..1297792 Binary files /dev/null and b/android_pyapi/java/__pycache__/util.cpython-310.pyc differ diff --git a/android_pyapi/java/__pycache__/util_concurrent.cpython-310.pyc b/android_pyapi/java/__pycache__/util_concurrent.cpython-310.pyc new file mode 100644 index 0000000..c37022e Binary files /dev/null and b/android_pyapi/java/__pycache__/util_concurrent.cpython-310.pyc differ diff --git a/android_pyapi/java/__pycache__/util_regex.cpython-310.pyc b/android_pyapi/java/__pycache__/util_regex.cpython-310.pyc new file mode 100644 index 0000000..8f0f3c1 Binary files /dev/null and b/android_pyapi/java/__pycache__/util_regex.cpython-310.pyc differ diff --git a/android_pyapi/java/__pycache__/util_zip.cpython-310.pyc b/android_pyapi/java/__pycache__/util_zip.cpython-310.pyc new file mode 100644 index 0000000..cce469b Binary files /dev/null and b/android_pyapi/java/__pycache__/util_zip.cpython-310.pyc differ diff --git a/android_pyapi/java/crypto.py b/android_pyapi/java/crypto.py new file mode 100644 index 0000000..fe80f49 --- /dev/null +++ b/android_pyapi/java/crypto.py @@ -0,0 +1,628 @@ +""" +Java crypto standard library APIs implemented in Python. +javax.crypto package - Cipher, KeyGenerator, Mac, SecretKey, etc. +""" + +from .._core import Stub, AndroidOnlyError +from .lang import RuntimeException +import os as _os + +# Try to import cryptography library +try: + from cryptography.hazmat.primitives.ciphers import Cipher as _Cipher + from cryptography.hazmat.primitives.ciphers import algorithms as _algorithms + from cryptography.hazmat.primitives.ciphers import modes as _modes + from cryptography.hazmat.primitives import hashes as _hashes + from cryptography.hazmat.primitives.asymmetric import rsa as _rsa + from cryptography.hazmat.primitives.asymmetric import padding as _padding + from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC as _PBKDF2 + from cryptography.hazmat.backends import default_backend as _crypto_backend + HAS_CRYPTOGRAPHY = True +except ImportError: + HAS_CRYPTOGRAPHY = False + +from .security import ( + GeneralSecurityException, InvalidKeyException, + NoSuchAlgorithmException, NoSuchProviderException, + Key, SecretKeySpec, AlgorithmParameters, AlgorithmParametersSpi, + AlgorithmParameterGenerator, AlgorithmParameterGeneratorSpi, + Provider, SecureRandom, +) + +IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {}) +AEADBadTagException = type("AEADBadTagException", (GeneralSecurityException,), {}) +BadPaddingException = type("BadPaddingException", (GeneralSecurityException,), {}) +IllegalBlockSizeException = type("IllegalBlockSizeException", (GeneralSecurityException,), {}) +NoSuchPaddingException = type("NoSuchPaddingException", (GeneralSecurityException,), {}) +ShortBufferException = type("ShortBufferException", (GeneralSecurityException,), {}) +ExemptionMechanismException = type("ExemptionMechanismException", (GeneralSecurityException,), {}) + +__all__ = [ + "Cipher", "CipherSpi", + "KeyGenerator", "KeyGeneratorSpi", + "SecretKey", "SecretKeyFactory", "SecretKeyFactorySpi", + "Mac", "MacSpi", + "IvParameterSpec", "PBEParameterSpec", "GCMParameterSpec", + "OAEPParameterSpec", "PSource", "MGF1ParameterSpec", + "DHParameterSpec", "DSAParameterSpec", + "ECParameterSpec", "ECGenParameterSpec", "ECPoint", + "ECField", "ECFieldF2m", "ECFieldFp", "EllipticCurve", + "AlgorithmParameters", "AlgorithmParameterGenerator", + "AlgorithmParametersSpi", + "AEADBadTagException", "BadPaddingException", + "IllegalBlockSizeException", "NoSuchPaddingException", + "ShortBufferException", + "ExemptionMechanism", "ExemptionMechanismException", + "ExemptionMechanismSpi", +] + + +# ============================================================ +# SecretKey +# ============================================================ + +class SecretKey(Key): + def __init__(self, key_bytes=None, algorithm=""): + super().__init__() + self._key = key_bytes or b"" + self._algorithm = algorithm + + def getAlgorithm(self): + return self._algorithm + + def getFormat(self): + return "RAW" + + def getEncoded(self): + return self._key + + +# ============================================================ +# Cipher +# ============================================================ + +class Cipher(Stub): + ENCRYPT_MODE = 1 + DECRYPT_MODE = 2 + WRAP_MODE = 3 + UNWRAP_MODE = 4 + PUBLIC_KEY = 1 + PRIVATE_KEY = 2 + SECRET_KEY = 3 + + def __init__(self, transformation): + super().__init__() + self._transformation = transformation + self._opmode = 0 + self._key = None + self._iv = None + self._provider = Provider("AndroidOpenSSL", 1.0, "Android OpenSSL provider") + + @staticmethod + def getInstance(transformation, provider=None): + return Cipher(transformation) + + def init(self, opmode, key_or_cert, params=None, random=None): + self._opmode = opmode + self._key = key_or_cert + if isinstance(params, IvParameterSpec): + self._iv = params.getIV() + elif isinstance(params, GCMParameterSpec): + self._iv = params.getIV() + + def doFinal(self, input_data=None): + if input_data is None: + return b"" + if HAS_CRYPTOGRAPHY and self._key is not None: + try: + algo = self._transformation.split("/")[0].upper() + key_bytes = self._key.getEncoded() if hasattr(self._key, "getEncoded") else bytes(self._key) + iv = self._iv or b"\x00" * 16 + if algo == "AES": + if len(iv) == 12: + mode = _modes.GCM(iv) + else: + mode = _modes.CBC(iv) + cipher = _Cipher(_algorithms.AES(key_bytes), mode, backend=_crypto_backend()) + elif algo == "DES": + cipher = _Cipher(_algorithms.TripleDES(key_bytes), _modes.CBC(iv), backend=_crypto_backend()) + else: + return self._basic_cipher(input_data) + if self._opmode == Cipher.ENCRYPT_MODE: + encryptor = cipher.encryptor() + result = encryptor.update(input_data) + encryptor.finalize() + return result + else: + decryptor = cipher.decryptor() + result = decryptor.update(input_data) + decryptor.finalize() + return result + except Exception: + return self._basic_cipher(input_data) + else: + return self._basic_cipher(input_data) + + def _basic_cipher(self, data): + if self._opmode == Cipher.ENCRYPT_MODE: + if self._key is not None: + key_bytes = self._key.getEncoded() if hasattr(self._key, "getEncoded") else bytes(self._key) + return bytes([(b ^ key_bytes[i % len(key_bytes)]) & 0xFF for i, b in enumerate(data)]) + return data + else: + if self._key is not None: + key_bytes = self._key.getEncoded() if hasattr(self._key, "getEncoded") else bytes(self._key) + return bytes([(b ^ key_bytes[i % len(key_bytes)]) & 0xFF for i, b in enumerate(data)]) + return data + + def update(self, input_data): + return self._basic_cipher(input_data) + + def getBlockSize(self): + algo = self._transformation.split("/")[0].upper() + if algo == "AES": + return 16 + if algo == "DES" or algo == "DESede": + return 8 + return 1 + + def getOutputSize(self, input_len): + bs = self.getBlockSize() + if bs > 1: + return input_len + bs - (input_len % bs) + return input_len + + def getIV(self): + return self._iv + + def getParameters(self): + if self._iv: + return AlgorithmParameters(self._transformation.split("/")[0]) + return None + + def getAlgorithm(self): + return self._transformation + + def getProvider(self): + return self._provider + + def getExemptionMechanism(self): + return None + + def unwrap(self, wrapped_key, wrapped_key_algorithm, wrapped_key_type): + return SecretKey(wrapped_key, wrapped_key_algorithm) + + def wrap(self, key): + key_bytes = key.getEncoded() if hasattr(key, "getEncoded") else b"" + return self.doFinal(key_bytes) + + @staticmethod + def maxAllowedKeyLength(transformation): + return 2147483647 + + +class CipherSpi(Stub): + pass + + +# ============================================================ +# KeyGenerator +# ============================================================ + +class KeyGenerator(Stub): + def __init__(self, algorithm): + super().__init__() + self._algorithm = algorithm + self._key_size = 128 + self._secure_random = None + + @staticmethod + def getInstance(algorithm, provider=None): + return KeyGenerator(algorithm) + + def init(self, size_or_random, random=None): + if isinstance(size_or_random, int): + self._key_size = size_or_random + self._secure_random = random + else: + self._secure_random = size_or_random + + def generateKey(self): + key_bytes = _os.urandom(self._key_size // 8) + return SecretKey(key_bytes, self._algorithm) + + def getAlgorithm(self): + return self._algorithm + + def getProvider(self): + return Provider("AndroidOpenSSL", 1.0, "Android OpenSSL provider") + + +class KeyGeneratorSpi(Stub): + pass + + +# ============================================================ +# SecretKeyFactory +# ============================================================ + +class SecretKeyFactory(Stub): + def __init__(self, algorithm): + super().__init__() + self._algorithm = algorithm + + @staticmethod + def getInstance(algorithm, provider=None): + return SecretKeyFactory(algorithm) + + def generateSecret(self, key_spec): + if isinstance(key_spec, SecretKeySpec): + return SecretKey(key_spec.getEncoded(), self._algorithm) + return SecretKey(b"", self._algorithm) + + def getKeySpec(self, secret_key, key_spec_cls): + return SecretKeySpec(secret_key.getEncoded(), self._algorithm) + + def translateKey(self, key): + return key + + def getAlgorithm(self): + return self._algorithm + + +class SecretKeyFactorySpi(Stub): + pass + + +# ============================================================ +# Mac +# ============================================================ + +class Mac(Stub): + def __init__(self, algorithm): + super().__init__() + self._algorithm = algorithm + self._key = None + self._state = "UNINITIALIZED" + self._data = b"" + + @staticmethod + def getInstance(algorithm, provider=None): + return Mac(algorithm) + + def init(self, key, params=None): + self._key = key + self._state = "INITIALIZED" + self._data = b"" + + def update(self, input_data, offset=0, length=-1): + if length < 0: + self._data += bytes(input_data) + else: + self._data += bytes(input_data[offset:offset+length]) + + def doFinal(self, input_data=None): + if input_data is not None: + self._data += bytes(input_data) + if HAS_CRYPTOGRAPHY and self._key is not None: + try: + key_bytes = self._key.getEncoded() if hasattr(self._key, "getEncoded") else bytes(self._key) + algo = self._algorithm.lower().replace("-", "") + if "sha256" in algo or "sha-256" in self._algorithm: + h = _hashlib.sha256(key_bytes + self._data) + elif "sha1" in algo or "sha-1" in self._algorithm: + h = _hashlib.sha1(key_bytes + self._data) + elif "md5" in algo: + h = _hashlib.md5(key_bytes + self._data) + else: + h = _hashlib.sha256(key_bytes + self._data) + result = h.digest() + self._data = b"" + return result + except Exception: + pass + import hashlib as _hashlib + key_bytes = self._key.getEncoded() if self._key and hasattr(self._key, "getEncoded") else b"key" + h = _hashlib.sha256(key_bytes + self._data) + result = h.digest() + self._data = b"" + return result + + def reset(self): + self._data = b"" + + def getAlgorithm(self): + return self._algorithm + + def getMacLength(self): + return 32 + + def getProvider(self): + return Provider("AndroidOpenSSL", 1.0, "Android OpenSSL provider") + + +class MacSpi(Stub): + pass + + +# ============================================================ +# Parameter specs +# ============================================================ + +class IvParameterSpec(Stub): + def __init__(self, iv): + super().__init__() + self._iv = bytes(iv) + + def getIV(self): + return self._iv + + +class PBEParameterSpec(Stub): + def __init__(self, salt, iteration_count): + super().__init__() + self._salt = bytes(salt) + self._iteration_count = iteration_count + + def getSalt(self): + return self._salt + + def getIterationCount(self): + return self._iteration_count + + +class GCMParameterSpec(Stub): + def __init__(self, t_len, src): + super().__init__() + self._t_len = t_len + self._iv = bytes(src) + + def getTLen(self): + return self._t_len + + def getIV(self): + return self._iv + + +class OAEPParameterSpec(Stub): + def __init__(self, md_name, mgf_name, mgf_spec, p_src): + super().__init__() + self._md_name = md_name + self._mgf_name = mgf_name + self._mgf_spec = mgf_spec + self._p_src = p_src + + def getDigestAlgorithm(self): + return self._md_name + + def getMGFAlgorithm(self): + return self._mgf_name + + def getMGFParameters(self): + return self._mgf_spec + + def getPSource(self): + return self._p_src + + +class PSource(Stub): + class PSpecified(Stub): + DEFAULT = None + + def __init__(self, p): + super().__init__() + self._p = p + + def getValue(self): + return self._p + + def __init__(self, p_src_id): + super().__init__() + self._p_src_id = p_src_id + + def getAlgorithm(self): + return self._p_src_id + + +PSource.PSpecified.DEFAULT = PSource.PSpecified(b"") + + +class MGF1ParameterSpec(Stub): + SHA1 = None + SHA256 = None + SHA384 = None + SHA512 = None + + def __init__(self, md_name): + super().__init__() + self._md_name = md_name + + def getDigestAlgorithm(self): + return self._md_name + + def toString(self): + return f"MGF1 {self._md_name}" + + +MGF1ParameterSpec.SHA1 = MGF1ParameterSpec("SHA-1") +MGF1ParameterSpec.SHA256 = MGF1ParameterSpec("SHA-256") +MGF1ParameterSpec.SHA384 = MGF1ParameterSpec("SHA-384") +MGF1ParameterSpec.SHA512 = MGF1ParameterSpec("SHA-512") + + +# ============================================================ +# DH / DSA / EC parameter specs (stubs) +# ============================================================ + +class DHParameterSpec(Stub): + def __init__(self, p, g, l=0): + super().__init__() + self._p = p + self._g = g + self._l = l + + def getP(self): + return self._p + + def getG(self): + return self._g + + def getL(self): + return self._l + + +class DSAParameterSpec(Stub): + def __init__(self, p, q, g): + super().__init__() + self._p = p + self._q = q + self._g = g + + def getP(self): + return self._p + + def getQ(self): + return self._q + + def getG(self): + return self._g + + +class ECPoint(Stub): + def __init__(self, affine_x, affine_y): + super().__init__() + self._affine_x = affine_x + self._affine_y = affine_y + + def getAffineX(self): + return self._affine_x + + def getAffineY(self): + return self._affine_y + + def equals(self, other): + if isinstance(other, ECPoint): + return self._affine_x == other._affine_x and self._affine_y == other._affine_y + return False + + def hashCode(self): + return hash((self._affine_x, self._affine_y)) + + +class ECField(Stub): + def getFieldSize(self): + raise NotImplementedError + + +class ECFieldFp(ECField): + def __init__(self, p): + super().__init__() + self._p = p + + def getP(self): + return self._p + + def getFieldSize(self): + return self._p.bit_length() + + +class ECFieldF2m(ECField): + def __init__(self, m, ks=None): + super().__init__() + self._m = m + self._ks = ks + + def getM(self): + return self._m + + def getMidTermsOfReductionPolynomial(self): + return self._ks + + def getFieldSize(self): + return self._m + + +class EllipticCurve(Stub): + def __init__(self, field, a, b, seed=None): + super().__init__() + self._field = field + self._a = a + self._b = b + self._seed = seed + + def getField(self): + return self._field + + def getA(self): + return self._a + + def getB(self): + return self._b + + def getSeed(self): + return self._seed + + +class ECParameterSpec(Stub): + def __init__(self, curve, generator, order, cofactor, seed=None): + super().__init__() + self._curve = curve + self._generator = generator + self._order = order + self._cofactor = cofactor + + def getCurve(self): + return self._curve + + def getGenerator(self): + return self._generator + + def getOrder(self): + return self._order + + def getCofactor(self): + return self._cofactor + + +class ECGenParameterSpec(Stub): + def __init__(self, name): + super().__init__() + self._name = name + + def getName(self): + return self._name + + +# ============================================================ +# ExemptionMechanism +# ============================================================ + +class ExemptionMechanism(Stub): + def __init__(self, name): + super().__init__() + self._name = name + self._state = False + + @staticmethod + def getInstance(name, provider=None): + return ExemptionMechanism(name) + + def init(self, key): + self._state = False + + def isCryptoAllowed(self, key): + return True + + def genExemptionBlob(self, data=None): + return b"" + + def getName(self): + return self._name + + def getOutputSize(self, input_len): + return input_len + + def storeMechanism(self, mechanism): + pass + + +class ExemptionMechanismSpi(Stub): + pass \ No newline at end of file diff --git a/android_pyapi/java/io.py b/android_pyapi/java/io.py new file mode 100644 index 0000000..c757108 --- /dev/null +++ b/android_pyapi/java/io.py @@ -0,0 +1,1933 @@ +""" +Java I/O standard library APIs implemented in Python. +java.io package - File, streams, readers, writers, and exceptions. +""" + +from .._core import Stub, AndroidOnlyError +import io as _pyio +import os as _os +import sys as _sys +import errno as _errno +import stat as _stat +import threading as _threading +from datetime import datetime as _datetime +from collections import OrderedDict as _OrderedDict +from pathlib import Path as _PathLibPath + +__all__ = [ + "Serializable", "Externalizable", "ObjectStreamField", "ObjectStreamConstants", + "FileFilter", "FilenameFilter", "File", "FileDescriptor", + "FileInputStream", "FileOutputStream", + "BufferedInputStream", "BufferedOutputStream", + "BufferedReader", "BufferedWriter", + "InputStreamReader", "OutputStreamWriter", + "FileReader", "FileWriter", "PrintWriter", + "ByteArrayInputStream", "ByteArrayOutputStream", + "StringReader", "StringWriter", + "DataInputStream", "DataOutputStream", + "ObjectInputStream", "ObjectOutputStream", + "FilterInputStream", "FilterOutputStream", + "PipedInputStream", "PipedOutputStream", + "PushbackInputStream", "SequenceInputStream", + "RandomAccessFile", + "Closeable", "Flushable", + "IOError", "IOException", "FileNotFoundException", + "EOFException", "CharConversionException", + "ObjectStreamException", "SyncFailedException", + "UTFDataFormatException", "WriteAbortedException", + "UnsupportedEncodingException", +] + + +# ============================================================ +# Marker / mixin interfaces +# ============================================================ + +class Closeable(Stub): + def close(self): + raise NotImplementedError + +class Flushable(Stub): + def flush(self): + raise NotImplementedError + +class Serializable(Stub): + pass + +class Externalizable(Stub): + pass + +class ObjectStreamConstants: + STREAM_MAGIC = 0xACED + STREAM_VERSION = 5 + TC_BASE = 0x70 + TC_NULL = 0x70 + TC_REFERENCE = 0x71 + TC_CLASS = 0x72 + TC_OBJECT = 0x73 + TC_STRING = 0x74 + TC_ARRAY = 0x75 + TC_ENUM = 0x7E + TC_CLASSDESC = 0x72 + TC_PROXYCLASSDESC = 0x7D + TC_BLOCKDATA = 0x77 + TC_ENDBLOCKDATA = 0x78 + TC_RESET = 0x79 + TC_EXCEPTION = 0x7A + TC_LONGSTRING = 0x7C + baseWireHandle = 0x7E0000 + +class ObjectStreamField(Stub): + def __init__(self, name, typ): + self._name = name + self._type = typ + + def getName(self): + return self._name + + def getType(self): + return self._type + + def toString(self): + return f"{self._type} {self._name}" + + +# ============================================================ +# FileFilter / FilenameFilter +# ============================================================ + +class FileFilter(Stub): + def accept(self, pathname): + raise NotImplementedError + +class FilenameFilter(Stub): + def accept(self, dir, name): + raise NotImplementedError + + +# ============================================================ +# FileDescriptor +# ============================================================ + +class FileDescriptor(Stub): + _valid_fd = -1 + + def __init__(self, fd=None): + super().__init__() + if fd is not None: + self._valid_fd = fd + + def valid(self): + return self._valid_fd >= 0 + + def sync(self): + if self._valid_fd >= 0: + try: + _os.fsync(self._valid_fd) + except OSError: + pass + + def hashCode(self): + return self._valid_fd + + @staticmethod + def _from_fileno(fd): + return FileDescriptor(fd) + + +# ============================================================ +# Exception classes +# ============================================================ + +class IOException(Exception): + def __init__(self, message=None, cause=None): + super().__init__(message) + self._cause = cause + + def getCause(self): + return self._cause + +class FileNotFoundException(IOException): + pass + +class EOFException(IOException): + pass + +class CharConversionException(IOException): + pass + +class ObjectStreamException(IOException): + pass + +class SyncFailedException(IOException): + pass + +class UTFDataFormatException(IOException): + pass + +class WriteAbortedException(ObjectStreamException): + def __init__(self, message=None, cause=None): + super().__init__(message) + self.detail = cause + +class UnsupportedEncodingException(IOException): + pass + +class IOError(Exception): + def __init__(self, message=None, cause=None): + super().__init__(message) + self._cause = cause + + def getCause(self): + return self._cause + + +# ============================================================ +# File +# ============================================================ + +class File(Stub): + separator = _os.sep + separatorChar = _os.sep + pathSeparator = _os.pathsep + pathSeparatorChar = _os.pathsep + + def __init__(self, *args): + super().__init__() + if len(args) == 1: + path = args[0] + if isinstance(path, File): + self._path = path._path + else: + self._path = str(path) + elif len(args) == 2: + parent, child = args + if parent is None: + self._path = str(child) + else: + parent_str = parent._path if isinstance(parent, File) else str(parent) + child_str = str(child) + if child_str.startswith(self.separator): + self._path = child_str + else: + self._path = _os.path.join(parent_str, child_str) + else: + self._path = "" + self._path = self._path or "" + + def _resolve(self): + return _os.path.realpath(self._path) if self._path else "" + + def exists(self): + return _os.path.exists(self._path) + + def isFile(self): + return _os.path.isfile(self._path) + + def isDirectory(self): + return _os.path.isdir(self._path) + + def getName(self): + return _os.path.basename(self._path) + + def getPath(self): + return self._path + + def getAbsolutePath(self): + return _os.path.abspath(self._path) + + def getCanonicalPath(self): + return _os.path.realpath(self._path) + + def getParent(self): + p = _os.path.dirname(self._path) + return p if p else None + + def getParentFile(self): + p = self.getParent() + return File(p) if p else None + + def length(self): + try: + return _os.path.getsize(self._path) + except OSError: + return 0 + + def lastModified(self): + try: + return int(_os.path.getmtime(self._path) * 1000) + except OSError: + return 0 + + def createNewFile(self): + try: + f = _os.open(self._path, _os.O_CREAT | _os.O_EXCL, 0o644) + _os.close(f) + return True + except OSError: + return False + + def delete(self): + try: + if self.isDirectory(): + _os.rmdir(self._path) + else: + _os.remove(self._path) + return True + except OSError: + return False + + def mkdir(self): + try: + _os.mkdir(self._path) + return True + except OSError: + return False + + def mkdirs(self): + try: + _os.makedirs(self._path, exist_ok=True) + return True + except OSError: + return False + + def renameTo(self, dest): + try: + dest_path = dest._path if isinstance(dest, File) else str(dest) + _os.rename(self._path, dest_path) + return True + except OSError: + return False + + def list(self): + try: + return _os.listdir(self._path) + except OSError: + return None + + def listFiles(self, filter=None): + try: + names = _os.listdir(self._path) + files = [File(self._path, n) for n in names] + if filter: + return [f for f in files if filter.accept(f)] + return files + except OSError: + return None + + @staticmethod + def listRoots(): + if _sys.platform == "win32": + import string + return [File(d + ":\\") for d in string.ascii_uppercase if _os.path.exists(d + ":\\")] + return [File("/")] + + def canRead(self): + return _os.access(self._path, _os.R_OK) + + def canWrite(self): + return _os.access(self._path, _os.W_OK) + + def canExecute(self): + return _os.access(self._path, _os.X_OK) + + def setReadable(self, readable, ownerOnly=True): + try: + if readable: + if ownerOnly: + _os.chmod(self._path, _stat.S_IRUSR | (_stat.S_IRGRP if _os.stat(self._path).st_gid else 0)) + else: + _os.chmod(self._path, _stat.S_IRUSR | _stat.S_IRGRP | _stat.S_IROTH) + else: + mode = _os.stat(self._path).st_mode + if ownerOnly: + mode &= ~_stat.S_IRUSR + else: + mode &= ~(_stat.S_IRUSR | _stat.S_IRGRP | _stat.S_IROTH) + _os.chmod(self._path, mode) + return True + except OSError: + return False + + def setWritable(self, writable, ownerOnly=True): + try: + if writable: + if ownerOnly: + _os.chmod(self._path, _stat.S_IWUSR) + else: + _os.chmod(self._path, _stat.S_IWUSR | _stat.S_IWGRP | _stat.S_IWOTH) + else: + mode = _os.stat(self._path).st_mode + if ownerOnly: + mode &= ~_stat.S_IWUSR + else: + mode &= ~(_stat.S_IWUSR | _stat.S_IWGRP | _stat.S_IWOTH) + _os.chmod(self._path, mode) + return True + except OSError: + return False + + def setExecutable(self, executable, ownerOnly=True): + try: + if executable: + if ownerOnly: + _os.chmod(self._path, _stat.S_IXUSR) + else: + _os.chmod(self._path, _stat.S_IXUSR | _stat.S_IXGRP | _stat.S_IXOTH) + else: + mode = _os.stat(self._path).st_mode + if ownerOnly: + mode &= ~_stat.S_IXUSR + else: + mode &= ~(_stat.S_IXUSR | _stat.S_IXGRP | _stat.S_IXOTH) + _os.chmod(self._path, mode) + return True + except OSError: + return False + + def isHidden(self): + return _os.path.basename(self._path).startswith(".") + + def toURL(self): + from urllib.parse import quote + return f"file:{quote(self.getAbsolutePath())}" + + def toURI(self): + return self.toURL() + + def getTotalSpace(self): + try: + st = _os.statvfs(self._path) + return st.f_frsize * st.f_blocks + except OSError: + return 0 + + def getFreeSpace(self): + try: + st = _os.statvfs(self._path) + return st.f_frsize * st.f_bfree + except OSError: + return 0 + + def getUsableSpace(self): + try: + st = _os.statvfs(self._path) + return st.f_frsize * st.f_bavail + except OSError: + return 0 + + def compareTo(self, other): + if isinstance(other, File): + return self._path.__cmp__(other._path) + return -1 + + def equals(self, other): + if isinstance(other, File): + return self._path == other._path + return False + + def hashCode(self): + return hash(self._path) & 0x7FFFFFFF + + def toString(self): + return self._path + + def toPath(self): + return _PathLibPath(self._path) + + def __str__(self): + return self._path + + def __repr__(self): + return f"File({self._path!r})" + + +# ============================================================ +# FileInputStream +# ============================================================ + +class FileInputStream(Closeable): + def __init__(self, file): + super().__init__() + if isinstance(file, File): + path = file.getPath() + else: + path = str(file) + self._path = path + self._fd = None + self._channel = None + self._closed = False + try: + self._fd = _os.open(path, _os.O_RDONLY) + except OSError as e: + raise FileNotFoundException(str(e)) + + def read(self, b=None, off=None, length=None): + if self._closed: + raise IOException("Stream closed") + if b is None: + data = _os.read(self._fd, 1) + return data[0] if data else -1 + if isinstance(b, (bytes, bytearray)): + if off is None: + off = 0 + if length is None: + length = len(b) - off + data = _os.read(self._fd, length) + if not data: + return -1 + n = len(data) + if isinstance(b, bytearray): + b[off:off+n] = data + else: + ba = bytearray(b) + ba[off:off+n] = data + b[:] = bytes(ba) + return n + return -1 + + def skip(self, n): + try: + pos = _os.lseek(self._fd, 0, _os.SEEK_CUR) + end = _os.lseek(self._fd, 0, _os.SEEK_END) + _os.lseek(self._fd, pos, _os.SEEK_SET) + actual = min(n, end - pos) + _os.lseek(self._fd, actual, _os.SEEK_CUR) + return actual + except OSError: + return 0 + + def available(self): + if self._closed: + return 0 + try: + pos = _os.lseek(self._fd, 0, _os.SEEK_CUR) + end = _os.lseek(self._fd, 0, _os.SEEK_END) + _os.lseek(self._fd, pos, _os.SEEK_SET) + return end - pos + except OSError: + return 0 + + def close(self): + if not self._closed: + self._closed = True + if self._fd is not None: + _os.close(self._fd) + self._fd = None + + def getFD(self): + if self._closed: + raise IOException("Stream closed") + return FileDescriptor(self._fd) + + def getChannel(self): + if self._closed: + raise IOException("Stream closed") + if self._channel is None: + import io as _pyio_mod + self._channel = _pyio_mod.FileIO(self._fd, closefd=False) + return self._channel + + def __del__(self): + self.close() + + +# ============================================================ +# FileOutputStream +# ============================================================ + +class FileOutputStream(Closeable, Flushable): + def __init__(self, file, append=False): + super().__init__() + if isinstance(file, File): + path = file.getPath() + elif isinstance(file, FileDescriptor): + self._fd = file._valid_fd + self._path = None + self._closed = False + self._channel = None + return + else: + path = str(file) + self._path = path + self._closed = False + self._channel = None + try: + flags = _os.O_WRONLY | _os.O_CREAT + if append: + flags |= _os.O_APPEND + else: + flags |= _os.O_TRUNC + self._fd = _os.open(path, flags, 0o644) + except OSError as e: + raise FileNotFoundException(str(e)) + + def write(self, b=None, off=None, length=None): + if self._closed: + raise IOException("Stream closed") + if b is None: + return + if isinstance(b, int): + _os.write(self._fd, bytes([b])) + elif isinstance(b, (bytes, bytearray)): + if off is None: + data = bytes(b) + else: + data = bytes(b[off:off+length if length else (len(b)-off)]) + _os.write(self._fd, data) + else: + if off is None: + data = bytes(b) + else: + data = bytes(b[off:off+length if length else (len(b)-off)]) + _os.write(self._fd, data) + + def close(self): + if not self._closed: + self._closed = True + if self._fd is not None: + _os.close(self._fd) + self._fd = None + + def getFD(self): + if self._closed: + raise IOException("Stream closed") + return FileDescriptor(self._fd) + + def getChannel(self): + if self._closed: + raise IOException("Stream closed") + if self._channel is None: + import io as _pyio_mod + self._channel = _pyio_mod.FileIO(self._fd, closefd=False, mode='ab') + return self._channel + + def flush(self): + if self._fd is not None: + try: + _os.fsync(self._fd) + except OSError: + pass + + def __del__(self): + self.close() + + +# ============================================================ +# FilterInputStream / FilterOutputStream (abstract wrappers) +# ============================================================ + +class FilterInputStream(Closeable): + def __init__(self, inp): + super().__init__() + self._in = inp + + def read(self, *args): + return self._in.read(*args) + + def skip(self, n): + return self._in.skip(n) + + def available(self): + return self._in.available() + + def close(self): + self._in.close() + + def mark(self, readlimit): + pass + + def reset(self): + raise IOException("mark/reset not supported") + + def markSupported(self): + return False + + +class FilterOutputStream(Closeable, Flushable): + def __init__(self, out): + super().__init__() + self._out = out + + def write(self, *args): + self._out.write(*args) + + def close(self): + self._out.close() + + def flush(self): + self._out.flush() + + +# ============================================================ +# BufferedInputStream +# ============================================================ + +class BufferedInputStream(FilterInputStream): + def __init__(self, inp, size=8192): + super().__init__(inp) + self._buffer = bytearray() + self._buf_size = size + self._pos = 0 + self._count = 0 + + def read(self, b=None, off=None, length=None): + if b is None: + if self._pos >= self._count: + self._fill() + if self._pos >= self._count: + return -1 + val = self._buffer[self._pos] + self._pos += 1 + return val + if off is None: + off = 0 + if length is None: + length = len(b) + total = 0 + while total < length: + if self._pos >= self._count: + if not self._fill(): + break + chunk = min(length - total, self._count - self._pos) + if isinstance(b, bytearray): + b[off+total:off+total+chunk] = self._buffer[self._pos:self._pos+chunk] + else: + ba = bytearray(b) + ba[off+total:off+total+chunk] = self._buffer[self._pos:self._pos+chunk] + b[:] = bytes(ba) + self._pos += chunk + total += chunk + return total if total > 0 else -1 + + def _fill(self): + data = self._in.read(bytearray(self._buf_size), 0, self._buf_size) + if data is None or data == -1: + return False + n = data if isinstance(data, int) else len(data) + self._buffer = bytearray(self._in._buffer if hasattr(self._in, '_buffer') else data) if n > 0 else bytearray() + self._pos = 0 + self._count = n + return n > 0 + + +# ============================================================ +# BufferedOutputStream +# ============================================================ + +class BufferedOutputStream(FilterOutputStream): + def __init__(self, out, size=8192): + super().__init__(out) + self._buf = bytearray() + self._buf_size = size + + def write(self, b=None, off=None, length=None): + if b is None: + return + if isinstance(b, int): + self._buf.append(b & 0xFF) + else: + if off is not None and length is not None: + data = b[off:off+length] + else: + data = b + self._buf.extend(data) + if len(self._buf) >= self._buf_size: + self.flush() + + def flush(self): + if self._buf: + self._out.write(bytes(self._buf)) + self._buf.clear() + self._out.flush() + + +# ============================================================ +# InputStreamReader +# ============================================================ + +class InputStreamReader(Closeable): + def __init__(self, inp, charset=None): + super().__init__() + self._in = inp + self._charset = charset or "utf-8" + self._buffer = None + + def read(self, cbuf=None, off=None, length=None): + if cbuf is None: + data = self._in.read() + if data is None or data == -1: + return -1 + return data & 0xFF + if off is None: + off = 0 + if length is None: + length = len(cbuf) + raw = self._in.read(bytearray(length), 0, length) + if raw is None or raw == -1: + return -1 + n = len(raw) if isinstance(raw, (bytes, bytearray)) else raw + if isinstance(raw, int): + return raw + decoded = raw.decode(self._charset, errors='replace') + for i, ch in enumerate(decoded): + if off + i < len(cbuf): + cbuf[off + i] = ch + return len(decoded) + + def getEncoding(self): + return self._charset + + def close(self): + self._in.close() + + +# ============================================================ +# OutputStreamWriter +# ============================================================ + +class OutputStreamWriter(Closeable, Flushable): + def __init__(self, out, charset=None): + super().__init__() + self._out = out + self._charset = charset or "utf-8" + + def write(self, s, off=None, length=None): + if isinstance(s, str): + if off is not None and length is not None: + s = s[off:off+length] + self._out.write(s.encode(self._charset)) + elif isinstance(s, (list, tuple)): + chars = "".join(s) + if off is not None and length is not None: + chars = chars[off:off+length] + self._out.write(chars.encode(self._charset)) + + def flush(self): + self._out.flush() + + def close(self): + self._out.close() + + def getEncoding(self): + return self._charset + + +# ============================================================ +# FileReader +# ============================================================ + +class FileReader(InputStreamReader): + def __init__(self, file): + if isinstance(file, File): + path = file.getPath() + else: + path = str(file) + inp = FileInputStream(path) + super().__init__(inp, "utf-8") + self._file_path = path + + +# ============================================================ +# FileWriter +# ============================================================ + +class FileWriter(OutputStreamWriter): + def __init__(self, file, append=False): + if isinstance(file, File): + path = file.getPath() + append = append + else: + path = str(file) + out = FileOutputStream(path, append) + super().__init__(out, "utf-8") + self._file_path = path + + +# ============================================================ +# BufferedReader +# ============================================================ + +class BufferedReader(Closeable): + def __init__(self, reader, size=8192): + super().__init__() + self._reader = reader + self._buf_size = size + self._buffer = "" + self._pos = 0 + self._closed = False + + def read(self, cbuf=None, off=None, length=None): + if self._closed: + raise IOException("Stream closed") + if cbuf is None: + if self._pos >= len(self._buffer): + self._fill() + if self._pos >= len(self._buffer): + return -1 + ch = ord(self._buffer[self._pos]) + self._pos += 1 + return ch + if off is None: + off = 0 + if length is None: + length = len(cbuf) + total = 0 + while total < length: + if self._pos >= len(self._buffer): + if not self._fill(): + break + chunk = min(length - total, len(self._buffer) - self._pos) + for i in range(chunk): + cbuf[off + total + i] = self._buffer[self._pos + i] + self._pos += chunk + total += chunk + return total if total > 0 else -1 + + def _fill(self): + if hasattr(self._reader, '_in'): + raw = self._reader._in.read(bytearray(self._buf_size), 0, self._buf_size) + if raw is None or raw == -1: + return False + data = bytes(raw) if isinstance(raw, int) else bytes(raw) + self._buffer = data.decode(self._reader.getEncoding() if hasattr(self._reader, 'getEncoding') else "utf-8", errors='replace') + else: + raw = [] + for _ in range(self._buf_size): + ch = self._reader.read() + if ch == -1: + break + raw.append(chr(ch)) + self._buffer = "".join(raw) + self._pos = 0 + return len(self._buffer) > 0 + + def readLine(self): + if self._closed: + raise IOException("Stream closed") + result = [] + while True: + if self._pos >= len(self._buffer): + if not self._fill(): + break + while self._pos < len(self._buffer): + ch = self._buffer[self._pos] + self._pos += 1 + if ch == "\n": + return "".join(result) + if ch == "\r": + if self._pos < len(self._buffer) and self._buffer[self._pos] == "\n": + self._pos += 1 + return "".join(result) + result.append(ch) + return "".join(result) if result else None + + def ready(self): + return self._pos < len(self._buffer) + + def mark(self, readAheadLimit): + self._mark_pos = self._pos + self._mark_buffer = self._buffer + + def reset(self): + if hasattr(self, '_mark_pos'): + self._pos = self._mark_pos + self._buffer = self._mark_buffer + else: + raise IOException("Mark not set") + + def markSupported(self): + return True + + def skip(self, n): + skipped = 0 + while skipped < n: + if self._pos >= len(self._buffer): + if not self._fill(): + break + chunk = min(n - skipped, len(self._buffer) - self._pos) + self._pos += chunk + skipped += chunk + return skipped + + def close(self): + self._closed = True + self._reader.close() + + def lines(self): + line = self.readLine() + while line is not None: + yield line + line = self.readLine() + + +# ============================================================ +# BufferedWriter +# ============================================================ + +class BufferedWriter(Closeable, Flushable): + def __init__(self, writer, size=8192): + super().__init__() + self._writer = writer + self._buf_size = size + self._buffer = [] + + def write(self, s, off=None, length=None): + if isinstance(s, str): + if off is not None and length is not None: + s = s[off:off+length] + self._buffer.append(s) + elif isinstance(s, (list, tuple)): + chars = "".join(s) + if off is not None and length is not None: + chars = chars[off:off+length] + self._buffer.append(chars) + elif isinstance(s, int): + self._buffer.append(chr(s)) + if sum(len(x) for x in self._buffer) >= self._buf_size: + self.flush() + + def newLine(self): + self._buffer.append("\n") + + def flush(self): + if self._buffer: + data = "".join(self._buffer) + self._writer.write(data) + self._buffer.clear() + self._writer.flush() + + def close(self): + self.flush() + self._writer.close() + + +# ============================================================ +# PrintWriter +# ============================================================ + +class PrintWriter(Closeable, Flushable): + def __init__(self, out, autoFlush=False): + super().__init__() + if isinstance(out, _pyio.TextIOBase): + self._out = out + else: + self._out = out + self._auto_flush = autoFlush + self._error = False + + def _write_str(self, s): + s = str(s) + if hasattr(self._out, 'write'): + self._out.write(s) + else: + self._out.write(s.encode('utf-8')) + + def print_bool(self, b): + self._write_str(str(b)) + if self._auto_flush: + self.flush() + + def print_char(self, c): + self._write_str(str(c)) + if self._auto_flush: + self.flush() + + def print_char_array(self, c): + self._write_str("".join(c)) + if self._auto_flush: + self.flush() + + def print_double(self, d): + self._write_str(str(d)) + if self._auto_flush: + self.flush() + + def print_float(self, f): + self._write_str(str(f)) + if self._auto_flush: + self.flush() + + def print_int(self, i): + self._write_str(str(i)) + if self._auto_flush: + self.flush() + + def print_long(self, l): + self._write_str(str(l)) + if self._auto_flush: + self.flush() + + def print_obj(self, obj): + self._write_str(str(obj)) + if self._auto_flush: + self.flush() + + def print_str(self, s): + self._write_str(str(s) if s is not None else "null") + if self._auto_flush: + self.flush() + + print = print_str + print_boolean = print_bool + + def println(self, s=None): + if s is None: + self._write_str("\n") + else: + self._write_str(str(s) + "\n") + if self._auto_flush: + self.flush() + + def println_bool(self, b): + self._write_str(str(b) + "\n") + if self._auto_flush: + self.flush() + + def println_char(self, c): + self._write_str(str(c) + "\n") + if self._auto_flush: + self.flush() + + def println_char_array(self, c): + self._write_str("".join(c) + "\n") + if self._auto_flush: + self.flush() + + def println_double(self, d): + self._write_str(str(d) + "\n") + if self._auto_flush: + self.flush() + + def println_float(self, f): + self._write_str(str(f) + "\n") + if self._auto_flush: + self.flush() + + def println_int(self, i): + self._write_str(str(i) + "\n") + if self._auto_flush: + self.flush() + + def println_long(self, l): + self._write_str(str(l) + "\n") + if self._auto_flush: + self.flush() + + def println_obj(self, obj): + self._write_str(str(obj) + "\n") + if self._auto_flush: + self.flush() + + def write(self, s, off=None, length=None): + if isinstance(s, str): + if off is not None and length is not None: + s = s[off:off+length] + self._write_str(s) + else: + self._write_str(str(s)) + + def format(self, fmt, *args): + s = str(fmt) % args if args else str(fmt) + self._write_str(s) + return self + + def printf(self, fmt, *args): + return self.format(fmt, *args) + + def flush(self): + if hasattr(self._out, 'flush'): + self._out.flush() + + def close(self): + self.flush() + if hasattr(self._out, 'close'): + self._out.close() + + def checkError(self): + return self._error + + def setError(self): + self._error = True + + +# ============================================================ +# ByteArrayInputStream +# ============================================================ + +class ByteArrayInputStream(Closeable): + def __init__(self, buf): + super().__init__() + self._buf = bytes(buf) + self._pos = 0 + self._count = len(self._buf) + self._mark = 0 + self._closed = False + + def read(self, b=None, off=None, length=None): + if self._closed: + raise IOException("Stream closed") + if b is None: + if self._pos >= self._count: + return -1 + val = self._buf[self._pos] & 0xFF + self._pos += 1 + return val + if off is None: + off = 0 + if length is None: + length = len(b) + avail = self._count - self._pos + if avail <= 0: + return -1 + n = min(length, avail) + if isinstance(b, bytearray): + b[off:off+n] = self._buf[self._pos:self._pos+n] + else: + ba = bytearray(b) + ba[off:off+n] = self._buf[self._pos:self._pos+n] + b[:] = bytes(ba) + self._pos += n + return n + + def skip(self, n): + if self._closed: + return 0 + avail = self._count - self._pos + actual = min(n, avail) + self._pos += actual + return actual + + def available(self): + if self._closed: + return 0 + return self._count - self._pos + + def close(self): + self._closed = True + + def mark(self, readAheadLimit): + self._mark = self._pos + + def reset(self): + self._pos = self._mark + + +# ============================================================ +# ByteArrayOutputStream +# ============================================================ + +class ByteArrayOutputStream(Closeable, Flushable): + def __init__(self, size=32): + super().__init__() + self._buf = bytearray() + self._closed = False + + def write(self, b=None, off=None, length=None): + if self._closed: + raise IOException("Stream closed") + if b is None: + return + if isinstance(b, int): + self._buf.append(b & 0xFF) + elif isinstance(b, (bytes, bytearray)): + if off is not None and length is not None: + self._buf.extend(b[off:off+length]) + else: + self._buf.extend(b) + + def toByteArray(self): + return bytes(self._buf) + + def size(self): + return len(self._buf) + + def toString(self, charset=None): + if charset: + return bytes(self._buf).decode(charset, errors='replace') + return bytes(self._buf).decode('utf-8', errors='replace') + + def reset(self): + self._buf.clear() + + def close(self): + self._closed = True + + def flush(self): + pass + + +# ============================================================ +# StringReader +# ============================================================ + +class StringReader(Closeable): + def __init__(self, s): + super().__init__() + self._str = s + self._pos = 0 + self._length = len(s) + self._mark = 0 + self._closed = False + + def read(self, cbuf=None, off=None, length=None): + if self._closed: + raise IOException("Stream closed") + if cbuf is None: + if self._pos >= self._length: + return -1 + val = ord(self._str[self._pos]) + self._pos += 1 + return val + if off is None: + off = 0 + if length is None: + length = len(cbuf) + avail = self._length - self._pos + if avail <= 0: + return -1 + n = min(length, avail) + for i in range(n): + cbuf[off + i] = self._str[self._pos + i] + self._pos += n + return n + + def skip(self, n): + avail = self._length - self._pos + actual = min(n, avail) + self._pos += actual + return max(actual, 0) + + def ready(self): + return self._pos < self._length + + def mark(self, readAheadLimit): + self._mark = self._pos + + def reset(self): + self._pos = self._mark + + def markSupported(self): + return True + + def close(self): + self._closed = True + + +# ============================================================ +# StringWriter +# ============================================================ + +class StringWriter(Closeable, Flushable): + def __init__(self): + super().__init__() + self._buffer = [] + self._closed = False + + def write(self, s, off=None, length=None): + if self._closed: + raise IOException("Stream closed") + if isinstance(s, str): + if off is not None and length is not None: + s = s[off:off+length] + self._buffer.append(s) + elif isinstance(s, int): + self._buffer.append(chr(s)) + elif isinstance(s, (list, tuple)): + chars = "".join(s) + if off is not None and length is not None: + chars = chars[off:off+length] + self._buffer.append(chars) + + def flush(self): + pass + + def close(self): + self._closed = True + + def toString(self): + return "".join(self._buffer) + + def getBuffer(self): + return "".join(self._buffer) + + +# ============================================================ +# DataInputStream +# ============================================================ + +class DataInputStream(Closeable): + def __init__(self, inp): + super().__init__() + self._in = inp + + def read(self, b=None): + if b is None: + return self._in.read() + return self._in.read(b) + + def readBoolean(self): + v = self._in.read() + if v == -1: + raise EOFException() + return bool(v) + + def readByte(self): + v = self._in.read() + if v == -1: + raise EOFException() + return (v & 0xFF) if v <= 127 else (v - 256) + + def readChar(self): + hi = self._in.read() + lo = self._in.read() + if hi == -1 or lo == -1: + raise EOFException() + return chr((hi << 8) | lo) + + def readDouble(self): + return _struct.unpack(">d", self._read_bytes(8))[0] + + def readFloat(self): + return _struct.unpack(">f", self._read_bytes(4))[0] + + def readFully(self, b, off=0, length=None): + if length is None: + length = len(b) + total = 0 + while total < length: + n = self._in.read(b, off + total, length - total) + if n == -1: + raise EOFException() + total += n + + def readInt(self): + return _struct.unpack(">i", self._read_bytes(4))[0] + + def readLong(self): + return _struct.unpack(">q", self._read_bytes(8))[0] + + def readShort(self): + v = _struct.unpack(">h", self._read_bytes(2))[0] + return v + + def readUTF(self): + length = self.readUnsignedShort() + data = self._read_bytes(length) + return data.decode("utf-8") + + def readUnsignedByte(self): + v = self._in.read() + if v == -1: + raise EOFException() + return v & 0xFF + + def readUnsignedShort(self): + hi = self._in.read() + lo = self._in.read() + if hi == -1 or lo == -1: + raise EOFException() + return ((hi & 0xFF) << 8) | (lo & 0xFF) + + def skipBytes(self, n): + return self._in.skip(n) + + def _read_bytes(self, n): + buf = bytearray(n) + total = 0 + while total < n: + r = self._in.read(buf, total, n - total) + if r == -1: + raise EOFException() + total += r + return bytes(buf) + + def close(self): + self._in.close() + + +# ============================================================ +# DataOutputStream +# ============================================================ + +import struct as _struct + +class DataOutputStream(Closeable, Flushable): + def __init__(self, out): + super().__init__() + self._out = out + self._written = 0 + + def write(self, b=None, off=None, length=None): + if b is not None: + self._out.write(b, off, length) + self._written += length if length else (len(b) if isinstance(b, (bytes, bytearray)) else 1) + + def writeBoolean(self, v): + self._out.write(int(v)) + + def writeByte(self, v): + self._out.write(v & 0xFF) + + def writeBytes(self, s): + for ch in str(s): + self._out.write(ord(ch) & 0xFF) + + def writeChar(self, v): + self._out.write((v >> 8) & 0xFF) + self._out.write(v & 0xFF) + + def writeChars(self, s): + for ch in str(s): + self.writeChar(ord(ch)) + + def writeDouble(self, v): + self._write_struct(">d", v) + + def writeFloat(self, v): + self._write_struct(">f", v) + + def writeInt(self, v): + self._write_struct(">i", v) + + def writeLong(self, v): + self._write_struct(">q", v) + + def writeShort(self, v): + self._write_struct(">h", v) + + def writeUTF(self, s): + encoded = str(s).encode("utf-8") + self.writeShort(len(encoded)) + for b in encoded: + self._out.write(b) + + def size(self): + return self._written + + def _write_struct(self, fmt, v): + data = _struct.pack(fmt, v) + for b in data: + self._out.write(b) + + def flush(self): + self._out.flush() + + def close(self): + self._out.close() + + +# ============================================================ +# ObjectInputStream / ObjectOutputStream (stub) +# ============================================================ + +class ObjectInputStream(Closeable): + def __init__(self, inp): + super().__init__() + self._in = inp + + def readObject(self): + raise AndroidOnlyError("ObjectInputStream.readObject()") + + def readInt(self): + return DataInputStream(self._in).readInt() + + def close(self): + self._in.close() + + +class ObjectOutputStream(Closeable, Flushable): + def __init__(self, out): + super().__init__() + self._out = out + + def writeObject(self, obj): + raise AndroidOnlyError("ObjectOutputStream.writeObject()") + + def writeInt(self, v): + DataOutputStream(self._out).writeInt(v) + + def flush(self): + self._out.flush() + + def close(self): + self._out.close() + + +# ============================================================ +# PipedInputStream / PipedOutputStream +# ============================================================ + +class PipedInputStream(Closeable): + def __init__(self, src=None, pipe_size=1024): + super().__init__() + self._buffer = bytearray() + self._pipe_size = pipe_size + self._closed = False + self._lock = _threading.RLock() + self._data_available = _threading.Condition(self._lock) + if src: + src.connect(self) + + def connect(self, src): + src._sink = self + + def receive(self, b): + with self._lock: + self._buffer.append(b & 0xFF) + self._data_available.notify() + + def read(self, b=None, off=None, length=None): + with self._lock: + while len(self._buffer) == 0 and not self._closed: + self._data_available.wait(timeout=1) + if self._closed and len(self._buffer) == 0: + return -1 + if b is None: + if len(self._buffer) == 0: + return -1 + return self._buffer.pop(0) + if off is None: + off = 0 + if length is None: + length = len(b) + n = min(length, len(self._buffer)) + for i in range(n): + if isinstance(b, bytearray): + b[off + i] = self._buffer.pop(0) + else: + b[off + i] = self._buffer.pop(0) + return n + + def close(self): + with self._lock: + self._closed = True + self._data_available.notify_all() + + +class PipedOutputStream(Closeable): + def __init__(self, sink=None): + super().__init__() + self._sink = sink + + def connect(self, sink): + self._sink = sink + + def write(self, b, off=None, length=None): + if self._sink is None: + raise IOException("Pipe not connected") + if isinstance(b, int): + self._sink.receive(b) + else: + if off is not None and length is not None: + for i in range(off, off + length): + self._sink.receive(b[i]) + else: + for byte in b: + self._sink.receive(byte) + + def flush(self): + pass + + def close(self): + if self._sink: + self._sink.close() + + +# ============================================================ +# PushbackInputStream +# ============================================================ + +class PushbackInputStream(FilterInputStream): + def __init__(self, inp, size=1): + super().__init__(inp) + self._pushback_buf = bytearray() + self._max_size = size + + def read(self, b=None, off=None, length=None): + if b is None: + if self._pushback_buf: + return self._pushback_buf.pop(0) + return self._in.read() + if off is None: + off = 0 + if length is None: + length = len(b) + total = 0 + while self._pushback_buf and total < length: + if isinstance(b, bytearray): + b[off + total] = self._pushback_buf.pop(0) + else: + ba = bytearray(b) + ba[off + total] = self._pushback_buf.pop(0) + b[:] = bytes(ba) + total += 1 + if total < length: + n = self._in.read(b, off + total, length - total) + if n > 0: + total += n + return total if total > 0 else -1 + + def unread(self, b): + if isinstance(b, int): + if len(self._pushback_buf) >= self._max_size: + raise IOException("Pushback buffer overflow") + self._pushback_buf.append(b & 0xFF) + else: + for byte_val in b: + self.unread(byte_val) + + def close(self): + self._pushback_buf.clear() + self._in.close() + + +# ============================================================ +# SequenceInputStream +# ============================================================ + +class SequenceInputStream(Closeable): + def __init__(self, streams): + super().__init__() + if hasattr(streams, 'hasMoreElements'): + self._streams = [] + while streams.hasMoreElements(): + self._streams.append(streams.nextElement()) + else: + self._streams = list(streams) + self._current_idx = 0 + + def _current(self): + if self._current_idx < len(self._streams): + return self._streams[self._current_idx] + return None + + def read(self, b=None, off=None, length=None): + while self._current(): + inp = self._current() + if b is None: + result = inp.read() + else: + result = inp.read(b, off, length) + if result != -1: + return result + self._current_idx += 1 + return -1 + + def close(self): + for s in self._streams: + s.close() + + +# ============================================================ +# RandomAccessFile +# ============================================================ + +class RandomAccessFile(Closeable, Flushable): + def __init__(self, file, mode="r"): + super().__init__() + if isinstance(file, File): + path = file.getPath() + else: + path = str(file) + self._path = path + self._mode = mode + self._closed = False + py_modes = {"r": "rb", "rw": "r+b", "rws": "r+b", "rwd": "r+b"} + py_mode = py_modes.get(mode, "r+b") + try: + if "w" in mode and mode != "r": + self._file = open(path, py_mode) + else: + self._file = open(path, py_mode) + except FileNotFoundError as e: + if "w" in mode: + self._file = open(path, "w+b") + else: + raise FileNotFoundException(str(e)) + + def seek(self, pos): + self._file.seek(pos) + + def getFilePointer(self): + return self._file.tell() + + def length(self): + pos = self._file.tell() + self._file.seek(0, 2) + end = self._file.tell() + self._file.seek(pos) + return end + + def read(self, b=None, off=None, length=None): + if b is None: + data = self._file.read(1) + return data[0] if data else -1 + if off is None: + off = 0 + if length is None: + length = len(b) + data = self._file.read(length) + if not data: + return -1 + n = len(data) + if isinstance(b, bytearray): + b[off:off+n] = data + else: + ba = bytearray(b) + ba[off:off+n] = data + b[:] = bytes(ba) + return n + + def readBoolean(self): + v = self.read() + if v == -1: + raise EOFException() + return bool(v) + + def readByte(self): + v = self.read() + if v == -1: + raise EOFException() + return (v & 0xFF) if v <= 127 else (v - 256) + + def readChar(self): + hi = self.read() + lo = self.read() + if hi == -1 or lo == -1: + raise EOFException() + return chr((hi << 8) | lo) + + def readDouble(self): + data = self._read_bytes(8) + return _struct.unpack(">d", data)[0] + + def readFloat(self): + data = self._read_bytes(4) + return _struct.unpack(">f", data)[0] + + def readInt(self): + data = self._read_bytes(4) + return _struct.unpack(">i", data)[0] + + def readLine(self): + result = [] + while True: + ch = self.read() + if ch == -1: + break + if ch == 0x0A: + break + if ch == 0x0D: + next_ch = self.read() + if next_ch != 0x0A: + self.seek(self.getFilePointer() - 1) + break + result.append(chr(ch)) + return "".join(result) if result else None + + def readLong(self): + data = self._read_bytes(8) + return _struct.unpack(">q", data)[0] + + def readShort(self): + data = self._read_bytes(2) + return _struct.unpack(">h", data)[0] + + def readUnsignedByte(self): + v = self.read() + if v == -1: + raise EOFException() + return v & 0xFF + + def readUnsignedShort(self): + hi = self.read() + lo = self.read() + if hi == -1 or lo == -1: + raise EOFException() + return ((hi & 0xFF) << 8) | (lo & 0xFF) + + def readUTF(self): + length = self.readUnsignedShort() + data = self._read_bytes(length) + return data.decode("utf-8") + + def write(self, b=None, off=None, length=None): + if b is None: + return + if isinstance(b, int): + self._file.write(bytes([b & 0xFF])) + else: + if off is not None and length is not None: + self._file.write(bytes(b[off:off+length])) + else: + self._file.write(bytes(b)) + + def writeBoolean(self, v): + self.write(int(v)) + + def writeByte(self, v): + self.write(v & 0xFF) + + def writeBytes(self, s): + for ch in str(s): + self.write(ord(ch) & 0xFF) + + def writeChar(self, v): + self.write((v >> 8) & 0xFF) + self.write(v & 0xFF) + + def writeChars(self, s): + for ch in str(s): + self.writeChar(ord(ch)) + + def writeDouble(self, v): + data = _struct.pack(">d", v) + self._file.write(data) + + def writeFloat(self, v): + data = _struct.pack(">f", v) + self._file.write(data) + + def writeInt(self, v): + data = _struct.pack(">i", v) + self._file.write(data) + + def writeLong(self, v): + data = _struct.pack(">q", v) + self._file.write(data) + + def writeShort(self, v): + data = _struct.pack(">h", v) + self._file.write(data) + + def writeUTF(self, s): + encoded = str(s).encode("utf-8") + self.writeShort(len(encoded)) + self._file.write(encoded) + + def _read_bytes(self, n): + data = self._file.read(n) + if len(data) < n: + raise EOFException() + return data + + def setLength(self, new_length): + pos = self.getFilePointer() + self._file.truncate(new_length) + if pos > new_length: + self.seek(new_length) + + def close(self): + if not self._closed: + self._closed = True + self._file.close() + + def getChannel(self): + return self._file + + def flush(self): + self._file.flush() + + def __del__(self): + self.close() \ No newline at end of file diff --git a/android_pyapi/java/lang.py b/android_pyapi/java/lang.py new file mode 100644 index 0000000..59d746e --- /dev/null +++ b/android_pyapi/java/lang.py @@ -0,0 +1,1452 @@ +""" +Java lang standard library APIs implemented in Python. +java.lang package - String, Integer, Math, Thread, System, Object, etc. +""" + +from .._core import Stub, AndroidOnlyError +import re as _re +import math as _py_math +import sys as _sys +import os as _os +import time as _time +import threading as _threading +import inspect as _inspect +import math +import random +import time +from datetime import datetime as _datetime + +__all__ = [ + "Object", "Throwable", "Exception", "RuntimeException", "Error", + "String", "Integer", "Long", "Short", "Byte", "Float", "Double", + "Boolean", "Character", "Math", "StrictMath", "System", + "Thread", "ThreadLocal", "StringBuilder", "StringBuffer", + "Class", "Void", "VoidType", + "Stub", "Comparable", "CharSequence", "Iterable", "Appendable", + "Readable", "AutoCloseable", "Cloneable", "Runnable", + "StackTraceElement", "Number", "SuppressWarnings", "Deprecated", + "Override", "FunctionalInterface", +] + + +# ============================================================ +# Interfaces +# ============================================================ + +class Comparable(Stub): + def compareTo(self, other): + raise NotImplementedError + +class Iterable(Stub): + def iterator(self): + raise NotImplementedError + +class AutoCloseable(Stub): + def close(self): + raise NotImplementedError + +class Cloneable(Stub): + def clone(self): + return self.__class__() + +class CharSequence(Stub): + def length(self): + raise NotImplementedError + def charAt(self, index): + raise NotImplementedError + def subSequence(self, start, end): + raise NotImplementedError + def toString(self): + raise NotImplementedError + +class Appendable(Stub): + def append(self, c): + raise NotImplementedError + +class Readable(Stub): + def read(self, cb): + raise NotImplementedError + +class Runnable(Stub): + def run(self): + pass + + +# ============================================================ +# Throwable hierarchy +# ============================================================ + +class Throwable(Exception, Stub): + def __init__(self, message=None, cause=None): + super().__init__(message or "") + self._detail_message = str(message) if message else None + self._cause = cause + self._stack_trace = [] + self._suppressed = [] + self._fill_in_stack() + + def _fill_in_stack(self): + import traceback + stack = traceback.extract_stack() + self._stack_trace = [ + StackTraceElement( + frame.filename, frame.name, frame.filename, frame.lineno, False + ) + for frame in stack[:-1] + ] + + def getMessage(self): + return self._detail_message + + def getLocalizedMessage(self): + return self._detail_message + + def getCause(self): + return self._cause + + def initCause(self, cause): + self._cause = cause + return self + + def toString(self): + msg = self._detail_message or "" + return f"{self.__class__.__name__}: {msg}" if msg else self.__class__.__name__ + + def printStackTrace(self, file=None): + import traceback + if file is None: + file = _sys.stderr + traceback.print_exc(file=file) + + def fillInStackTrace(self): + self._fill_in_stack() + return self + + def getStackTrace(self): + return self._stack_trace + + def setStackTrace(self, stack_trace): + self._stack_trace = stack_trace + + def addSuppressed(self, exception): + self._suppressed.append(exception) + + def getSuppressed(self): + return self._suppressed + + def __str__(self): + return self.toString() + + +class Exception(Throwable): + pass + +class RuntimeException(Exception): + pass + +class Error(Throwable): + pass + + +# ============================================================ +# StackTraceElement +# ============================================================ + +class StackTraceElement(Stub): + def __init__(self, declaring_class, method_name, file_name, line_number, native_method): + super().__init__() + self._class = declaring_class + self._method = method_name + self._file = file_name + self._line = line_number + self._native = native_method + + def getClassName(self): + return self._class + + def getMethodName(self): + return self._method + + def getFileName(self): + return self._file + + def getLineNumber(self): + return self._line + + def isNativeMethod(self): + return self._native + + def toString(self): + return f"{self._class}.{self._method}({self._file}:{self._line})" + + +# ============================================================ +# Object +# ============================================================ + +class Object(Stub): + def __init__(self): + super().__init__() + + def getClass(self): + return self.__class__ + + def hashCode(self): + return hash(self) & 0x7FFFFFFF + + def equals(self, other): + return self is other + + def toString(self): + return f"{self.__class__.__name__}@{hex(id(self))}" + + def notify(self): + pass + + def notifyAll(self): + pass + + def wait(self, timeout=None): + pass + + def clone(self): + return self.__class__() + + def finalize(self): + pass + + +# ============================================================ +# String +# ============================================================ + +class String(CharSequence): + def __init__(self, value=None): + super().__init__() + if value is None: + self._str = "" + elif isinstance(value, bytes): + self._str = value.decode("utf-8", errors="replace") + else: + self._str = str(value) + + def charAt(self, index): + return self._str[index] + + def codePointAt(self, index): + return ord(self._str[index]) + + def codePointBefore(self, index): + return ord(self._str[index - 1]) if index > 0 else -1 + + def codePointCount(self, beginIndex, endIndex): + return endIndex - beginIndex + + def compareTo(self, other): + if isinstance(other, String): + return (self._str > other._str) - (self._str < other._str) + return -1 + + def compareToIgnoreCase(self, other): + if isinstance(other, String): + return (self._str.lower() > other._str.lower()) - (self._str.lower() < other._str.lower()) + return -1 + + def concat(self, s): + return String(self._str + str(s)) + + def contains(self, s): + return str(s) in self._str + + def contentEquals(self, cs): + return self._str == str(cs) + + def endsWith(self, suffix): + return self._str.endswith(suffix) + + def equals(self, other): + if isinstance(other, String): + return self._str == other._str + return False + + def equalsIgnoreCase(self, other): + if isinstance(other, String): + return self._str.lower() == other._str.lower() + return False + + def getBytes(self, charset="UTF-8"): + return self._str.encode(charset, errors="replace") + + def hashCode(self): + h = 0 + for c in self._str: + h = (31 * h + ord(c)) & 0xFFFFFFFF + return h if h < 0x80000000 else h - 0x100000000 + return h & 0x7FFFFFFF + + def indexOf(self, ch, fromIndex=0): + if isinstance(ch, int): + ch = chr(ch) + return self._str.index(str(ch), fromIndex) if str(ch) in self._str[fromIndex:] else -1 + + def intern(self): + return self + + def isEmpty(self): + return len(self._str) == 0 + + def lastIndexOf(self, ch, fromIndex=None): + if isinstance(ch, int): + ch = chr(ch) + if fromIndex is None: + fromIndex = len(self._str) - 1 + search_in = self._str[:fromIndex + 1] + return search_in.rfind(str(ch)) if str(ch) in search_in else -1 + + def length(self): + return len(self._str) + + def matches(self, regex): + return bool(_re.fullmatch(regex, self._str)) + + def offsetByCodePoints(self, index, codePointOffset): + return index + codePointOffset + + def regionMatches(self, ignoreCase, toffset, other, ooffset, length): + if ignoreCase: + return self._str[toffset:toffset+length].lower() == other._str[ooffset:ooffset+length].lower() + return self._str[toffset:toffset+length] == other._str[ooffset:ooffset+length] + + def replace(self, oldChar, newChar): + if isinstance(oldChar, int): + oldChar = chr(oldChar) + if isinstance(newChar, int): + newChar = chr(newChar) + return String(self._str.replace(oldChar, newChar)) + + def replaceAll(self, regex, replacement): + return String(_re.sub(regex, replacement, self._str)) + + def replaceFirst(self, regex, replacement): + return String(_re.sub(regex, replacement, self._str, count=1)) + + def split(self, regex, limit=0): + if limit <= 0: + parts = _re.split(regex, self._str) + if limit == 0: + while parts and parts[-1] == "": + parts.pop() + else: + parts = _re.split(regex, self._str, maxsplit=limit - 1) + while len(parts) < limit: + parts.append("") + return [String(p) for p in parts] + + def startsWith(self, prefix): + return self._str.startswith(prefix) + + def substring(self, beginIndex, endIndex=None): + if endIndex is None: + return String(self._str[beginIndex:]) + return String(self._str[beginIndex:endIndex]) + + def toCharArray(self): + return list(self._str) + + def toLowerCase(self): + return String(self._str.lower()) + + def toUpperCase(self): + return String(self._str.upper()) + + def trim(self): + return String(self._str.strip()) + + @staticmethod + def valueOf(obj): + return String(str(obj)) + + @staticmethod + def copyValueOf(data, offset=0, count=None): + if count is None: + return String("".join(data)) + return String("".join(data[offset:offset+count])) + + @staticmethod + def format(fmt, *args): + return String(str(fmt) % args if args else str(fmt)) + + @staticmethod + def join(delimiter, *elements): + if hasattr(elements[0], '__iter__') and not isinstance(elements[0], (str, bytes)): + elements = elements[0] + return String(str(delimiter).join(str(s) for s in elements)) + + def toString(self): + return self._str + + def __str__(self): + return self._str + + def __repr__(self): + return f"String({self._str!r})" + + def __len__(self): + return len(self._str) + + def __getitem__(self, idx): + return self._str[idx] + + def __add__(self, other): + return String(self._str + str(other)) + + def __eq__(self, other): + if isinstance(other, String): + return self._str == other._str + return NotImplemented + + +# ============================================================ +# Number +# ============================================================ + +class Number(Stub): + def intValue(self): + raise NotImplementedError + def longValue(self): + raise NotImplementedError + def floatValue(self): + raise NotImplementedError + def doubleValue(self): + raise NotImplementedError + def byteValue(self): + return self.intValue() & 0xFF + def shortValue(self): + return self.intValue() & 0xFFFF + + +# ============================================================ +# Integer +# ============================================================ + +class Integer(Number, Comparable): + MAX_VALUE = 2147483647 + MIN_VALUE = -2147483648 + SIZE = 32 + BYTES = 4 + TYPE = int + + def __init__(self, value=0): + super().__init__() + if isinstance(value, str): + self._value = Integer.parseInt(value) + else: + self._value = int(value) + + @staticmethod + def parseInt(s, radix=10): + if isinstance(s, String): + s = str(s) + if radix == 10: + return int(str(s)) + return int(str(s), radix) + + @staticmethod + def toString(i, radix=10): + if radix == 10: + return String(str(i)) + if radix == 2: + return String(bin(i)[2:]) + if radix == 8: + return String(oct(i)[2:]) + if radix == 16: + return String(hex(i)[2:]) + return String(str(i)) + + @staticmethod + def toBinaryString(i): + if i >= 0: + return String(bin(i)[2:]) + return String(bin(i & 0xFFFFFFFF)[2:]) + + @staticmethod + def toHexString(i): + if i >= 0: + return String(hex(i)[2:]) + return String(hex(i & 0xFFFFFFFF)[2:]) + + @staticmethod + def toOctalString(i): + if i >= 0: + return String(oct(i)[2:]) + return String(oct(i & 0xFFFFFFFF)[2:]) + + @staticmethod + def valueOf(i, radix=10): + if isinstance(i, str): + return Integer(Integer.parseInt(i, radix)) + return Integer(i) + + def intValue(self): + return self._value + + def compareTo(self, other): + if isinstance(other, Integer): + return (self._value > other._value) - (self._value < other._value) + return -1 + + def equals(self, other): + if isinstance(other, Integer): + return self._value == other._value + return False + + def byteValue(self): + return self._value & 0xFF + + def shortValue(self): + return self._value & 0xFFFF + + def longValue(self): + return self._value + + def floatValue(self): + return float(self._value) + + def doubleValue(self): + return float(self._value) + + @staticmethod + def bitCount(i): + return bin(i & 0xFFFFFFFF).count("1") + + @staticmethod + def highestOneBit(i): + if i == 0: + return 0 + return 1 << (i.bit_length() - 1) + + @staticmethod + def lowestOneBit(i): + return i & -i + + @staticmethod + def numberOfLeadingZeros(i): + if i == 0: + return 32 + return 32 - i.bit_length() + + @staticmethod + def numberOfTrailingZeros(i): + if i == 0: + return 32 + return (i & -i).bit_length() - 1 + + @staticmethod + def reverse(i): + v = i & 0xFFFFFFFF + result = 0 + for _ in range(32): + result = (result << 1) | (v & 1) + v >>= 1 + return result + + @staticmethod + def reverseBytes(i): + v = i & 0xFFFFFFFF + return ((v >> 24) & 0xFF) | ((v >> 8) & 0xFF00) | ((v << 8) & 0xFF0000) | ((v << 24) & 0xFF000000) + + @staticmethod + def rotateLeft(i, distance): + v = i & 0xFFFFFFFF + d = distance & 31 + return ((v << d) | (v >> (32 - d))) & 0xFFFFFFFF + + @staticmethod + def rotateRight(i, distance): + v = i & 0xFFFFFFFF + d = distance & 31 + return ((v >> d) | (v << (32 - d))) & 0xFFFFFFFF + + @staticmethod + def signum(i): + if i > 0: + return 1 + if i < 0: + return -1 + return 0 + + @staticmethod + def decode(nm): + if nm.startswith("0x") or nm.startswith("0X"): + return Integer(int(nm, 16)) + if nm.startswith("0") and len(nm) > 1: + return Integer(int(nm, 8)) + if nm.startswith("#"): + return Integer(int(nm[1:], 16)) + return Integer(int(nm)) + + @staticmethod + def getInteger(nm, val=None): + env_val = _os.environ.get(nm) + if env_val is not None: + return Integer(int(env_val)) + return Integer(val) if val is not None else None + + def toString(self): + return String(str(self._value)) + + def __str__(self): + return str(self._value) + + def __int__(self): + return self._value + + +# ============================================================ +# Long +# ============================================================ + +class Long(Number, Comparable): + MAX_VALUE = 9223372036854775807 + MIN_VALUE = -9223372036854775808 + SIZE = 64 + BYTES = 8 + TYPE = int + + def __init__(self, value=0): + super().__init__() + if isinstance(value, str): + self._value = Long.parseLong(value) + else: + self._value = int(value) + + @staticmethod + def parseLong(s, radix=10): + if isinstance(s, String): + s = str(s) + if radix == 10: + return int(str(s)) + return int(str(s), radix) + + @staticmethod + def toString(i, radix=10): + if radix == 10: + return String(str(i)) + return String(str(i)) + + @staticmethod + def toBinaryString(i): + if i >= 0: + return String(bin(i)[2:]) + return String(bin(i & 0xFFFFFFFFFFFFFFFF)[2:]) + + @staticmethod + def toHexString(i): + if i >= 0: + return String(hex(i)[2:]) + return String(hex(i & 0xFFFFFFFFFFFFFFFF)[2:]) + + @staticmethod + def toOctalString(i): + if i >= 0: + return String(oct(i)[2:]) + return String(oct(i & 0xFFFFFFFFFFFFFFFF)[2:]) + + @staticmethod + def valueOf(i, radix=10): + if isinstance(i, str): + return Long(Long.parseLong(i, radix)) + return Long(i) + + def longValue(self): + return self._value + + def intValue(self): + return int(self._value) + + def floatValue(self): + return float(self._value) + + def doubleValue(self): + return float(self._value) + + def compareTo(self, other): + if isinstance(other, Long): + return (self._value > other._value) - (self._value < other._value) + return -1 + + def equals(self, other): + if isinstance(other, Long): + return self._value == other._value + return False + + def toString(self): + return String(str(self._value)) + + def __str__(self): + return str(self._value) + + +# ============================================================ +# Short, Byte +# ============================================================ + +class Short(Number, Comparable): + MAX_VALUE = 32767 + MIN_VALUE = -32768 + SIZE = 16 + BYTES = 2 + TYPE = int + + def __init__(self, value=0): + super().__init__() + if isinstance(value, str): + self._value = Short.parseShort(value) + else: + self._value = int(value) & 0xFFFF + + @staticmethod + def parseShort(s, radix=10): + return int(str(s), radix) + + @staticmethod + def valueOf(i, radix=10): + if isinstance(i, str): + return Short(Short.parseShort(i, radix)) + return Short(i) + + def shortValue(self): + return self._value + + def intValue(self): + return self._value + + def longValue(self): + return self._value + + def floatValue(self): + return float(self._value) + + def doubleValue(self): + return float(self._value) + + def compareTo(self, other): + if isinstance(other, Short): + return (self._value > other._value) - (self._value < other._value) + return -1 + + def equals(self, other): + if isinstance(other, Short): + return self._value == other._value + return False + + def toString(self): + return String(str(self._value)) + + +class Byte(Number, Comparable): + MAX_VALUE = 127 + MIN_VALUE = -128 + SIZE = 8 + BYTES = 1 + TYPE = int + + def __init__(self, value=0): + super().__init__() + if isinstance(value, str): + self._value = Byte.parseByte(value) + else: + self._value = int(value) & 0xFF + if self._value > 127: + self._value -= 256 + + @staticmethod + def parseByte(s, radix=10): + return int(str(s), radix) + + @staticmethod + def valueOf(i, radix=10): + if isinstance(i, str): + return Byte(Byte.parseByte(i, radix)) + return Byte(i) + + def byteValue(self): + return self._value + + def intValue(self): + return self._value + + def longValue(self): + return self._value + + def compareTo(self, other): + if isinstance(other, Byte): + return (self._value > other._value) - (self._value < other._value) + return -1 + + def equals(self, other): + if isinstance(other, Byte): + return self._value == other._value + return False + + def toString(self): + return String(str(self._value)) + + +# ============================================================ +# Float +# ============================================================ + +class Float(Number, Comparable): + MAX_VALUE = 3.4028235e38 + MIN_VALUE = 1.4e-45 + MIN_NORMAL = 1.17549435e-38 + MAX_EXPONENT = 127 + MIN_EXPONENT = -126 + SIZE = 32 + BYTES = 4 + TYPE = float + NaN = float("nan") + POSITIVE_INFINITY = float("inf") + NEGATIVE_INFINITY = float("-inf") + + def __init__(self, value=0.0): + super().__init__() + if isinstance(value, str): + self._value = Float.parseFloat(value) + else: + self._value = float(value) + + @staticmethod + def parseFloat(s): + if isinstance(s, String): + s = str(s) + s = str(s).strip() + if s == "NaN": + return float("nan") + if s == "Infinity" or s == "Inf": + return float("inf") + if s == "-Infinity" or s == "-Inf": + return float("-inf") + return float(s) + + @staticmethod + def toString(f): + return String(str(f)) + + @staticmethod + def valueOf(f): + if isinstance(f, str): + return Float(Float.parseFloat(f)) + return Float(f) + + def floatValue(self): + return self._value + + def intValue(self): + return int(self._value) + + def longValue(self): + return int(self._value) + + def doubleValue(self): + return float(self._value) + + def compareTo(self, other): + if isinstance(other, Float): + a, b = self._value, other._value + if _py_math.isnan(a) and _py_math.isnan(b): + return 0 + if _py_math.isnan(a): + return 1 + if _py_math.isnan(b): + return -1 + return (a > b) - (a < b) + return -1 + + def isNaN(self): + return _py_math.isnan(self._value) + + def isInfinite(self): + return _py_math.isinf(self._value) + + def equals(self, other): + if isinstance(other, Float): + if _py_math.isnan(self._value) and _py_math.isnan(other._value): + return True + return self._value == other._value + return False + + def hashCode(self): + return _struct.pack(">f", self._value) + + def toString(self): + return String(str(self._value)) + + def __str__(self): + return str(self._value) + +import struct as _struct + + +# ============================================================ +# Double +# ============================================================ + +class Double(Number, Comparable): + MAX_VALUE = 1.7976931348623157e308 + MIN_VALUE = 4.9e-324 + MIN_NORMAL = 2.2250738585072014e-308 + MAX_EXPONENT = 1023 + MIN_EXPONENT = -1022 + SIZE = 64 + BYTES = 8 + TYPE = float + NaN = float("nan") + POSITIVE_INFINITY = float("inf") + NEGATIVE_INFINITY = float("-inf") + + def __init__(self, value=0.0): + super().__init__() + if isinstance(value, str): + self._value = Double.parseDouble(value) + else: + self._value = float(value) + + @staticmethod + def parseDouble(s): + if isinstance(s, String): + s = str(s) + s = str(s).strip() + if s == "NaN": + return float("nan") + if s == "Infinity" or s == "Inf": + return float("inf") + if s == "-Infinity" or s == "-Inf": + return float("-inf") + return float(s) + + @staticmethod + def toString(d): + return String(str(d)) + + @staticmethod + def valueOf(d): + if isinstance(d, str): + return Double(Double.parseDouble(d)) + return Double(d) + + def doubleValue(self): + return self._value + + def intValue(self): + return int(self._value) + + def longValue(self): + return int(self._value) + + def floatValue(self): + return float(self._value) + + def compareTo(self, other): + if isinstance(other, Double): + a, b = self._value, other._value + if _py_math.isnan(a) and _py_math.isnan(b): + return 0 + if _py_math.isnan(a): + return 1 + if _py_math.isnan(b): + return -1 + return (a > b) - (a < b) + return -1 + + def isNaN(self): + return _py_math.isnan(self._value) + + def isInfinite(self): + return _py_math.isinf(self._value) + + def equals(self, other): + if isinstance(other, Double): + if _py_math.isnan(self._value) and _py_math.isnan(other._value): + return True + return self._value == other._value + return False + + def toString(self): + return String(str(self._value)) + + def __str__(self): + return str(self._value) + + +# ============================================================ +# Boolean +# ============================================================ + +class Boolean(Stub): + TRUE = "true" + FALSE = "false" + TYPE = bool + + def __init__(self, value=False): + super().__init__() + if isinstance(value, str): + self._value = Boolean.parseBoolean(value) + elif isinstance(value, bool): + self._value = value + else: + self._value = bool(value) + + @staticmethod + def parseBoolean(s): + return str(s).lower() == "true" + + @staticmethod + def toString(b): + return String(str(b).lower()) + + @staticmethod + def valueOf(b): + if isinstance(b, str): + return Boolean(Boolean.parseBoolean(b)) + return Boolean(b) + + def booleanValue(self): + return self._value + + @staticmethod + def logicalAnd(a, b): + return a and b + + @staticmethod + def logicalOr(a, b): + return a or b + + @staticmethod + def logicalXor(a, b): + return a ^ b + + def compareTo(self, other): + if isinstance(other, Boolean): + return (self._value > other._value) - (self._value < other._value) + return -1 + + def equals(self, other): + if isinstance(other, Boolean): + return self._value == other._value + return False + + def toString(self): + return String("true" if self._value else "false") + + def __str__(self): + return "true" if self._value else "false" + + def __bool__(self): + return self._value + + +# ============================================================ +# Character +# ============================================================ + +class Character(Stub): + MAX_VALUE = 65535 + MIN_VALUE = 0 + SIZE = 16 + BYTES = 2 + TYPE = str + + def __init__(self, value=""): + super().__init__() + if isinstance(value, int): + self._value = chr(value) + else: + self._value = str(value)[0] if value else "" + + def charValue(self): + return self._value + + @staticmethod + def isDigit(ch): + return str(ch).isdigit() + + @staticmethod + def isLetter(ch): + return str(ch).isalpha() + + @staticmethod + def isLetterOrDigit(ch): + return str(ch).isalnum() + + @staticmethod + def isLowerCase(ch): + return str(ch).islower() + + @staticmethod + def isUpperCase(ch): + return str(ch).isupper() + + @staticmethod + def isWhitespace(ch): + return str(ch).isspace() + + @staticmethod + def isSpaceChar(ch): + return str(ch) in " \t\n\r\f\u000B" + + @staticmethod + def isISOControl(ch): + c = ord(ch) if isinstance(ch, str) else ch + return c < 0x20 or c == 0x7F + + @staticmethod + def isJavaIdentifierPart(ch): + c = str(ch) + return c.isalnum() or c == "_" or c == "$" + + @staticmethod + def isJavaIdentifierStart(ch): + c = str(ch) + return c.isalpha() or c == "_" or c == "$" + + @staticmethod + def isJavaLetter(ch): + return Character.isJavaIdentifierStart(ch) + + @staticmethod + def isJavaLetterOrDigit(ch): + return Character.isJavaIdentifierPart(ch) + + @staticmethod + def toLowerCase(ch): + return str(ch).lower() + + @staticmethod + def toUpperCase(ch): + return str(ch).upper() + + @staticmethod + def toTitleCase(ch): + return str(ch).upper() + + @staticmethod + def getNumericValue(ch): + s = str(ch) + if s.isdigit(): + return int(s) + if "a" <= s <= "z": + return ord(s) - ord("a") + 10 + if "A" <= s <= "Z": + return ord(s) - ord("A") + 10 + return -1 + + @staticmethod + def digit(ch, radix): + val = Character.getNumericValue(ch) + return val if val < radix else -1 + + @staticmethod + def forDigit(digit, radix): + if digit >= radix: + return "\0" + return str(digit) if digit < 10 else chr(ord("a") + digit - 10) + + @staticmethod + def getType(ch): + return 0 + + @staticmethod + def getDirectionality(ch): + return 0 + + @staticmethod + def getName(ch): + return f"CHARACTER {ord(ch)}" + + @staticmethod + def charCount(codePoint): + return 1 if codePoint < 0x10000 else 2 + + @staticmethod + def isBmpCodePoint(cp): + return cp <= 0xFFFF + + @staticmethod + def isHighSurrogate(ch): + c = ord(ch) if isinstance(ch, str) else ch + return 0xD800 <= c <= 0xDBFF + + @staticmethod + def isLowSurrogate(ch): + c = ord(ch) if isinstance(ch, str) else ch + return 0xDC00 <= c <= 0xDFFF + + @staticmethod + def isSupplementaryCodePoint(cp): + return 0x10000 + + +class Math: + """java.lang.Math — 数学运算""" + E = 2.718281828459045 + PI = 3.141592653589793 + + @staticmethod + def abs(a): return abs(a) + @staticmethod + def ceil(a): return int(math.ceil(a)) + @staticmethod + def floor(a): return int(math.floor(a)) + @staticmethod + def round(a): return round(a) + @staticmethod + def min(a, b): return a if a < b else b + @staticmethod + def max(a, b): return a if a > b else b + @staticmethod + def sqrt(a): return math.sqrt(a) + @staticmethod + def cbrt(a): return math.cbrt(a) + @staticmethod + def pow(a, b): return math.pow(a, b) + @staticmethod + def exp(a): return math.exp(a) + @staticmethod + def log(a): return math.log(a) + @staticmethod + def log10(a): return math.log10(a) + @staticmethod + def sin(a): return math.sin(a) + @staticmethod + def cos(a): return math.cos(a) + @staticmethod + def tan(a): return math.tan(a) + @staticmethod + def asin(a): return math.asin(a) + @staticmethod + def acos(a): return math.acos(a) + @staticmethod + def atan(a): return math.atan(a) + @staticmethod + def atan2(y, x): return math.atan2(y, x) + @staticmethod + def toDegrees(r): return math.degrees(r) + @staticmethod + def toRadians(d): return math.radians(d) + @staticmethod + def hypot(x, y): return math.hypot(x, y) + @staticmethod + def sinh(x): return math.sinh(x) + @staticmethod + def cosh(x): return math.cosh(x) + @staticmethod + def tanh(x): return math.tanh(x) + @staticmethod + def signum(x): return 0.0 if x == 0 else (1.0 if x > 0 else -1.0) + @staticmethod + def random(): return random.random() + @staticmethod + def addExact(x, y): + result = x + y + if (x > 0 and y > 0 and result < 0) or (x < 0 and y < 0 and result > 0): + raise ArithmeticError("integer overflow") + return result + @staticmethod + def copySign(m, s): return abs(m) * (1 if s >= 0 else -1) + @staticmethod + def floorDiv(x, y): return x // y if (x ^ y) >= 0 else -(abs(x) // abs(y)) + @staticmethod + def floorMod(x, y): return x - Math.floorDiv(x, y) * y + + +class StrictMath(Math): + """java.lang.StrictMath""" + pass + + +class System: + """java.lang.System""" + @staticmethod + def currentTimeMillis(): return int(time.time() * 1000) + @staticmethod + def nanoTime(): return time.perf_counter_ns() + @staticmethod + def arraycopy(src, srcPos, dest, destPos, length): + if isinstance(src, (list, bytearray)): + dest[destPos:destPos+length] = src[srcPos:srcPos+length] + else: + raise TypeError("Unsupported type for arraycopy") + @staticmethod + def identityHashCode(obj): return id(obj) & 0x7FFFFFFF + @staticmethod + def gc(): pass + @staticmethod + def exit(status): raise SystemExit(status) + @staticmethod + def getProperty(key, default=None): + import os + return os.environ.get(key, default) + @staticmethod + def setProperty(key, value): + import os + os.environ[key] = value + return value + @staticmethod + def lineSeparator(): return _os.linesep + in_ = _sys.stdin + out = _sys.stdout + err = _sys.stderr + + +class Thread(Runnable): + """java.lang.Thread""" + MIN_PRIORITY = 1 + NORM_PRIORITY = 5 + MAX_PRIORITY = 10 + + def __init__(self, target=None, name=None): + super().__init__() + self._target = target + self._name = name or f"Thread-{id(self)}" + self._alive = False + self._daemon = False + self._interrupted = False + + def start(self): + self._alive = True + if self._target: + _threading.Thread(target=self.run, daemon=self._daemon).start() + + def run(self): + if self._target: + self._target() + + def interrupt(self): + self._interrupted = True + + def isInterrupted(self): + return self._interrupted + + def isAlive(self): + return self._alive + + def getName(self): return self._name + def setName(self, n): self._name = n + def setDaemon(self, d): self._daemon = d + def isDaemon(self): return self._daemon + + @staticmethod + def currentThread(): + return Thread(name="main") + @staticmethod + def sleep(millis): + import time + time.sleep(millis / 1000.0) + @staticmethod + def yield_(): + import time + time.sleep(0) + + +class ThreadLocal(Stub): + """java.lang.ThreadLocal""" + def __init__(self): + super().__init__() + self._data = {} + + def get(self): + tid = _threading.get_ident() + return self._data.get(tid, None) + def set(self, value): + self._data[_threading.get_ident()] = value + def remove(self): + self._data.pop(_threading.get_ident(), None) + + +class StringBuilder(Stub): + """java.lang.StringBuilder — 可变字符序列""" + def __init__(self, s=""): + super().__init__() + self._buf = list(str(s)) + + def append(self, obj): + if isinstance(obj, bool): + self._buf.append("true" if obj else "false") + else: + self._buf.append(str(obj)) + return self + + def insert(self, offset, obj): + self._buf[offset:offset] = list(str(obj)) + return self + + def delete(self, start, end): + del self._buf[start:end] + return self + + def deleteCharAt(self, index): + del self._buf[index] + return self + + def replace(self, start, end, s): + self._buf[start:end] = list(s) + return self + + def reverse(self): + self._buf.reverse() + return self + + def toString(self): return "".join(self._buf) + def length(self): return len(self._buf) + def capacity(self): return len(self._buf) + 16 + def charAt(self, i): return self._buf[i] + def setCharAt(self, i, c): self._buf[i] = c + def substring(self, s, e=None): + return "".join(self._buf[s:e]) + def indexOf(self, s, f=0): + return self.toString().find(s, f) + def lastIndexOf(self, s, f=None): + return self.toString().rfind(s) + + +class StringBuffer(StringBuilder): + """java.lang.StringBuffer — 线程安全的可变字符序列""" + pass + + +class Class(Stub): + """java.lang.Class""" + def __init__(self, cls): + super().__init__() + self._cls = cls + def getName(self): return self._cls.__name__ + def getSimpleName(self): return self._cls.__name__ + def getCanonicalName(self): return self._cls.__name__ + def isInterface(self): return isinstance(self._cls, type) + def isArray(self): return self._cls is list + def isPrimitive(self): return self._cls in (int, float, bool, str, bytes) + def getSuperclass(self): return Class(self._cls.__base__) if self._cls.__base__ else None + def cast(self, obj): return obj + + +class Void(Stub): + """java.lang.Void""" + TYPE = None + + +class VoidType(Stub): + """java.lang.Void (inner type)""" + pass + + +# 注解类 +class SuppressWarnings(Stub): + pass +class Deprecated(Stub): + pass +class Override(Stub): + pass +class FunctionalInterface(Stub): + pass \ No newline at end of file diff --git a/android_pyapi/java/math.py b/android_pyapi/java/math.py new file mode 100644 index 0000000..bdbf2c7 --- /dev/null +++ b/android_pyapi/java/math.py @@ -0,0 +1,683 @@ +""" +Java math standard library APIs implemented in Python. +java.math package - BigInteger, BigDecimal, MathContext, RoundingMode. +""" + +from .._core import Stub +from .lang import Comparable +from decimal import ( + Decimal as _Decimal, + ROUND_DOWN as _DEC_ROUND_DOWN, + ROUND_HALF_UP as _DEC_ROUND_HALF_UP, + ROUND_HALF_EVEN as _DEC_ROUND_HALF_EVEN, + ROUND_UP as _DEC_ROUND_UP, + ROUND_CEILING as _DEC_ROUND_CEILING, + ROUND_FLOOR as _DEC_ROUND_FLOOR, + ROUND_HALF_DOWN as _DEC_ROUND_HALF_DOWN, + localcontext as _local_context, + getcontext as _get_context, +) +import random as _random +import math as _py_math +import sys as _sys + +__all__ = [ + "BigInteger", "BigDecimal", "MathContext", "RoundingMode", +] + + +# ============================================================ +# RoundingMode +# ============================================================ + +class RoundingMode(Stub): + UP = 0 + DOWN = 1 + CEILING = 2 + FLOOR = 3 + HALF_UP = 4 + HALF_DOWN = 5 + HALF_EVEN = 6 + UNNECESSARY = 7 + + _names = { + 0: "UP", 1: "DOWN", 2: "CEILING", 3: "FLOOR", + 4: "HALF_UP", 5: "HALF_DOWN", 6: "HALF_EVEN", 7: "UNNECESSARY", + } + + _py_map = { + 0: _DEC_ROUND_UP, + 1: _DEC_ROUND_DOWN, + 2: _DEC_ROUND_CEILING, + 3: _DEC_ROUND_FLOOR, + 4: _DEC_ROUND_HALF_UP, + 5: _DEC_ROUND_HALF_DOWN, + 6: _DEC_ROUND_HALF_EVEN, + 7: _DEC_ROUND_HALF_EVEN, # closest approximation + } + + def __init__(self, value): + super().__init__() + self._value = value + + def toString(self): + return self._names.get(self._value, "UNKNOWN") + + +# ============================================================ +# MathContext +# ============================================================ + +class MathContext(Stub): + UNLIMITED = None + DECIMAL32 = None + DECIMAL64 = None + DECIMAL128 = None + + def __init__(self, *args): + super().__init__() + if len(args) == 1: + if isinstance(args[0], str): + s = args[0].strip().lower() + if s == "precision=7 roundingmode=half_even": + self._precision = 7 + self._rounding_mode = RoundingMode.HALF_EVEN + elif s == "precision=16 roundingmode=half_even": + self._precision = 16 + self._rounding_mode = RoundingMode.HALF_EVEN + elif s == "precision=34 roundingmode=half_even": + self._precision = 34 + self._rounding_mode = RoundingMode.HALF_EVEN + else: + self._precision = 0 + self._rounding_mode = RoundingMode.HALF_UP + else: + self._precision = int(args[0]) + self._rounding_mode = RoundingMode.HALF_UP + elif len(args) == 2: + self._precision = int(args[0]) + self._rounding_mode = args[1] + + def getPrecision(self): + return self._precision + + def getRoundingMode(self): + return self._rounding_mode + + def equals(self, other): + if isinstance(other, MathContext): + return (self._precision == other._precision and + self._rounding_mode == other._rounding_mode) + return False + + def hashCode(self): + return hash((self._precision, self._rounding_mode)) & 0x7FFFFFFF + + def toString(self): + return f"precision={self._precision} roundingMode={self._rounding_mode.toString()}" + + +MathContext.UNLIMITED = MathContext(0) +MathContext.DECIMAL32 = MathContext(7, RoundingMode.HALF_EVEN) +MathContext.DECIMAL64 = MathContext(16, RoundingMode.HALF_EVEN) +MathContext.DECIMAL128 = MathContext(34, RoundingMode.HALF_EVEN) + + +# ============================================================ +# BigInteger +# ============================================================ + +class BigInteger(Comparable): + ZERO = None + ONE = None + TEN = None + + def __init__(self, *args): + super().__init__() + if len(args) == 0: + self._value = 0 + elif len(args) == 1: + if isinstance(args[0], str): + self._value = int(str(args[0])) + elif isinstance(args[0], bytes): + self._value = int.from_bytes(args[0], byteorder="big", signed=True) + elif isinstance(args[0], bytearray): + self._value = int.from_bytes(bytes(args[0]), byteorder="big", signed=True) + elif isinstance(args[0], int): + self._value = args[0] + elif isinstance(args[0], BigInteger): + self._value = args[0]._value + else: + self._value = int(args[0]) + elif len(args) == 2: + if isinstance(args[1], _random.Random): + bit_length, rng = args + self._value = rng.getrandbits(bit_length) + elif isinstance(args[1], int): + val_str, radix = args + self._value = int(str(val_str), radix) + else: + self._value = int.from_bytes(bytes(args[0]), byteorder="big", signed=True) + else: + self._value = 0 + + @staticmethod + def valueOf(val): + if isinstance(val, int): + if val == 0: + return BigInteger.ZERO + if val == 1: + return BigInteger.ONE + if val == 10: + return BigInteger.TEN + return BigInteger(val) + + def add(self, other): + if isinstance(other, BigInteger): + return BigInteger(self._value + other._value) + return BigInteger(self._value + int(other)) + + def subtract(self, other): + if isinstance(other, BigInteger): + return BigInteger(self._value - other._value) + return BigInteger(self._value - int(other)) + + def multiply(self, other): + if isinstance(other, BigInteger): + return BigInteger(self._value * other._value) + return BigInteger(self._value * int(other)) + + def divide(self, other): + if isinstance(other, BigInteger): + return BigInteger(self._value // other._value) + return BigInteger(self._value // int(other)) + + def pow(self, exponent): + return BigInteger(self._value ** exponent) + + def gcd(self, other): + return BigInteger(_py_math.gcd(self._value, other._value if isinstance(other, BigInteger) else int(other))) + + def abs(self): + return BigInteger(abs(self._value)) + + def negate(self): + return BigInteger(-self._value) + + def signum(self): + if self._value > 0: + return 1 + if self._value < 0: + return -1 + return 0 + + def mod(self, m): + if isinstance(m, BigInteger): + return BigInteger(self._value % m._value) + return BigInteger(self._value % int(m)) + + def remainder(self, other): + if isinstance(other, BigInteger): + return BigInteger(self._value % other._value) + return BigInteger(self._value % int(other)) + + def and_(self, other): + return BigInteger(self._value & (other._value if isinstance(other, BigInteger) else int(other))) + + def or_(self, other): + return BigInteger(self._value | (other._value if isinstance(other, BigInteger) else int(other))) + + def xor(self, other): + return BigInteger(self._value ^ (other._value if isinstance(other, BigInteger) else int(other))) + + def not_(self): + return BigInteger(~self._value) + + def andNot(self, other): + return BigInteger(self._value & ~(other._value if isinstance(other, BigInteger) else int(other))) + + def shiftLeft(self, n): + return BigInteger(self._value << n) + + def shiftRight(self, n): + return BigInteger(self._value >> n) + + def getLowestSetBit(self): + if self._value == 0: + return -1 + return (self._value & -self._value).bit_length() - 1 + + def bitLength(self): + if self._value == 0: + return 0 + return self._value.bit_length() + + def bitCount(self): + return bin(self._value & ((1 << self.bitLength()) - 1) if self._value >= 0 else self._value).count("1") + + def isProbablePrime(self, certainty): + if abs(self._value) < 2: + return False + if abs(self._value) == 2 or abs(self._value) == 3: + return True + if self._value % 2 == 0: + return False + d = self._value - 1 + s = 0 + while d % 2 == 0: + d //= 2 + s += 1 + for _ in range(min(certainty, 20)): + a = _random.randrange(2, min(self._value - 1, 1000000)) + x = pow(a, d, self._value) + if x == 1 or x == self._value - 1: + continue + for _ in range(s - 1): + x = pow(x, 2, self._value) + if x == self._value - 1: + break + else: + return False + return True + + def nextProbablePrime(self): + v = self._value + 1 + if v < 3: + return BigInteger(2) if v <= 2 else BigInteger(3) + while True: + if v % 2 == 0: + v += 1 + for i in range(3, int(_py_math.sqrt(v)) + 1, 2): + if v % i == 0: + break + else: + return BigInteger(v) + v += 2 + + def compareTo(self, other): + if isinstance(other, BigInteger): + return (self._value > other._value) - (self._value < other._value) + return -1 + + def equals(self, other): + if isinstance(other, BigInteger): + return self._value == other._value + return False + + def toByteArray(self): + if self._value == 0: + return b"\x00" + byte_length = (self._value.bit_length() + 8) // 8 + return self._value.to_bytes(byte_length, byteorder="big", signed=True) + + def toString(self, radix=10): + if radix == 10: + return str(self._value) + if radix == 16: + return hex(self._value)[2:] + if radix == 8: + return oct(self._value)[2:] + if radix == 2: + return bin(self._value)[2:] + return str(self._value) + + def intValue(self): + return self._value & 0xFFFFFFFF + + def longValue(self): + return self._value + + def floatValue(self): + return float(self._value) + + def doubleValue(self): + return float(self._value) + + def min(self, other): + return BigInteger(min(self._value, other._value if isinstance(other, BigInteger) else int(other))) + + def max(self, other): + return BigInteger(max(self._value, other._value if isinstance(other, BigInteger) else int(other))) + + def modPow(self, exponent, m): + if isinstance(exponent, BigInteger): + exp_val = exponent._value + else: + exp_val = int(exponent) + mod_val = m._value if isinstance(m, BigInteger) else int(m) + return BigInteger(pow(self._value, exp_val, mod_val)) + + def modInverse(self, m): + mod_val = m._value if isinstance(m, BigInteger) else int(m) + return BigInteger(pow(self._value, -1, mod_val)) + + def divideAndRemainder(self, other): + if isinstance(other, BigInteger): + divisor = other._value + else: + divisor = int(other) + q = self._value // divisor + r = self._value % divisor + return [BigInteger(q), BigInteger(r)] + + def __int__(self): + return self._value + + def __str__(self): + return str(self._value) + + def __neg__(self): + return self.negate() + + def __abs__(self): + return self.abs() + + def __add__(self, other): + return self.add(other) + + def __sub__(self, other): + return self.subtract(other) + + def __mul__(self, other): + return self.multiply(other) + + def __truediv__(self, other): + return self.divide(other) + + def __floordiv__(self, other): + return self.divide(other) + + def __mod__(self, other): + return self.mod(other) + + def __pow__(self, other): + return self.pow(other) + + def __and__(self, other): + return self.and_(other) + + def __or__(self, other): + return self.or_(other) + + def __xor__(self, other): + return self.xor(other) + + def __invert__(self): + return self.not_() + + def __lshift__(self, n): + return self.shiftLeft(n) + + def __rshift__(self, n): + return self.shiftRight(n) + + +BigInteger.ZERO = BigInteger(0) +BigInteger.ONE = BigInteger(1) +BigInteger.TEN = BigInteger(10) + + +# ============================================================ +# BigDecimal +# ============================================================ + +class BigDecimal(Comparable): + ZERO = None + ONE = None + TEN = None + + ROUND_UP = 0 + ROUND_DOWN = 1 + ROUND_CEILING = 2 + ROUND_FLOOR = 3 + ROUND_HALF_UP = 4 + ROUND_HALF_DOWN = 5 + ROUND_HALF_EVEN = 6 + ROUND_UNNECESSARY = 7 + + _round_map = { + 0: _DEC_ROUND_UP, + 1: _DEC_ROUND_DOWN, + 2: _DEC_ROUND_CEILING, + 3: _DEC_ROUND_FLOOR, + 4: _DEC_ROUND_HALF_UP, + 5: _DEC_ROUND_HALF_DOWN, + 6: _DEC_ROUND_HALF_EVEN, + 7: _DEC_ROUND_HALF_EVEN, + } + + def __init__(self, *args): + super().__init__() + if len(args) == 1: + val = args[0] + if isinstance(val, str): + self._dec = _Decimal(str(val)) + elif isinstance(val, float): + self._dec = _Decimal(repr(val)) + elif isinstance(val, int): + self._dec = _Decimal(val) + elif isinstance(val, BigInteger): + self._dec = _Decimal(val._value) + elif isinstance(val, BigDecimal): + self._dec = _Decimal(val._dec) + elif isinstance(val, _Decimal): + self._dec = val + else: + self._dec = _Decimal(str(val)) + elif len(args) == 2: + val, mc = args + if isinstance(val, str): + self._dec = _Decimal(str(val)) + elif isinstance(val, float): + self._dec = _Decimal(repr(val)) + else: + self._dec = _Decimal(val) + if mc and hasattr(mc, 'getPrecision') and mc.getPrecision() > 0: + self._dec = +self._dec + + @staticmethod + def valueOf(val): + if isinstance(val, int): + if val == 0: + return BigDecimal.ZERO + if val == 1: + return BigDecimal.ONE + if val == 10: + return BigDecimal.TEN + if isinstance(val, float): + return BigDecimal(repr(val)) + return BigDecimal(val) + + def add(self, other): + if isinstance(other, BigDecimal): + return BigDecimal(self._dec + other._dec) + return BigDecimal(self._dec + _Decimal(str(other))) + + def subtract(self, other): + if isinstance(other, BigDecimal): + return BigDecimal(self._dec - other._dec) + return BigDecimal(self._dec - _Decimal(str(other))) + + def multiply(self, other): + if isinstance(other, BigDecimal): + return BigDecimal(self._dec * other._dec) + return BigDecimal(self._dec * _Decimal(str(other))) + + def divide(self, *args): + if len(args) == 1: + other = args[0] + if isinstance(other, BigDecimal): + return BigDecimal(self._dec / other._dec) + return BigDecimal(self._dec / _Decimal(str(other))) + elif len(args) == 2: + other, rounding_mode = args + if isinstance(other, BigDecimal): + result = self._dec / other._dec + else: + result = self._dec / _Decimal(str(other)) + return BigDecimal(result) + elif len(args) == 3: + other, scale, rounding_mode = args + if isinstance(other, BigDecimal): + result = self._dec / other._dec + else: + result = self._dec / _Decimal(str(other)) + rounded = BigDecimal(result) + return rounded.setScale(scale, rounding_mode) + + def divideToIntegralValue(self, other): + if isinstance(other, BigDecimal): + return BigDecimal(int(self._dec / other._dec)) + return BigDecimal(int(self._dec / _Decimal(str(other)))) + + def remainder(self, other): + if isinstance(other, BigDecimal): + return BigDecimal(self._dec % other._dec) + return BigDecimal(self._dec % _Decimal(str(other))) + + def pow(self, n): + return BigDecimal(self._dec ** n) + + def abs(self): + return BigDecimal(abs(self._dec)) + + def negate(self): + return BigDecimal(-self._dec) + + def signum(self): + if self._dec > 0: + return 1 + if self._dec < 0: + return -1 + return 0 + + def scale(self): + return abs(self._dec.as_tuple().exponent) + + def precision(self): + return len(self._dec.as_tuple().digits) + + def round(self, mc): + if mc.getPrecision() > 0: + with _local_context() as ctx: + ctx.prec = mc.getPrecision() + return BigDecimal(+self._dec) + return BigDecimal(self._dec) + + def setScale(self, new_scale, rounding_mode=None): + if rounding_mode is None: + qe = _Decimal(10) ** (-new_scale) + return BigDecimal(self._dec.quantize(qe)) + py_mode = self._round_map.get(rounding_mode, _DEC_ROUND_HALF_UP) + qe = _Decimal(10) ** (-new_scale) + return BigDecimal(self._dec.quantize(qe, rounding=py_mode)) + + def ulp(self): + exponent = self._dec.as_tuple().exponent + return BigDecimal(_Decimal(10) ** exponent) + + def unscaledValue(self): + t = self._dec.as_tuple() + digits = t.digits + val = int("".join(str(d) for d in digits)) + return BigInteger(-val if t.sign else val) + + def byteValueExact(self): + val = self.intValueExact() + if val < -128 or val > 127: + raise ArithmeticError("Overflow") + return val + + def shortValueExact(self): + val = self.intValueExact() + if val < -32768 or val > 32767: + raise ArithmeticError("Overflow") + return val + + def intValueExact(self): + if self._dec != int(self._dec): + raise ArithmeticError("Rounding necessary") + return int(self._dec) + + def longValueExact(self): + if self._dec != int(self._dec): + raise ArithmeticError("Rounding necessary") + return int(self._dec) + + def floatValue(self): + return float(self._dec) + + def doubleValue(self): + return float(self._dec) + + def intValue(self): + return int(self._dec) + + def longValue(self): + return int(self._dec) + + def toString(self): + return str(self._dec) + + def toEngineeringString(self): + return format(self._dec, "e") + + def toPlainString(self): + return format(self._dec, "f") + + def compareTo(self, other): + if isinstance(other, BigDecimal): + return (self._dec > other._dec) - (self._dec < other._dec) + return -1 + + def equals(self, other): + if isinstance(other, BigDecimal): + return self._dec == other._dec + return False + + def hashCode(self): + return hash(self._dec) & 0x7FFFFFFF + + def min(self, other): + return BigDecimal(min(self._dec, other._dec if isinstance(other, BigDecimal) else _Decimal(str(other)))) + + def max(self, other): + return BigDecimal(max(self._dec, other._dec if isinstance(other, BigDecimal) else _Decimal(str(other)))) + + def __str__(self): + return str(self._dec) + + def __int__(self): + return int(self._dec) + + def __float__(self): + return float(self._dec) + + def __neg__(self): + return self.negate() + + def __abs__(self): + return self.abs() + + def __add__(self, other): + return self.add(other) + + def __sub__(self, other): + return self.subtract(other) + + def __mul__(self, other): + return self.multiply(other) + + def __truediv__(self, other): + return self.divide(other) + + def __mod__(self, other): + return self.remainder(other) + + def __pow__(self, other): + return self.pow(other) + + +BigDecimal.ZERO = BigDecimal(0) +BigDecimal.ONE = BigDecimal(1) +BigDecimal.TEN = BigDecimal(10) \ No newline at end of file diff --git a/android_pyapi/java/net.py b/android_pyapi/java/net.py new file mode 100644 index 0000000..9995689 --- /dev/null +++ b/android_pyapi/java/net.py @@ -0,0 +1,1582 @@ +""" +Java net standard library APIs implemented in Python. +java.net package - URL, Socket, HTTP connection, and networking classes. +""" + +from .._core import Stub, AndroidOnlyError, HAS_REQUESTS, HAS_HTTPX +from .io import IOException + +import os as _os +import socket as _socket +import struct as _struct +import threading as _threading +from urllib.parse import ( + urlparse as _urlparse, + urlunparse as _urlunparse, + quote as _quote, + unquote as _unquote, +) +from datetime import datetime as _datetime +from collections import OrderedDict as _OrderedDict + +__all__ = [ + "URL", "URLConnection", "HttpURLConnection", + "InetAddress", "InetSocketAddress", + "Socket", "ServerSocket", "DatagramSocket", "DatagramPacket", + "Proxy", "ProxySelector", "Authenticator", "PasswordAuthentication", + "NetworkInterface", "URI", + "CookieHandler", "CookieManager", "HttpCookie", + "URLDecoder", "URLEncoder", + "SocketException", "ConnectException", "BindException", + "NoRouteToHostException", "PortUnreachableException", + "ProtocolException", "SocketTimeoutException", + "MalformedURLException", "UnknownHostException", "UnknownServiceException", +] + + +# ============================================================ +# Exception classes +# ============================================================ + +class SocketException(IOException): + pass + +class ConnectException(SocketException): + pass + +class BindException(SocketException): + pass + +class NoRouteToHostException(SocketException): + pass + +class PortUnreachableException(SocketException): + pass + +class ProtocolException(IOException): + pass + +class SocketTimeoutException(IOException): + pass + +class MalformedURLException(IOException): + pass + +class UnknownHostException(IOException): + pass + +class UnknownServiceException(IOException): + pass + + +# ============================================================ +# InetAddress +# ============================================================ + +class InetAddress(Stub): + def __init__(self, host, addr): + super().__init__() + self._host = host + self._addr = addr # bytes, 4 or 16 + + @staticmethod + def getByName(host): + try: + addr = _socket.getaddrinfo(host, None, family=_socket.AF_UNSPEC) + if addr: + raw = addr[0][4][0] + return InetAddress(host, _socket.inet_pton(_socket.AF_INET, raw) if "." in raw else _socket.inet_pton(_socket.AF_INET6, raw)) + except Exception: + raise UnknownHostException(host) + + @staticmethod + def getAllByName(host): + try: + addrs = _socket.getaddrinfo(host, None, family=_socket.AF_UNSPEC) + results = [] + seen = set() + for a in addrs: + raw = a[4][0] + if raw not in seen: + seen.add(raw) + if "." in raw: + results.append(InetAddress(host, _socket.inet_pton(_socket.AF_INET, raw))) + else: + results.append(InetAddress(host, _socket.inet_pton(_socket.AF_INET6, raw))) + return results + except Exception: + raise UnknownHostException(host) + + @staticmethod + def getLocalHost(): + hostname = _socket.gethostname() + return InetAddress.getByName(hostname) + + @staticmethod + def getLoopbackAddress(): + return InetAddress("localhost", b"\x7f\x00\x00\x01") + + @staticmethod + def getByAddress(host, addr): + return InetAddress(host, addr) + + def getHostName(self): + return self._host + + def getHostAddress(self): + if len(self._addr) == 4: + return _socket.inet_ntoa(self._addr) + return _socket.inet_ntop(_socket.AF_INET6, self._addr) + + def getCanonicalHostName(self): + try: + name, _, _ = _socket.gethostbyaddr(self.getHostAddress()) + return name + except Exception: + return self._host + + def isReachable(self, timeout=5000): + try: + s = _socket.socket(_socket.AF_INET, _socket.SOCK_STREAM) + s.settimeout(timeout / 1000.0) + result = s.connect_ex((self.getHostAddress(), 7)) + s.close() + return result == 0 + except Exception: + return False + + def isLoopbackAddress(self): + if len(self._addr) == 4: + return self._addr[0] == 127 + return self._addr == b"\x00" * 15 + b"\x01" + + def isLinkLocalAddress(self): + if len(self._addr) == 4: + return self._addr[0] == 169 and self._addr[1] == 254 + return (self._addr[0] & 0xFF) == 0xFE and (self._addr[1] & 0xC0) == 0x80 + + def isSiteLocalAddress(self): + if len(self._addr) == 4: + b0 = self._addr[0] + return b0 == 10 or (b0 == 172 and 16 <= self._addr[1] <= 31) or (b0 == 192 and self._addr[1] == 168) + return False + + def isMCGlobal(self): + if len(self._addr) == 4: + return 224 <= self._addr[0] <= 239 and self._addr[0] != 224 and self._addr[0] != 239 + return (self._addr[0] & 0xFF) == 0xFF and (self._addr[1] & 0x0F) == 0x0E and (self._addr[2] & 0x20) == 0 + + def isMCLinkLocal(self): + if len(self._addr) == 4: + return self._addr[0] == 224 and self._addr[1] == 0 and self._addr[2] == 0 + return (self._addr[0] & 0xFF) == 0xFF and (self._addr[1] & 0x0F) == 0x02 + + def isMCNodeLocal(self): + if len(self._addr) == 4: + return self._addr[0] == 224 and self._addr[1] == 0 and self._addr[2] == 0 and self._addr[3] == 1 + return (self._addr[0] & 0xFF) == 0xFF and (self._addr[1] & 0x0F) == 0x01 + + def isMCOrgLocal(self): + if len(self._addr) == 4: + return self._addr[0] == 239 and 192 <= self._addr[1] <= 195 + return (self._addr[0] & 0xFF) == 0xFF and (self._addr[1] & 0x0F) == 0x08 + + def isMCSiteLocal(self): + if len(self._addr) == 4: + return self._addr[0] == 239 and 255 <= self._addr[1] <= 255 + return (self._addr[0] & 0xFF) == 0xFF and (self._addr[1] & 0x0F) == 0x05 + + def getAddress(self): + return self._addr + + def toString(self): + return f"{self._host}/{self.getHostAddress()}" + + def equals(self, other): + if isinstance(other, InetAddress): + return self._addr == other._addr + return False + + def hashCode(self): + return hash(bytes(self._addr)) & 0x7FFFFFFF + + +# ============================================================ +# InetSocketAddress +# ============================================================ + +class InetSocketAddress(Stub): + def __init__(self, *args): + super().__init__() + if len(args) == 2 and isinstance(args[0], int): + self._addr = None + self._port = args[0] if isinstance(args[0], int) else args[1] + elif len(args) == 2: + hostname, port = args + self._addr = InetAddress.getByName(hostname) if isinstance(hostname, str) else hostname + self._port = port + elif len(args) == 1 and isinstance(args[0], int): + self._addr = None + self._port = args[0] + + def getAddress(self): + return self._addr + + def getPort(self): + return self._port + + def getHostName(self): + return self._addr.getHostName() if self._addr else None + + def getHostString(self): + return self.getHostName() + + def isUnresolved(self): + return self._addr is None + + def toString(self): + return f"{self.getHostName()}:{self._port}" + + def equals(self, other): + if isinstance(other, InetSocketAddress): + return self._port == other._port and self._addr == other._addr + return False + + def hashCode(self): + return hash((self._port, self._addr)) & 0x7FFFFFFF + + +# ============================================================ +# URI +# ============================================================ + +class URI(Stub): + def __init__(self, *args): + super().__init__() + if len(args) == 1: + self._uri = args[0] + parsed = _urlparse(self._uri) + self._scheme = parsed.scheme + self._host = parsed.hostname + self._port = parsed.port + self._path = parsed.path + self._query = parsed.query + self._fragment = parsed.fragment + self._user_info = parsed.username + self._authority = parsed.netloc + elif len(args) == 5: + self._scheme = args[0] + self._authority = args[1] or "" + self._path = args[2] or "" + self._query = args[3] or "" + self._fragment = args[4] or "" + parsed = _urlparse(f"{self._scheme}://{self._authority}{self._path}") + self._host = parsed.hostname + self._port = parsed.port + self._user_info = parsed.username + self._uri = f"{self._scheme}://{self._authority}{self._path}" + if self._query: + self._uri += "?" + self._query + if self._fragment: + self._uri += "#" + self._fragment + else: + raise ValueError("URI constructor requires 1 or 5 arguments") + + def getScheme(self): + return self._scheme + + def getHost(self): + return self._host + + def getPort(self): + return self._port if self._port else -1 + + def getPath(self): + return self._path + + def getQuery(self): + return self._query + + def getFragment(self): + return self._fragment + + def getAuthority(self): + return self._authority + + def getRawAuthority(self): + return self._authority + + def getRawFragment(self): + return self._fragment + + def getRawPath(self): + return self._path + + def getRawQuery(self): + return self._query + + def getRawSchemeSpecificPart(self): + return f"//{self._authority}{self._path}" + + def getRawUserInfo(self): + return self._user_info or "" + + def getSchemeSpecificPart(self): + return self.getRawSchemeSpecificPart() + + def getUserInfo(self): + return self._user_info or "" + + def isAbsolute(self): + return bool(self._scheme) + + def isOpaque(self): + return not self._path and not self._authority and self._scheme + + def normalize(self): + import posixpath + normalized = posixpath.normpath(self._path) + return URI(f"{self._scheme}://{self._authority}{normalized}") + + def resolve(self, uri_or_str): + if isinstance(uri_or_str, URI): + other = uri_or_str + else: + other = URI(uri_or_str) + if other.isAbsolute(): + return other + base_path = self._path or "/" + if base_path.endswith("/"): + new_path = base_path + other._path + else: + new_path = "/".join(base_path.rsplit("/", 1)[:-1] + [other._path]) if other._path else base_path + return URI(f"{self._scheme}://{self._authority}{new_path}") + + def relativize(self, other): + if self._path and other._path and other._path.startswith(self._path): + rel = other._path[len(self._path):] + if rel.startswith("/"): + rel = rel[1:] + return URI(rel) + return other + + def toASCIIString(self): + return _quote(self._uri, safe=":/?#[]@!$&'()*+,;=-._~") + + def toString(self): + return self._uri + + def toURL(self): + return URL(self._uri) + + def compareTo(self, other): + return -1 if self._uri < other._uri else (1 if self._uri > other._uri else 0) + + def equals(self, other): + if isinstance(other, URI): + return self._uri == other._uri + return False + + def hashCode(self): + return hash(self._uri) & 0x7FFFFFFF + + def parseServerAuthority(self): + return self + + +# ============================================================ +# URL +# ============================================================ + +class URL(Stub): + def __init__(self, *args): + super().__init__() + if len(args) == 1: + spec = args[0] + parsed = _urlparse(str(spec)) + self._protocol = parsed.scheme + self._host = parsed.hostname or "" + self._port = parsed.port + self._path = parsed.path or "" + self._file = parsed.path or "" + if parsed.query: + self._file += "?" + parsed.query + self._query = parsed.query or "" + self._ref = parsed.fragment or "" + self._authority = parsed.netloc or "" + self._user_info = parsed.username or "" + self._url_str = str(spec) + elif len(args) == 4: + protocol, host, port, file = args + self._protocol = protocol or "" + self._host = host or "" + self._port = port + self._path = file or "" + self._file = file or "" + self._query = "" + self._ref = "" + self._authority = f"{host}:{port}" if port and port > 0 else host + self._user_info = "" + parts = [file or ""] + self._url_str = f"{protocol}://{self._authority}{file}" + + def getProtocol(self): + return self._protocol + + def getHost(self): + return self._host + + def getPort(self): + return self._port if self._port else -1 + + def getDefaultPort(self): + defaults = { + "http": 80, "https": 443, "ftp": 21, "smtp": 25, + "ssh": 22, "telnet": 23, "ldap": 389, + } + return defaults.get(self._protocol, -1) + + def getPath(self): + return self._path + + def getFile(self): + return self._file + + def getQuery(self): + return self._query + + def getRef(self): + return self._ref + + def getAuthority(self): + return self._authority + + def getUserInfo(self): + return self._user_info + + def getContent(self, classes=None): + return self.openConnection().getContent(classes) + + def openConnection(self, proxy=None): + return HttpURLConnection(self._url_str, proxy) + + def openStream(self): + return self.openConnection().getInputStream() + + def sameFile(self, other): + return (self._protocol == other._protocol and self._host == other._host + and self._port == other._port and self._path == other._path) + + def toExternalForm(self): + return self._url_str + + def toString(self): + return self._url_str + + def toURI(self): + return URI(self._url_str) + + def equals(self, other): + if isinstance(other, URL): + return self._url_str == other._url_str + return False + + def hashCode(self): + return hash(self._url_str) & 0x7FFFFFFF + + def openConnection(self, proxy=None): + return HttpURLConnection(self._url_str, proxy) + + def __str__(self): + return self._url_str + + +# ============================================================ +# URLConnection +# ============================================================ + +class URLConnection(Stub): + def __init__(self, url_str): + super().__init__() + self._url_str = url_str + self._connected = False + self._do_input = True + self._do_output = False + self._allow_user_interaction = False + self._use_caches = True + self._default_use_caches = True + self._if_modified_since = 0 + self._read_timeout = 0 + self._connect_timeout = 0 + self._request_properties = _OrderedDict() + self._response_code = None + self._response_message = None + self._response_headers = _OrderedDict() + self._content = None + + def connect(self): + if not self._connected: + self._connected = True + + def getContent(self, classes=None): + raise UnknownServiceException("getContent not implemented") + + def getContentEncoding(self): + return self._response_headers.get("Content-Encoding") + + def getContentLength(self): + val = self._response_headers.get("Content-Length") + return int(val) if val else -1 + + def getContentLengthLong(self): + return self.getContentLength() + + def getContentType(self): + return self._response_headers.get("Content-Type") + + def getDate(self): + val = self._response_headers.get("Date") + if val: + try: + return int(_datetime.strptime(val, "%a, %d %b %Y %H:%M:%S %Z").timestamp() * 1000) + except Exception: + pass + return 0 + + def getExpiration(self): + val = self._response_headers.get("Expires") + if val: + try: + return int(_datetime.strptime(val, "%a, %d %b %Y %H:%M:%S %Z").timestamp() * 1000) + except Exception: + pass + return 0 + + def getHeaderField(self, name): + return self._response_headers.get(name) + + def getHeaderFieldDate(self, name, default): + val = self._response_headers.get(name) + if val: + try: + return int(_datetime.strptime(val, "%a, %d %b %Y %H:%M:%S %Z").timestamp() * 1000) + except Exception: + pass + return default + + def getHeaderFieldInt(self, name, default): + val = self._response_headers.get(name) + return int(val) if val and val.isdigit() else default + + def getHeaderFieldKey(self, n): + keys = list(self._response_headers.keys()) + return keys[n] if n < len(keys) else None + + def getHeaderFields(self): + return self._response_headers + + def getInputStream(self): + raise UnknownServiceException("getInputStream not implemented") + + def getOutputStream(self): + raise UnknownServiceException("getOutputStream not implemented") + + def getPermission(self): + return None + + def getReadTimeout(self): + return self._read_timeout + + def setReadTimeout(self, timeout): + self._read_timeout = timeout + + def getConnectTimeout(self): + return self._connect_timeout + + def setConnectTimeout(self, timeout): + self._connect_timeout = timeout + + def getRequestProperty(self, key): + return self._request_properties.get(key) + + def setRequestProperty(self, key, value): + self._request_properties[key] = value + + def addRequestProperty(self, key, value): + if key in self._request_properties: + existing = self._request_properties[key] + if isinstance(existing, list): + existing.append(value) + else: + self._request_properties[key] = [existing, value] + else: + self._request_properties[key] = value + + def getDefaultUseCaches(self): + return self._default_use_caches + + def setDefaultUseCaches(self, use): + self._default_use_caches = use + + def getUseCaches(self): + return self._use_caches + + def setUseCaches(self, use): + self._use_caches = use + + def setDoInput(self, do_input): + self._do_input = do_input + + def getDoInput(self): + return self._do_input + + def setDoOutput(self, do_output): + self._do_output = do_output + + def getDoOutput(self): + return self._do_output + + def setAllowUserInteraction(self, allow): + self._allow_user_interaction = allow + + def getAllowUserInteraction(self): + return self._allow_user_interaction + + def setIfModifiedSince(self, time): + self._if_modified_since = time + + def getIfModifiedSince(self): + return self._if_modified_since + + +# ============================================================ +# HttpURLConnection +# ============================================================ + +class HttpURLConnection(URLConnection): + HTTP_OK = 200 + HTTP_CREATED = 201 + HTTP_ACCEPTED = 202 + HTTP_MOVED_PERM = 301 + HTTP_MOVED_TEMP = 302 + HTTP_NOT_MODIFIED = 304 + HTTP_BAD_REQUEST = 400 + HTTP_UNAUTHORIZED = 401 + HTTP_FORBIDDEN = 403 + HTTP_NOT_FOUND = 404 + HTTP_INTERNAL_ERROR = 500 + + def __init__(self, url_str, proxy=None): + super().__init__(url_str) + self._request_method = "GET" + self._instance_follow_redirects = True + self._chunked = False + self._fixed_length = -1 + self._input_stream = None + self._error_stream = None + self._response_code = -1 + self._response_message = "" + + def setRequestMethod(self, method): + method = method.upper() + valid = ["GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE", "TRACE", "PATCH"] + if method not in valid: + raise ProtocolException(f"Invalid HTTP method: {method}") + self._request_method = method + + def getRequestMethod(self): + return self._request_method + + def getResponseCode(self): + if self._response_code == -1: + self._do_request() + return self._response_code + + def getResponseMessage(self): + if self._response_code == -1: + self._do_request() + return self._response_message + + def getHeaderFields(self): + if self._response_code == -1: + self._do_request() + return self._response_headers + + def setInstanceFollowRedirects(self, follow): + self._instance_follow_redirects = follow + + def getInstanceFollowRedirects(self): + return self._instance_follow_redirects + + def getErrorStream(self): + if self._response_code == -1: + self._do_request() + if self._response_code >= 400: + return self._error_stream + return None + + def setChunkedStreamingMode(self, chunklen): + self._chunked = True + self._chunk_length = chunklen + + def setFixedLengthStreamingMode(self, content_length): + self._fixed_length = content_length + + def setFixedLengthStreamingModeLong(self, content_length): + self._fixed_length = content_length + + def disconnect(self): + self._input_stream = None + self._error_stream = None + self._connected = False + + def usingProxy(self): + return False + + def getInputStream(self): + if self._response_code == -1: + self._do_request() + if self._input_stream is None: + import io as _pyio_mod + self._input_stream = _pyio_mod.BytesIO(self._content.encode("utf-8") if isinstance(self._content, str) else self._content) + return self._input_stream + + def _do_request(self): + if HAS_REQUESTS: + self._do_requests() + elif HAS_HTTPX: + self._do_httpx() + else: + self._do_urllib() + + def _do_requests(self): + import requests + try: + kwargs = { + "headers": dict(self._request_properties), + "timeout": self._read_timeout / 1000.0 if self._read_timeout else None, + } + if self._instance_follow_redirects: + kwargs["allow_redirects"] = True + else: + kwargs["allow_redirects"] = False + method = self._request_method.lower() + if method == "get": + r = requests.get(self._url_str, **kwargs) + elif method == "post": + body = self._request_properties.pop("__body", None) + r = requests.post(self._url_str, data=body, **kwargs) + elif method == "put": + body = self._request_properties.pop("__body", None) + r = requests.put(self._url_str, data=body, **kwargs) + elif method == "delete": + r = requests.delete(self._url_str, **kwargs) + elif method == "head": + r = requests.head(self._url_str, **kwargs) + elif method == "options": + r = requests.options(self._url_str, **kwargs) + elif method == "patch": + body = self._request_properties.pop("__body", None) + r = requests.patch(self._url_str, data=body, **kwargs) + else: + r = requests.request(self._request_method, self._url_str, **kwargs) + self._response_code = r.status_code + self._response_message = r.reason + self._response_headers = dict(r.headers) + self._content = r.content + except Exception as e: + self._response_code = 0 + self._response_message = str(e) + + def _do_httpx(self): + import httpx + try: + client = httpx.Client(follow_redirects=self._instance_follow_redirects) + kwargs = { + "headers": dict(self._request_properties) if self._request_properties else None, + "timeout": self._read_timeout / 1000.0 if self._read_timeout else None, + } + method = self._request_method.lower() + if method == "get": + r = client.get(self._url_str, **kwargs) + elif method == "post": + body = self._request_properties.pop("__body", None) + r = client.post(self._url_str, content=body, **kwargs) + elif method == "put": + body = self._request_properties.pop("__body", None) + r = client.put(self._url_str, content=body, **kwargs) + elif method == "delete": + r = client.delete(self._url_str, **kwargs) + else: + r = client.request(self._request_method, self._url_str, **kwargs) + self._response_code = r.status_code + self._response_message = r.reason_phrase + self._response_headers = dict(r.headers) + self._content = r.content + except Exception as e: + self._response_code = 0 + self._response_message = str(e) + + def _do_urllib(self): + from urllib.request import urlopen, Request + from urllib.error import HTTPError + try: + req = Request(self._url_str, headers=dict(self._request_properties) if self._request_properties else {}, + method=self._request_method) + resp = urlopen(req, timeout=self._read_timeout / 1000.0 if self._read_timeout else None) + self._response_code = resp.status + self._response_message = resp.reason + self._response_headers = dict(resp.headers) + self._content = resp.read() + except HTTPError as e: + self._response_code = e.code + self._response_message = e.reason + self._response_headers = dict(e.headers) + self._content = e.read() + except Exception as e: + self._response_code = 0 + self._response_message = str(e) + self._content = b"" + + +# ============================================================ +# Socket +# ============================================================ + +class Socket(Stub): + def __init__(self, *args): + super().__init__() + self._socket = None + self._connected = False + self._bound = False + self._closed = False + self._input_shutdown = False + self._output_shutdown = False + self._input_stream = None + self._output_stream = None + self._timeout = 0 + self._keep_alive = False + self._tcp_no_delay = False + self._reuse_address = False + self._send_buffer_size = 0 + self._receive_buffer_size = 0 + self._linger = -1 + self._traffic_class = 0 + if len(args) == 2: + host, port = args + self.connect(InetSocketAddress(host, port)) + elif len(args) == 1 and isinstance(args[0], InetAddress): + addr, port = args[0], 0 + self.connect(InetSocketAddress(addr, port)) + + def connect(self, endpoint, timeout=None): + if isinstance(endpoint, InetSocketAddress): + host = endpoint.getHostName() + port = endpoint.getPort() + else: + host = str(endpoint) + port = 80 + self._socket = _socket.socket(_socket.AF_INET, _socket.SOCK_STREAM) + if self._timeout > 0: + self._socket.settimeout(self._timeout / 1000.0) + try: + self._socket.connect((host, port)) + self._connected = True + except OSError as e: + raise ConnectException(str(e)) + + def bind(self, endpoint): + if isinstance(endpoint, InetSocketAddress): + host = endpoint.getHostName() + port = endpoint.getPort() + else: + host = "0.0.0.0" + port = 0 + self._socket = _socket.socket(_socket.AF_INET, _socket.SOCK_STREAM) + self._socket.bind((host or "0.0.0.0", port)) + self._bound = True + + def close(self): + if not self._closed: + self._closed = True + if self._socket: + self._socket.close() + + def getInputStream(self): + if self._input_stream is None and self._socket: + import io as _pyio_mod + fd = self._socket.fileno() + self._input_stream = _pyio_mod.FileIO(fd, closefd=False) + return self._input_stream + + def getOutputStream(self): + if self._output_stream is None and self._socket: + import io as _pyio_mod + fd = self._socket.fileno() + self._output_stream = _pyio_mod.FileIO(fd, closefd=False, mode="ab") + return self._output_stream + + def getInetAddress(self): + if self._socket and self._connected: + host, _ = self._socket.getpeername() + return InetAddress.getByName(host) + return None + + def getPort(self): + if self._socket and self._connected: + _, port = self._socket.getpeername() + return port + return 0 + + def getLocalPort(self): + if self._socket: + _, port = self._socket.getsockname() + return port + return 0 + + def getLocalAddress(self): + if self._socket: + host, _ = self._socket.getsockname() + return InetAddress.getByName(host) + return InetAddress.getLoopbackAddress() + + def getRemoteSocketAddress(self): + if self._socket and self._connected: + host, port = self._socket.getpeername() + return InetSocketAddress(host, port) + return None + + def getLocalSocketAddress(self): + if self._socket: + host, port = self._socket.getsockname() + return InetSocketAddress(host, port) + return None + + def getSoTimeout(self): + return self._timeout + + def setSoTimeout(self, timeout): + self._timeout = timeout + if self._socket: + self._socket.settimeout(timeout / 1000.0 if timeout > 0 else None) + + def getTcpNoDelay(self): + return self._tcp_no_delay + + def setTcpNoDelay(self, on): + self._tcp_no_delay = on + if self._socket: + self._socket.setsockopt(_socket.IPPROTO_TCP, _socket.TCP_NODELAY, 1 if on else 0) + + def getKeepAlive(self): + return self._keep_alive + + def setKeepAlive(self, on): + self._keep_alive = on + if self._socket: + self._socket.setsockopt(_socket.SOL_SOCKET, _socket.SO_KEEPALIVE, 1 if on else 0) + + def getReuseAddress(self): + return self._reuse_address + + def setReuseAddress(self, on): + self._reuse_address = on + if self._socket: + self._socket.setsockopt(_socket.SOL_SOCKET, _socket.SO_REUSEADDR, 1 if on else 0) + + def getSendBufferSize(self): + return self._send_buffer_size + + def setSendBufferSize(self, size): + self._send_buffer_size = size + if self._socket: + self._socket.setsockopt(_socket.SOL_SOCKET, _socket.SO_SNDBUF, size) + + def getReceiveBufferSize(self): + return self._receive_buffer_size + + def setReceiveBufferSize(self, size): + self._receive_buffer_size = size + if self._socket: + self._socket.setsockopt(_socket.SOL_SOCKET, _socket.SO_RCVBUF, size) + + def getSoLinger(self): + return self._linger + + def setSoLinger(self, on, linger): + if on: + self._linger = linger + if self._socket: + self._socket.setsockopt(_socket.SOL_SOCKET, _socket.SO_LINGER, _struct.pack("ii", 1, linger)) + else: + self._linger = -1 + + def getTrafficClass(self): + return self._traffic_class + + def setTrafficClass(self, tc): + self._traffic_class = tc + + def shutdownInput(self): + if self._socket: + self._socket.shutdown(_socket.SHUT_RD) + self._input_shutdown = True + + def shutdownOutput(self): + if self._socket: + self._socket.shutdown(_socket.SHUT_WR) + self._output_shutdown = True + + def isConnected(self): + return self._connected + + def isBound(self): + return self._bound + + def isClosed(self): + return self._closed + + def isInputShutdown(self): + return self._input_shutdown + + def isOutputShutdown(self): + return self._output_shutdown + + def setPerformancePreferences(self, connection_time, latency, bandwidth): + pass + + +# ============================================================ +# ServerSocket +# ============================================================ + +class ServerSocket(Stub): + def __init__(self, port=0, backlog=50, bindAddr=None): + super().__init__() + self._socket = _socket.socket(_socket.AF_INET, _socket.SOCK_STREAM) + self._socket.setsockopt(_socket.SOL_SOCKET, _socket.SO_REUSEADDR, 1) + self._port = port + self._backlog = backlog + self._bind_addr = bindAddr + self._closed = False + self._timeout = 0 + if port > 0 or bindAddr: + self.bind(InetSocketAddress(bindAddr or "0.0.0.0", port), backlog) + + def bind(self, endpoint, backlog=50): + host = endpoint.getHostName() if isinstance(endpoint, InetSocketAddress) else str(endpoint) + port = endpoint.getPort() if isinstance(endpoint, InetSocketAddress) else 0 + self._socket.bind((host or "0.0.0.0", port)) + self._socket.listen(backlog) + self._port = self._socket.getsockname()[1] + + def accept(self): + if self._closed: + raise SocketException("Socket closed") + if self._timeout > 0: + self._socket.settimeout(self._timeout / 1000.0) + try: + client_sock, addr = self._socket.accept() + sock = Socket() + sock._socket = client_sock + sock._connected = True + return sock + except _socket.timeout: + raise SocketTimeoutException("Accept timed out") + + def close(self): + if not self._closed: + self._closed = True + self._socket.close() + + def getInetAddress(self): + host, _ = self._socket.getsockname() + return InetAddress.getByName(host) + + def getLocalPort(self): + return self._socket.getsockname()[1] if not self._closed else -1 + + def getSoTimeout(self): + return self._timeout + + def setSoTimeout(self, timeout): + self._timeout = timeout + self._socket.settimeout(timeout / 1000.0 if timeout > 0 else None) + + def getReceiveBufferSize(self): + return self._socket.getsockopt(_socket.SOL_SOCKET, _socket.SO_RCVBUF) + + def setReceiveBufferSize(self, size): + self._socket.setsockopt(_socket.SOL_SOCKET, _socket.SO_RCVBUF, size) + + def getReuseAddress(self): + return bool(self._socket.getsockopt(_socket.SOL_SOCKET, _socket.SO_REUSEADDR)) + + def setReuseAddress(self, on): + self._socket.setsockopt(_socket.SOL_SOCKET, _socket.SO_REUSEADDR, 1 if on else 0) + + +# ============================================================ +# DatagramSocket / DatagramPacket +# ============================================================ + +class DatagramPacket(Stub): + def __init__(self, buf, length, address=None, port=None): + super().__init__() + if address is not None: + self._buf = bytearray(buf) + self._length = length + self._address = address + self._port = port + else: + self._buf = bytearray(buf) + self._length = length + self._address = None + self._port = -1 + + def getAddress(self): + return self._address + + def getPort(self): + return self._port + + def getData(self): + return bytes(self._buf) + + def getLength(self): + return self._length + + def setAddress(self, address): + self._address = address + + def setPort(self, port): + self._port = port + + def setData(self, buf, offset=None, length=None): + if offset is not None and length is not None: + self._buf = bytearray(buf[offset:offset+length]) + self._length = length + else: + self._buf = bytearray(buf) + self._length = len(buf) + + def setLength(self, length): + self._length = length + + +class DatagramSocket(Stub): + def __init__(self, port=None): + super().__init__() + self._socket = _socket.socket(_socket.AF_INET, _socket.SOCK_DGRAM) + self._bound = False + self._closed = False + if port is not None: + self.bind(port) + + def bind(self, port): + self._socket.bind(("0.0.0.0", port)) + self._bound = True + + def send(self, p): + addr = p.getAddress() + port = p.getPort() + self._socket.sendto(p.getData()[:p.getLength()], (addr.getHostAddress(), port)) + + def receive(self, p): + data, addr = self._socket.recvfrom(p.getLength()) + p.setData(data) + p.setAddress(InetAddress(addr[0], _socket.inet_pton(_socket.AF_INET, addr[0]))) + p.setPort(addr[1]) + + def close(self): + if not self._closed: + self._closed = True + self._socket.close() + + def getLocalPort(self): + return self._socket.getsockname()[1] if not self._closed else -1 + + def isBound(self): + return self._bound + + def isClosed(self): + return self._closed + + def setSoTimeout(self, timeout): + self._socket.settimeout(timeout / 1000.0 if timeout > 0 else None) + + def getSoTimeout(self): + return self._socket.gettimeout() * 1000 if self._socket.gettimeout() else 0 + + +# ============================================================ +# Proxy +# ============================================================ + +class Proxy(Stub): + class Type: + DIRECT = "DIRECT" + HTTP = "HTTP" + SOCKS = "SOCKS" + + NO_PROXY = None + + def __init__(self, typ, sa=None): + super().__init__() + self._type = typ + self._address = sa + + def type(self): + return self._type + + def address(self): + return self._address + + def toString(self): + return f"{self._type} proxy @ {self._address}" + + +class ProxySelector(Stub): + _default = None + + @staticmethod + def getDefault(): + return ProxySelector._default + + @staticmethod + def setDefault(sel): + ProxySelector._default = sel + + def select(self, uri): + return [Proxy.NO_PROXY] + + def connectFailed(self, uri, sa, ioe): + pass + + +# ============================================================ +# Authenticator / PasswordAuthentication +# ============================================================ + +class Authenticator(Stub): + _default = None + + @staticmethod + def getDefault(): + return Authenticator._default + + @staticmethod + def setDefault(auth): + Authenticator._default = auth + + def getPasswordAuthentication(self): + return self._pwd_auth + + def requestPasswordAuthentication(self, addr, port, protocol, prompt, scheme): + return self._pwd_auth + + +class PasswordAuthentication(Stub): + def __init__(self, user, password): + super().__init__() + self._user = user + self._password = password + + def getUserName(self): + return self._user + + def getPassword(self): + return self._password + + +# ============================================================ +# NetworkInterface +# ============================================================ + +class NetworkInterface(Stub): + _cache = {} + + def __init__(self, name, display_name, addrs): + super().__init__() + self._name = name + self._display_name = display_name + self._addrs = addrs + + @staticmethod + def getByName(name): + if name in NetworkInterface._cache: + return NetworkInterface._cache[name] + + @staticmethod + def getByInetAddress(addr): + for ni in NetworkInterface._cache.values(): + for a in ni._addrs: + if a.equals(addr): + return ni + return None + + @staticmethod + def getNetworkInterfaces(): + return list(NetworkInterface._cache.values()) + + def getName(self): + return self._name + + def getDisplayName(self): + return self._display_name + + def getInetAddresses(self): + return iter(self._addrs) + + def getInterfaceAddresses(self): + return self._addrs + + def isUp(self): + return True + + def isLoopback(self): + return self._name == "lo" + + def isPointToPoint(self): + return False + + def supportsMulticast(self): + return True + + def getHardwareAddress(self): + return None + + def getMTU(self): + return 1500 + + def getSubInterfaces(self): + return iter([]) + + def getParent(self): + return None + + def isVirtual(self): + return False + + def toString(self): + return f"{self._display_name} ({self._name})" + + @staticmethod + def _init_from_socket(): + import netifaces + for iface_name in netifaces.interfaces(): + addrs = [] + ifaddresses = netifaces.ifaddresses(iface_name) + for family in [netifaces.AF_INET, netifaces.AF_INET6]: + if family in ifaddresses: + for addr_info in ifaddresses[family]: + try: + addr = addr_info["addr"] + if "." in addr: + addrs.append(InetAddress(addr, _socket.inet_pton(_socket.AF_INET, addr))) + else: + addrs.append(InetAddress(addr, _socket.inet_pton(_socket.AF_INET6, addr))) + except Exception: + pass + NetworkInterface._cache[iface_name] = NetworkInterface(iface_name, iface_name, addrs) + +try: + NetworkInterface._init_from_socket() +except Exception: + pass + + +# ============================================================ +# CookieHandler / CookieManager / HttpCookie +# ============================================================ + +class CookieHandler(Stub): + _default = None + + @staticmethod + def getDefault(): + return CookieHandler._default + + @staticmethod + def setDefault(handler): + CookieHandler._default = handler + + def get(self, uri, requestHeaders): + raise NotImplementedError + + def put(self, uri, responseHeaders): + raise NotImplementedError + + +class HttpCookie(Stub): + def __init__(self, name, value): + super().__init__() + self._name = name + self._value = value + self._comment = None + self._comment_url = None + self._discard = False + self._domain = None + self._max_age = -1 + self._path = None + self._portlist = None + self._secure = False + self._version = 0 + self._http_only = False + self._creation_time = _datetime.now() + + def getName(self): + return self._name + + def getValue(self): + return self._value + + def getComment(self): + return self._comment + + def getCommentURL(self): + return self._comment_url + + def getDiscard(self): + return self._discard + + def getDomain(self): + return self._domain + + def getMaxAge(self): + return self._max_age + + def getPath(self): + return self._path + + def getPortlist(self): + return self._portlist + + def getSecure(self): + return self._secure + + def getVersion(self): + return self._version + + def getHttpOnly(self): + return self._http_only + + def setComment(self, comment): + self._comment = comment + + def setCommentURL(self, url): + self._comment_url = url + + def setDiscard(self, discard): + self._discard = discard + + def setDomain(self, domain): + self._domain = domain + + def setMaxAge(self, max_age): + self._max_age = max_age + + def setPath(self, path): + self._path = path + + def setPortlist(self, portlist): + self._portlist = portlist + + def setSecure(self, secure): + self._secure = secure + + def setVersion(self, version): + self._version = version + + def setHttpOnly(self, http_only): + self._http_only = http_only + + def hasExpired(self): + if self._max_age < 0: + return False + elapsed = (_datetime.now() - self._creation_time).total_seconds() + return elapsed > self._max_age + + def equals(self, other): + if isinstance(other, HttpCookie): + return self._name == other._name + return False + + def toString(self): + return f"{self._name}={self._value}" + + def validate(self, name): + if not name or name.strip() == "": + raise ValueError("Cookie name cannot be empty") + + def __str__(self): + return self.toString() + + +class CookieManager(CookieHandler): + def __init__(self): + super().__init__() + self._cookie_store = {} + + def get(self, uri, requestHeaders): + cookies = [] + for domain, path, cookie in self._cookie_store.values(): + if not cookie.hasExpired(): + cookies.append(cookie.toString()) + return {"Cookie": "; ".join(cookies)} if cookies else {} + + def put(self, uri, responseHeaders): + set_cookie = responseHeaders.get("Set-Cookie") + if set_cookie: + parts = set_cookie.split(";") + name_val = parts[0].split("=", 1) + if len(name_val) == 2: + cookie = HttpCookie(name_val[0], name_val[1]) + for part in parts[1:]: + part = part.strip() + if "=" in part: + k, v = part.split("=", 1) + if k.lower() == "domain": + cookie.setDomain(v) + elif k.lower() == "path": + cookie.setPath(v) + elif k.lower() == "max-age": + cookie.setMaxAge(int(v)) + elif k.lower() == "version": + cookie.setVersion(int(v)) + else: + if part.lower() == "secure": + cookie.setSecure(True) + elif part.lower() == "httponly": + cookie.setHttpOnly(True) + elif part.lower() == "discard": + cookie.setDiscard(True) + self._cookie_store[(cookie.getDomain(), cookie.getPath(), cookie.getName())] = (cookie.getDomain(), cookie.getPath(), cookie) + + +# ============================================================ +# URLEncoder / URLDecoder +# ============================================================ + +class URLEncoder(Stub): + @staticmethod + def encode(s, charset="UTF-8"): + return _quote(str(s), safe="") + + @staticmethod + def encode(s): + return _quote(str(s), safe="") + + +class URLDecoder(Stub): + @staticmethod + def decode(s, charset="UTF-8"): + return _unquote(str(s), encoding=charset) + + @staticmethod + def decode(s): + return _unquote(str(s)) + + +# Initialize NO_PROXY +Proxy.NO_PROXY = Proxy(Proxy.Type.DIRECT, None) \ No newline at end of file diff --git a/android_pyapi/java/nio.py b/android_pyapi/java/nio.py new file mode 100644 index 0000000..9418716 --- /dev/null +++ b/android_pyapi/java/nio.py @@ -0,0 +1,622 @@ +""" +Java NIO standard library APIs implemented in Python. +java.nio package - Buffer, ByteBuffer, CharBuffer, etc. +""" + +from .._core import Stub +from .lang import RuntimeException +from .io import IOException +import struct as _struct +import sys as _sys + +IllegalStateException = type("IllegalStateException", (RuntimeException,), {}) +IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {}) + +__all__ = [ + "Buffer", "ByteBuffer", "CharBuffer", "ShortBuffer", + "IntBuffer", "LongBuffer", "FloatBuffer", "DoubleBuffer", + "MappedByteBuffer", "ByteOrder", + "BufferOverflowException", "BufferUnderflowException", + "ReadOnlyBufferException", "InvalidMarkException", +] + + +# ============================================================ +# Exception classes +# ============================================================ + +class BufferOverflowException(RuntimeException): + pass + +class BufferUnderflowException(RuntimeException): + pass + +class ReadOnlyBufferException(RuntimeException): + pass + +class InvalidMarkException(IllegalStateException): + pass + + +# ============================================================ +# ByteOrder +# ============================================================ + +class ByteOrder(Stub): + BIG_ENDIAN = None + LITTLE_ENDIAN = None + + def __init__(self, name): + super().__init__() + self._name = name + + @staticmethod + def nativeOrder(): + if _sys.byteorder == "little": + return ByteOrder.LITTLE_ENDIAN + return ByteOrder.BIG_ENDIAN + + def toString(self): + return self._name + + +ByteOrder.BIG_ENDIAN = ByteOrder("BIG_ENDIAN") +ByteOrder.LITTLE_ENDIAN = ByteOrder("LITTLE_ENDIAN") + + +# ============================================================ +# Buffer +# ============================================================ + +class Buffer(Stub): + def __init__(self, capacity, backing=None): + super().__init__() + self._capacity = capacity + self._position = 0 + self._limit = capacity + self._mark = -1 + self._backing = backing if backing is not None else bytearray(capacity) + self._read_only = False + + def capacity(self): + return self._capacity + + def position(self): + return self._position + + def position(self, new_position): + if new_position < 0 or new_position > self._limit: + raise IllegalArgumentException("Invalid position") + self._position = new_position + return self + + def limit(self): + return self._limit + + def limit(self, new_limit): + if new_limit < 0 or new_limit > self._capacity: + raise IllegalArgumentException("Invalid limit") + self._limit = new_limit + if self._position > self._limit: + self._position = self._limit + if self._mark > self._limit: + self._mark = -1 + return self + + def mark(self): + self._mark = self._position + return self + + def reset(self): + if self._mark < 0: + raise InvalidMarkException() + self._position = self._mark + return self + + def clear(self): + self._position = 0 + self._limit = self._capacity + self._mark = -1 + return self + + def flip(self): + self._limit = self._position + self._position = 0 + self._mark = -1 + return self + + def rewind(self): + self._position = 0 + self._mark = -1 + return self + + def remaining(self): + return self._limit - self._position + + def hasRemaining(self): + return self._position < self._limit + + def isReadOnly(self): + return self._read_only + + def isDirect(self): + return False + + def array(self): + if self._read_only: + raise ReadOnlyBufferException() + return self._backing + + def arrayOffset(self): + return 0 + + +# ============================================================ +# ByteBuffer +# ============================================================ + +class ByteBuffer(Buffer): + def __init__(self, capacity, backing=None, order=None): + super().__init__(capacity, backing) + self._order = order or ByteOrder.BIG_ENDIAN + + @staticmethod + def allocate(capacity): + return ByteBuffer(capacity, bytearray(capacity)) + + @staticmethod + def allocateDirect(capacity): + return ByteBuffer(capacity, bytearray(capacity)) + + @staticmethod + def wrap(array, offset=0, length=None): + if length is None: + length = len(array) - offset + buf = ByteBuffer(length, bytearray(array)) + buf._position = offset + buf._limit = offset + length + return buf + + def put(self, b, index=None): + if self._read_only: + raise ReadOnlyBufferException() + if index is None: + if isinstance(b, int): + if self._position >= self._limit: + raise BufferOverflowException() + self._backing[self._position] = b & 0xFF + self._position += 1 + elif isinstance(b, ByteBuffer): + n = b.remaining() + if self.remaining() < n: + raise BufferOverflowException() + self._backing[self._position:self._position+n] = b._backing[b._position:b._limit] + self._position += n + b._position = b._limit + elif isinstance(b, (bytes, bytearray)): + if self.remaining() < len(b): + raise BufferOverflowException() + self._backing[self._position:self._position+len(b)] = b + self._position += len(b) + else: + self._backing[index] = b & 0xFF + return self + + def get(self, index=None): + if index is None: + if self._position >= self._limit: + raise BufferUnderflowException() + val = self._backing[self._position] + self._position += 1 + return val & 0xFF + return self._backing[index] & 0xFF + + def putInt(self, value, index=None): + data = _struct.pack(">i" if self._order == ByteOrder.BIG_ENDIAN else "i" if self._order == ByteOrder.BIG_ENDIAN else "q" if self._order == ByteOrder.BIG_ENDIAN else "q" if self._order == ByteOrder.BIG_ENDIAN else "f" if self._order == ByteOrder.BIG_ENDIAN else "f" if self._order == ByteOrder.BIG_ENDIAN else " d" if self._order == ByteOrder.BIG_ENDIAN else " d" if self._order == ByteOrder.BIG_ENDIAN else " h" if self._order == ByteOrder.BIG_ENDIAN else " h" if self._order == ByteOrder.BIG_ENDIAN else " b) - (a < b) + return -1 + + +# ============================================================ +# IntBuffer +# ============================================================ + +class IntBuffer(Buffer): + def __init__(self, capacity, backing, offset=0, order=None): + super().__init__(capacity, backing) + self._int_order = order or ByteOrder.BIG_ENDIAN + self._int_offset = offset + + def get(self, index=None): + if index is not None: + idx = self._int_offset + index * 4 + return _struct.unpack(">i" if self._int_order == ByteOrder.BIG_ENDIAN else "i" if self._int_order == ByteOrder.BIG_ENDIAN else "i" if self._int_order == ByteOrder.BIG_ENDIAN else "q" if self._long_order == ByteOrder.BIG_ENDIAN else " q" if self._long_order == ByteOrder.BIG_ENDIAN else "q" if self._long_order == ByteOrder.BIG_ENDIAN else "f" if self._float_order == ByteOrder.BIG_ENDIAN else "f" if self._float_order == ByteOrder.BIG_ENDIAN else " f" if self._float_order == ByteOrder.BIG_ENDIAN else " d" if self._double_order == ByteOrder.BIG_ENDIAN else " d" if self._double_order == ByteOrder.BIG_ENDIAN else " d" if self._double_order == ByteOrder.BIG_ENDIAN else " h" if self._short_order == ByteOrder.BIG_ENDIAN else " h" if self._short_order == ByteOrder.BIG_ENDIAN else " h" if self._short_order == ByteOrder.BIG_ENDIAN else " h" if self._char_order == ByteOrder.BIG_ENDIAN else " h" if self._char_order == ByteOrder.BIG_ENDIAN else " h" if self._char_order == ByteOrder.BIG_ENDIAN else " = len(parts): + raise IllegalArgumentException("Invalid index") + return Path(parts[index]) + + def getNameCount(self): + parts = self._pypath.parts + if self.getRoot(): + parts = parts[1:] + return len(parts) + + def subpath(self, beginIndex, endIndex): + parts = self._pypath.parts + if self.getRoot(): + parts = parts[1:] + return Path(_os.path.join(*parts[beginIndex:endIndex])) + + def startsWith(self, other): + if isinstance(other, Path): + return str(self._pypath).startswith(str(other._pypath)) + return str(self._pypath).startswith(str(other)) + + def endsWith(self, other): + if isinstance(other, Path): + return str(self._pypath).endswith(str(other._pypath)) + return str(self._pypath).endswith(str(other)) + + def normalize(self): + return Path(_os.path.normpath(self._path)) + + def resolve(self, other): + if isinstance(other, Path): + other_path = str(other) + else: + other_path = str(other) + if _os.path.isabs(other_path): + return Path(other_path) + return Path(_os.path.join(self._path, other_path)) + + def resolveSibling(self, other): + parent = self.getParent() + if parent: + return parent.resolve(other) + return Path(other) + + def relativize(self, other): + if isinstance(other, Path): + other_str = str(other._pypath) + else: + other_str = str(other) + try: + rel = _os.path.relpath(other_str, self._path) + return Path(rel) + except ValueError: + return other + + def compareTo(self, other): + if isinstance(other, Path): + return (self._path > other._path) - (self._path < other._path) + return -1 + + def toString(self): + return self._path + + def toFile(self): + from ..io import File + return File(self._path) + + def isAbsolute(self): + return _os.path.isabs(self._path) + + def getFileSystem(self): + return FileSystems.getDefault() + + def iterator(self): + parts = self._pypath.parts + if self.getRoot(): + parts = parts[1:] + return iter([Path(p) for p in parts]) + + def register(self, watcher, events, *modifiers): + raise AndroidOnlyError("WatchService nio") + + def __str__(self): + return self._path + + def __eq__(self, other): + if isinstance(other, Path): + return self._path == other._path + return NotImplemented + + def __hash__(self): + return hash(self._path) + + +# ============================================================ +# Paths +# ============================================================ + +class Paths(Stub): + @staticmethod + def get(first, *more): + parts = [str(first)] + parts.extend(str(m) for m in more) + return Path(_os.path.join(*parts)) + + +# ============================================================ +# FileSystem +# ============================================================ + +class FileSystem(Stub): + def __init__(self): + super().__init__() + self._separator = _os.sep + self._open = True + + def getPath(self, first, *more): + return Paths.get(first, *more) + + def getSeparator(self): + return self._separator + + def getRootDirectories(self): + if _os.name == "nt": + import string + return [Path(d + ":\\") for d in string.ascii_uppercase if _os.path.exists(d + ":\\")] + return [Path("/")] + + def getUserPrincipalLookupService(self): + raise AndroidOnlyError("UserPrincipalLookupService") + + def newWatchService(self): + raise AndroidOnlyError("WatchService") + + def getFileStores(self): + return [FileStore("/")] + + def supportedFileAttributeViews(self): + return ["basic", "owner", "posix"] + + def close(self): + self._open = False + + def isOpen(self): + return self._open + + def isReadOnly(self): + return False + + +class FileSystems(Stub): + _default = None + + @staticmethod + def getDefault(): + if FileSystems._default is None: + FileSystems._default = FileSystem() + return FileSystems._default + + +# ============================================================ +# FileStore +# ============================================================ + +class FileStore(Stub): + def __init__(self, path): + super().__init__() + self._path = path + + def name(self): + return _os.path.basename(self._path) or "/" + + def type(self): + return "ext4" + + def isReadOnly(self): + return False + + def getTotalSpace(self): + try: + st = _os.statvfs(self._path) + return st.f_frsize * st.f_blocks + except OSError: + return 0 + + def getUsableSpace(self): + try: + st = _os.statvfs(self._path) + return st.f_frsize * st.f_bavail + except OSError: + return 0 + + def getUnallocatedSpace(self): + try: + st = _os.statvfs(self._path) + return st.f_frsize * st.f_bfree + except OSError: + return 0 + + def toString(self): + return f"{self.name()} ({self.type()})" + + +# ============================================================ +# FileVisitor / SimpleFileVisitor +# ============================================================ + +class FileVisitor(Stub): + def preVisitDirectory(self, dir, attrs): + return FileVisitResult.CONTINUE + + def postVisitDirectory(self, dir, exc): + return FileVisitResult.CONTINUE + + def visitFile(self, file, attrs): + return FileVisitResult.CONTINUE + + def visitFileFailed(self, file, exc): + return FileVisitResult.CONTINUE + + +class SimpleFileVisitor(FileVisitor): + pass + + +# ============================================================ +# DirectoryStream / Filter +# ============================================================ + +class DirectoryStream(Stub): + def __init__(self, path, filter=None): + super().__init__() + self._path = path + self._filter = filter + + def __iter__(self): + try: + entries = _os.listdir(str(self._path)) + for entry in entries: + p = Path(self._path, entry) + if self._filter is None or self._filter.accept(p): + yield p + except OSError: + raise IOException(f"Failed to list directory: {self._path}") + + def close(self): + pass + + +class DirectoryStreamFilter(Stub): + def accept(self, entry): + raise NotImplementedError + + +# ============================================================ +# PathMatcher +# ============================================================ + +class PathMatcher(Stub): + def __init__(self, pattern): + super().__init__() + self._pattern = pattern + + def matches(self, path): + import fnmatch + return fnmatch.fnmatch(str(path), self._pattern) + + +# ============================================================ +# WatchService / WatchKey / WatchEvent / Watchable (stubs) +# ============================================================ + +class Watchable(Stub): + def register(self, watcher, events, *modifiers): + raise AndroidOnlyError("WatchService") + +class WatchService(Stub): + def close(self): + pass + def poll(self): + return None + def take(self): + raise AndroidOnlyError("WatchService.take") + def poll(self, timeout, unit): + return None + +class WatchKey(Stub): + def isValid(self): + return True + def pollEvents(self): + return [] + def reset(self): + return True + def cancel(self): + pass + def watchable(self): + return None + +class WatchEvent(Stub): + class Kind(Stub): + def name(self): + return "" + def type(self): + return None + + def kind(self): + return None + def count(self): + return 0 + def context(self): + return None + + +# ============================================================ +# Files +# ============================================================ + +class Files(Stub): + @staticmethod + def copy(source, target, *options): + src = str(source) + dst = str(target) + replace = any(isinstance(o, StandardCopyOption) and o == StandardCopyOption.REPLACE_EXISTING for o in options) + try: + if replace and _os.path.exists(dst): + _os.remove(dst) + if _os.path.isdir(src): + _shutil.copytree(src, dst) + else: + _shutil.copy2(src, dst) + except OSError as e: + raise IOException(str(e)) + + @staticmethod + def move(source, target, *options): + src = str(source) + dst = str(target) + replace = any(isinstance(o, StandardCopyOption) and o == StandardCopyOption.REPLACE_EXISTING for o in options) + try: + if replace and _os.path.exists(dst): + _os.remove(dst) + _shutil.move(src, dst) + except OSError as e: + raise IOException(str(e)) + + @staticmethod + def delete(path): + try: + p = str(path) + if _os.path.isdir(p): + _os.rmdir(p) + else: + _os.remove(p) + except OSError as e: + raise NoSuchFileException(str(path)) + + @staticmethod + def deleteIfExists(path): + try: + Files.delete(path) + return True + except NoSuchFileException: + return False + + @staticmethod + def createDirectories(path): + _os.makedirs(str(path), exist_ok=True) + return path + + @staticmethod + def createDirectory(path, attrs=None): + _os.makedirs(str(path), exist_ok=True) + return path + + @staticmethod + def createFile(path, attrs=None): + p = str(path) + if _os.path.exists(p): + raise FileAlreadyExistsException(str(path)) + open(p, "a").close() + return path + + @staticmethod + def createTempFile(prefix, suffix, dir=None): + f = _tempfile.NamedTemporaryFile(prefix=prefix, suffix=suffix, + dir=str(dir) if dir else None, delete=False) + p = f.name + f.close() + return Path(p) + + @staticmethod + def createTempDirectory(prefix, attrs=None): + d = _tempfile.mkdtemp(prefix=prefix) + return Path(d) + + @staticmethod + def exists(path, *options): + return _os.path.exists(str(path)) + + @staticmethod + def notExists(path, *options): + return not _os.path.exists(str(path)) + + @staticmethod + def isReadable(path): + return _os.access(str(path), _os.R_OK) + + @staticmethod + def isWritable(path): + return _os.access(str(path), _os.W_OK) + + @staticmethod + def isExecutable(path): + return _os.access(str(path), _os.X_OK) + + @staticmethod + def isDirectory(path, *options): + return _os.path.isdir(str(path)) + + @staticmethod + def isRegularFile(path, *options): + return _os.path.isfile(str(path)) + + @staticmethod + def isSymbolicLink(path): + return _os.path.islink(str(path)) + + @staticmethod + def isHidden(path): + return _os.path.basename(str(path)).startswith(".") + + @staticmethod + def getLastModifiedTime(path, *options): + return int(_os.path.getmtime(str(path)) * 1000) + + @staticmethod + def setLastModifiedTime(path, time): + _os.utime(str(path), (time / 1000, time / 1000)) + + @staticmethod + def getAttribute(path, attribute, *options): + return None + + @staticmethod + def setAttribute(path, attribute, value, *options): + pass + + @staticmethod + def getPosixFilePermissions(path, *options): + return [] + + @staticmethod + def setPosixFilePermissions(path, perms, *options): + pass + + @staticmethod + def newInputStream(path, *options): + from ..io import FileInputStream + return FileInputStream(str(path)) + + @staticmethod + def newOutputStream(path, *options): + from ..io import FileOutputStream + append = any(isinstance(o, StandardOpenOption) and o == StandardOpenOption.APPEND for o in options) + return FileOutputStream(str(path), append) + + @staticmethod + def newBufferedReader(path, charset=None): + from ..io import BufferedReader, InputStreamReader, FileInputStream + inp = FileInputStream(str(path)) + reader = InputStreamReader(inp, charset or "utf-8") + return BufferedReader(reader) + + @staticmethod + def newBufferedWriter(path, charset=None, *options): + from ..io import BufferedWriter, OutputStreamWriter, FileOutputStream + append = any(isinstance(o, StandardOpenOption) and o == StandardOpenOption.APPEND for o in options) + out = FileOutputStream(str(path), append) + writer = OutputStreamWriter(out, charset or "utf-8") + return BufferedWriter(writer) + + @staticmethod + def readAllBytes(path): + with open(str(path), "rb") as f: + return f.read() + + @staticmethod + def readAllLines(path, charset=None): + with open(str(path), "r", encoding=charset or "utf-8") as f: + return f.read().splitlines() + + @staticmethod + def readString(path, charset=None): + with open(str(path), "r", encoding=charset or "utf-8") as f: + return f.read() + + @staticmethod + def write(path, content, *options): + mode = "wb" if isinstance(content, bytes) else "w" + open_mode = mode + if any(isinstance(o, StandardOpenOption) and o == StandardOpenOption.APPEND for o in options): + open_mode = "ab" if mode == "wb" else "a" + with open(str(path), open_mode) as f: + f.write(content) + + @staticmethod + def writeString(path, content, *options): + open_mode = "w" + if any(isinstance(o, StandardOpenOption) and o == StandardOpenOption.APPEND for o in options): + open_mode = "a" + with open(str(path), open_mode, encoding="utf-8") as f: + f.write(content) + + @staticmethod + def list(path): + try: + return [Path(_os.path.join(str(path), entry)) for entry in _os.listdir(str(path))] + except OSError: + raise IOException(f"Failed to list directory: {path}") + + @staticmethod + def walk(path, maxDepth=None): + for root, dirs, files in _os.walk(str(path)): + yield Path(root) + + @staticmethod + def find(start, maxDepth, matcher, *options): + for root, dirs, files in _os.walk(str(start)): + for f in files: + p = Path(_os.path.join(root, f)) + if matcher.matches(p): + yield p + + @staticmethod + def lines(path, charset=None): + with open(str(path), "r", encoding=charset or "utf-8") as f: + for line in f: + yield line.rstrip("\n") + + @staticmethod + def getFileStore(path): + return FileStore(str(path)) + + @staticmethod + def probeContentType(path): + return "application/octet-stream" + + @staticmethod + def getOwner(path, *options): + raise AndroidOnlyError("getOwner") + + @staticmethod + def setOwner(path, owner, *options): + raise AndroidOnlyError("setOwner") \ No newline at end of file diff --git a/android_pyapi/java/security.py b/android_pyapi/java/security.py new file mode 100644 index 0000000..2a7e131 --- /dev/null +++ b/android_pyapi/java/security.py @@ -0,0 +1,1145 @@ +""" +Java security standard library APIs implemented in Python. +java.security package - MessageDigest, SecureRandom, KeyStore, Signature, etc. +""" + +from .._core import Stub, AndroidOnlyError +from .lang import RuntimeException +import hashlib as _hashlib +import os as _os + +GeneralSecurityException = type("GeneralSecurityException", (RuntimeException,), {}) +IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {}) +DigestException = type("DigestException", (GeneralSecurityException,), {}) +InvalidAlgorithmParameterException = type("InvalidAlgorithmParameterException", (GeneralSecurityException,), {}) +InvalidKeyException = type("InvalidKeyException", (GeneralSecurityException,), {}) +InvalidKeySpecException = type("InvalidKeySpecException", (GeneralSecurityException,), {}) +InvalidParameterException = type("InvalidParameterException", (IllegalArgumentException,), {}) +KeyException = type("KeyException", (GeneralSecurityException,), {}) +KeyManagementException = type("KeyManagementException", (KeyException,), {}) +KeyStoreException = type("KeyStoreException", (GeneralSecurityException,), {}) +NoSuchAlgorithmException = type("NoSuchAlgorithmException", (GeneralSecurityException,), {}) +NoSuchProviderException = type("NoSuchProviderException", (GeneralSecurityException,), {}) +ProviderException = type("ProviderException", (RuntimeException,), {}) +SignatureException = type("SignatureException", (GeneralSecurityException,), {}) + +__all__ = [ + "MessageDigest", "MessageDigestSpi", + "SecureRandom", "SecureRandomSpi", + "KeyStore", "KeyStoreSpi", + "Key", "PrivateKey", "PublicKey", "KeyPair", "KeyPairGenerator", + "KeyPairGeneratorSpi", "KeyFactory", "KeyFactorySpi", + "KeySpec", "SecretKeySpec", + "RSAPublicKeySpec", "RSAPrivateKeySpec", + "PKCS8EncodedKeySpec", "X509EncodedKeySpec", + "AlgorithmParameters", "AlgorithmParameterGenerator", + "AlgorithmParameterGeneratorSpi", "AlgorithmParametersSpi", + "Provider", "Security", + "DigestInputStream", "DigestOutputStream", + "Signature", "SignatureSpi", + "CodeSigner", "CodeSource", "Policy", "Permission", + "AllPermission", "BasicPermission", "PermissionCollection", + "Principal", + "CertPath", "CertPathBuilder", "CertPathBuilderSpi", + "CertPathValidator", "CertPathValidatorSpi", + "CertificateFactory", "CertificateFactorySpi", + "CertStore", "CertStoreSpi", + "CRL", "X509CRL", "X509Certificate", "X509Extension", + "Guard", "GuardedObject", "Identity", "IdentityScope", "Signer", + "Timestamp", "UnresolvedPermission", + "GeneralSecurityException", "DigestException", + "InvalidAlgorithmParameterException", "InvalidKeyException", + "InvalidKeySpecException", "InvalidParameterException", + "KeyException", "KeyManagementException", "KeyStoreException", + "NoSuchAlgorithmException", "NoSuchProviderException", + "ProviderException", "SignatureException", +] + + +# ============================================================ +# Key interfaces +# ============================================================ + +class Key(Stub): + def getAlgorithm(self): + return "" + + def getFormat(self): + return "" + + def getEncoded(self): + return b"" + + +class PrivateKey(Key): + pass + + +class PublicKey(Key): + pass + + +# ============================================================ +# KeySpec / SecretKeySpec / RSA specs / EncodedKeySpecs +# ============================================================ + +class KeySpec(Stub): + pass + + +class SecretKeySpec(KeySpec, Key): + def __init__(self, key_bytes, algorithm): + super().__init__() + self._key = key_bytes + self._algorithm = algorithm + + def getAlgorithm(self): + return self._algorithm + + def getFormat(self): + return "RAW" + + def getEncoded(self): + return self._key + + def hashCode(self): + return hash(self._key) ^ hash(self._algorithm) + + +class RSAPublicKeySpec(KeySpec): + def __init__(self, modulus, public_exponent): + super().__init__() + self._modulus = modulus + self._public_exponent = public_exponent + + def getModulus(self): + return self._modulus + + def getPublicExponent(self): + return self._public_exponent + + +class RSAPrivateKeySpec(KeySpec): + def __init__(self, modulus, private_exponent): + super().__init__() + self._modulus = modulus + self._private_exponent = private_exponent + + def getModulus(self): + return self._modulus + + def getPrivateExponent(self): + return self._private_exponent + + +class PKCS8EncodedKeySpec(KeySpec): + def __init__(self, encoded_key): + super().__init__() + self._encoded_key = encoded_key + + def getEncoded(self): + return self._encoded_key + + def getFormat(self): + return "PKCS#8" + + +class X509EncodedKeySpec(KeySpec): + def __init__(self, encoded_key): + super().__init__() + self._encoded_key = encoded_key + + def getEncoded(self): + return self._encoded_key + + def getFormat(self): + return "X.509" + + +# ============================================================ +# KeyPair +# ============================================================ + +class KeyPair(Stub): + def __init__(self, public_key, private_key): + super().__init__() + self._public = public_key + self._private = private_key + + def getPublic(self): + return self._public + + def getPrivate(self): + return self._private + + +# ============================================================ +# KeyPairGenerator +# ============================================================ + +class KeyPairGenerator(Stub): + def __init__(self, algorithm): + super().__init__() + self._algorithm = algorithm + self._keysize = 2048 + + @staticmethod + def getInstance(algorithm, provider=None): + return KeyPairGenerator(algorithm) + + def initialize(self, keysize_or_spec, random=None): + if isinstance(keysize_or_spec, int): + self._keysize = keysize_or_spec + + def generateKeyPair(self): + return KeyPair(PublicKey(), PrivateKey()) + + def getAlgorithm(self): + return self._algorithm + + def getProvider(self): + return Provider("SUN", 1.0, "Default provider") + + +# ============================================================ +# KeyFactory +# ============================================================ + +class KeyFactory(Stub): + def __init__(self, algorithm): + super().__init__() + self._algorithm = algorithm + + @staticmethod + def getInstance(algorithm, provider=None): + return KeyFactory(algorithm) + + def generatePublic(self, key_spec): + return PublicKey() + + def generatePrivate(self, key_spec): + return PrivateKey() + + def getKeySpec(self, key, key_spec_cls): + if key_spec_cls == X509EncodedKeySpec: + return X509EncodedKeySpec(key.getEncoded()) + raise InvalidKeySpecException("Unsupported key spec") + + def translateKey(self, key): + return key + + def getAlgorithm(self): + return self._algorithm + + +# ============================================================ +# KeyFactorySpi / KeyPairGeneratorSpi (stubs) +# ============================================================ + +class KeyFactorySpi(Stub): + pass + +class KeyPairGeneratorSpi(Stub): + pass + + +# ============================================================ +# MessageDigest +# ============================================================ + +class MessageDigest(Stub): + _MD_MAP = { + "MD5": "md5", + "SHA-1": "sha1", + "SHA": "sha1", + "SHA-224": "sha224", + "SHA-256": "sha256", + "SHA-384": "sha384", + "SHA-512": "sha512", + } + + def __init__(self, algorithm): + super().__init__() + self._algorithm = algorithm + algo_lower = algorithm.replace("-", "").lower() + if algo_lower in ("md5", "sha1", "sha224", "sha256", "sha384", "sha512"): + self._impl = _hashlib.new(algo_lower) + else: + self._impl = _hashlib.new("sha256") + + @staticmethod + def getInstance(algorithm, provider=None): + if algorithm not in MessageDigest._MD_MAP: + raise NoSuchAlgorithmException(f"{algorithm} not available") + return MessageDigest(algorithm) + + def update(self, data, offset=0, length=-1): + if length < 0: + self._impl.update(data) + else: + self._impl.update(data[offset:offset+length]) + + def digest(self, data=None): + if data is not None: + self._impl.update(data) + return self._impl.digest() + + def toString(self): + return f"MessageDigest: {self._algorithm}" + + def getAlgorithm(self): + return self._algorithm + + def getProvider(self): + return Provider("SUN", 1.0, "Default provider") + + def getDigestLength(self): + return self._impl.digest_size + + @staticmethod + def isEqual(digest_a, digest_b): + return digest_a == digest_b + + def clone(self): + md = MessageDigest(self._algorithm) + md._impl = self._impl.copy() + return md + + def reset(self): + self._impl = _hashlib.new(self._algorithm.replace("-", "").lower()) + + +class MessageDigestSpi(Stub): + pass + + +# ============================================================ +# SecureRandom +# ============================================================ + +class SecureRandom(Stub): + def __init__(self, seed=None): + super().__init__() + import random as _random + self._random = _random.Random() + if seed is not None: + self._random.seed(seed) + + def nextBytes(self, bytes_arr): + data = _os.urandom(len(bytes_arr)) + for i in range(len(bytes_arr)): + bytes_arr[i] = data[i] + + def nextInt(self, bound=None): + if bound is None: + return int.from_bytes(_os.urandom(4), "big") + return int.from_bytes(_os.urandom(4), "big") % bound + + def nextLong(self): + return int.from_bytes(_os.urandom(8), "big") + + def nextBoolean(self): + return int.from_bytes(_os.urandom(1), "big") % 2 == 0 + + def nextFloat(self): + return int.from_bytes(_os.urandom(4), "big") / 4294967296.0 + + def nextDouble(self): + return int.from_bytes(_os.urandom(8), "big") / 18446744073709551616.0 + + def nextGaussian(self): + import random as _random + return _random.gauss(0, 1) + + def generateSeed(self, num_bytes): + return _os.urandom(num_bytes) + + def getAlgorithm(self): + return "SHA1PRNG" + + def getProvider(self): + return Provider("SUN", 1.0, "Default provider") + + def setSeed(self, seed): + self._random.seed(seed) + + def getSeed(self, num_bytes): + return _os.urandom(num_bytes) + + +class SecureRandomSpi(Stub): + pass + + +# ============================================================ +# Provider / Security +# ============================================================ + +class Provider(Stub): + def __init__(self, name, version, info): + super().__init__() + self._name = name + self._version = version + self._info = info + self._properties = {} + + def getName(self): + return self._name + + def getVersion(self): + return self._version + + def getInfo(self): + return self._info + + def getProperty(self, key): + return self._properties.get(key) + + def setProperty(self, key, value): + self._properties[key] = value + + def toString(self): + return f"{self._name} v{self._version}" + + +class Security(Stub): + _providers = [Provider("SUN", 1.0, "SUN provider")] + + @staticmethod + def getProvider(name): + for p in Security._providers: + if p.getName() == name: + return p + return None + + @staticmethod + def getProviders(): + return Security._providers + + @staticmethod + def addProvider(provider): + Security._providers.append(provider) + return len(Security._providers) - 1 + + @staticmethod + def removeProvider(name): + for i, p in enumerate(Security._providers): + if p.getName() == name: + Security._providers.pop(i) + return + + @staticmethod + def getAlgorithms(service_name): + if service_name == "MessageDigest": + return list(MessageDigest._MD_MAP.keys()) + return [] + + +# ============================================================ +# DigestInputStream / DigestOutputStream +# ============================================================ + +class DigestInputStream(Stub): + def __init__(self, stream, digest): + super().__init__() + self._stream = stream + self._digest = digest + + def getMessageDigest(self): + return self._digest + + def setMessageDigest(self, digest): + self._digest = digest + + def read(self, b=None, off=0, length=0): + if b is None: + data = self._stream.read() + else: + data = self._stream.read(length) + if data: + self._digest.update(data) + return data + + def toString(self): + return f"DigestInputStream: {self._digest}" + + +class DigestOutputStream(Stub): + def __init__(self, stream, digest): + super().__init__() + self._stream = stream + self._digest = digest + + def getMessageDigest(self): + return self._digest + + def setMessageDigest(self, digest): + self._digest = digest + + def write(self, b, off=0, length=0): + if isinstance(b, (bytes, bytearray)): + data = b if length == 0 else b[off:off+length] + else: + data = bytes([b]) + self._digest.update(data) + self._stream.write(data) + + def toString(self): + return f"DigestOutputStream: {self._digest}" + + +# ============================================================ +# Signature +# ============================================================ + +class Signature(Stub): + def __init__(self, algorithm): + super().__init__() + self._algorithm = algorithm + self._state = "UNINITIALIZED" + self._impl = _hashlib.new("sha256") + + @staticmethod + def getInstance(algorithm, provider=None): + return Signature(algorithm) + + def initSign(self, private_key, random=None): + self._state = "SIGN" + + def initVerify(self, public_key): + self._state = "VERIFY" + + def update(self, data, off=0, length=-1): + if length < 0: + self._impl.update(data) + else: + self._impl.update(data[off:off+length]) + + def sign(self): + self._state = "UNINITIALIZED" + return self._impl.digest() + + def verify(self, signature): + self._state = "UNINITIALIZED" + return self._impl.digest() == signature + + def setParameter(self, param, value=None): + pass + + def getParameters(self): + return None + + def getAlgorithm(self): + return self._algorithm + + def getProvider(self): + return Provider("SUN", 1.0, "SUN provider") + + +class SignatureSpi(Stub): + pass + + +# ============================================================ +# KeyStore +# ============================================================ + +class KeyStore(Stub): + class PasswordProtection(Stub): + def __init__(self, password): + super().__init__() + self._password = password + + def getPassword(self): + return self._password + + class Entry(Stub): + pass + + class PrivateKeyEntry(Entry): + def __init__(self, private_key, cert_chain): + super().__init__() + self._private_key = private_key + self._cert_chain = cert_chain + + def getPrivateKey(self): + return self._private_key + + def getCertificateChain(self): + return self._cert_chain + + class SecretKeyEntry(Entry): + def __init__(self, secret_key): + super().__init__() + self._secret_key = secret_key + + def getSecretKey(self): + return self._secret_key + + class TrustedCertificateEntry(Entry): + def __init__(self, cert): + super().__init__() + self._cert = cert + + def getTrustedCertificate(self): + return self._cert + + def __init__(self, type_name="JKS"): + super().__init__() + self._type = type_name + self._entries = {} + self._loaded = False + + @staticmethod + def getInstance(type_name, provider=None): + return KeyStore(type_name) + + def load(self, stream=None, password=None): + self._loaded = True + + def store(self, stream, password): + pass + + def setKeyEntry(self, alias, key, password, chain): + self._entries[alias] = (key, chain) + + def setCertificateEntry(self, alias, cert): + self._entries[alias] = cert + + def getKey(self, alias, password): + entry = self._entries.get(alias) + if isinstance(entry, tuple): + return entry[0] + return None + + def getCertificate(self, alias): + entry = self._entries.get(alias) + if isinstance(entry, tuple): + return entry[1][0] if entry[1] else None + return entry + + def getCertificateChain(self, alias): + entry = self._entries.get(alias) + if isinstance(entry, tuple): + return entry[1] + return None + + def getCertificateAliases(self, cert): + return [] + + def aliases(self): + return list(self._entries.keys()) + + def containsAlias(self, alias): + return alias in self._entries + + def deleteEntry(self, alias): + if alias in self._entries: + del self._entries[alias] + + def size(self): + return len(self._entries) + + def isCertificateEntry(self, alias): + return alias in self._entries and not isinstance(self._entries[alias], tuple) + + def isKeyEntry(self, alias): + return alias in self._entries and isinstance(self._entries[alias], tuple) + + def getType(self): + return self._type + + def getProvider(self): + return Provider("SUN", 1.0, "SUN provider") + + def getCreationDate(self, alias): + from datetime import datetime + return datetime.now() + + +class KeyStoreSpi(Stub): + pass + + +# ============================================================ +# X509Certificate +# ============================================================ + +class X509Certificate(Stub): + def __init__(self): + super().__init__() + self._encoded = b"" + + def getSubjectX500Principal(self): + return Principal() + + def getIssuerX500Principal(self): + return Principal() + + def getSerialNumber(self): + return 0 + + def getNotBefore(self): + from datetime import datetime + return datetime.now() + + def getNotAfter(self): + from datetime import datetime + return datetime.now() + + def getSigAlgName(self): + return "SHA256withRSA" + + def getSigAlgOID(self): + return "1.2.840.113549.1.1.11" + + def getVersion(self): + return 3 + + def getPublicKey(self): + return PublicKey() + + def getEncoded(self): + return self._encoded + + def verify(self, key): + return True + + def checkValidity(self, date=None): + return True + + def toString(self): + return "X509Certificate" + + +# ============================================================ +# AlgorithmParameters / AlgorithmParameterGenerator (stubs) +# ============================================================ + +class AlgorithmParameters(Stub): + def __init__(self, algorithm): + super().__init__() + self._algorithm = algorithm + + @staticmethod + def getInstance(algorithm, provider=None): + return AlgorithmParameters(algorithm) + + def init(self, params, format=None): + pass + + def getEncoded(self, format=None): + return b"" + + def getAlgorithm(self): + return self._algorithm + + def getProvider(self): + return Provider("SUN", 1.0, "SUN provider") + + def toString(self): + return f"{self._algorithm} parameters" + + +class AlgorithmParametersSpi(Stub): + pass + + +class AlgorithmParameterGenerator(Stub): + @staticmethod + def getInstance(algorithm, provider=None): + return AlgorithmParameterGenerator() + + def init(self, size_or_genparam, random=None): + pass + + def generateParameters(self): + return AlgorithmParameters("DSA") + + +class AlgorithmParameterGeneratorSpi(Stub): + pass + + +# ============================================================ +# Permission / AllPermission / BasicPermission (stubs) +# ============================================================ + +class Permission(Stub): + def __init__(self, name): + super().__init__() + self._name = name + + def getName(self): + return self._name + + def getActions(self): + return "" + + def implies(self, permission): + return True + + def equals(self, obj): + return isinstance(obj, Permission) and self._name == obj._name + + def hashCode(self): + return hash(self._name) + + def toString(self): + return f"({self.__class__.__name__} {self._name})" + + +class AllPermission(Permission): + def __init__(self): + super().__init__(" ") + + +class BasicPermission(Permission): + def __init__(self, name, actions=None): + super().__init__(name) + + +class PermissionCollection(Stub): + def __init__(self): + super().__init__() + self._permissions = [] + + def add(self, permission): + self._permissions.append(permission) + + def implies(self, permission): + return True + + def elements(self): + return iter(self._permissions) + + def toString(self): + return str(self._permissions) + + +class UnresolvedPermission(Permission): + def __init__(self, type_name, name, actions, certs): + super().__init__(name) + self._unresolved_type = type_name + self._unresolved_actions = actions + self._certs = certs + + def getUnresolvedType(self): + return self._unresolved_type + + def getUnresolvedActions(self): + return self._unresolved_actions + + def getUnresolvedCerts(self): + return self._certs + + +# ============================================================ +# Principal / CodeSource / Policy (stubs) +# ============================================================ + +class Principal(Stub): + def __init__(self, name=""): + super().__init__() + self._name = name + + def getName(self): + return self._name + + def toString(self): + return self._name + + def equals(self, other): + return isinstance(other, Principal) and self._name == other._name + + def hashCode(self): + return hash(self._name) + + +class CodeSource(Stub): + def __init__(self, location, certs): + super().__init__() + self._location = location + + def getLocation(self): + return self._location + + def getCertificates(self): + return [] + + def implies(self, codesource): + return True + + +class Policy(Stub): + _policy = None + + @staticmethod + def getPolicy(): + if Policy._policy is None: + Policy._policy = Policy() + return Policy._policy + + @staticmethod + def setPolicy(policy): + Policy._policy = policy + + def getPermissions(self, codesource): + return PermissionCollection() + + def implies(self, cs, perm): + return True + + def refresh(self): + pass + + +# ============================================================ +# CertPath / CertPathBuilder / CertPathValidator (stubs) +# ============================================================ + +class CertPath(Stub): + def __init__(self, algorithm): + super().__init__() + self._algorithm = algorithm + self._certs = [] + + def getCertificates(self): + return self._certs + + def getType(self): + return self._algorithm + + def getEncoded(self): + return b"" + + def toString(self): + return f"CertPath: {self._algorithm}" + + +class CertPathBuilder(Stub): + @staticmethod + def getInstance(algorithm, provider=None): + return CertPathBuilder() + + def build(self, params): + return CertPath("X.509") + + +class CertPathBuilderSpi(Stub): + pass + + +class CertPathValidator(Stub): + @staticmethod + def getInstance(algorithm, provider=None): + return CertPathValidator() + + def validate(self, cert_path, params): + return None + + +class CertPathValidatorSpi(Stub): + pass + + +class CertificateFactory(Stub): + @staticmethod + def getInstance(algorithm, provider=None): + return CertificateFactory() + + def generateCertificate(self, in_stream): + return X509Certificate() + + def generateCertPath(self, in_stream): + return CertPath("X.509") + + def getCertPathEncodings(self): + return ["PKCS7"] + + +class CertificateFactorySpi(Stub): + pass + + +class CertStore(Stub): + @staticmethod + def getInstance(type_name, params, provider=None): + return CertStore() + + def getCertificates(self, selector): + return [] + + def getCRLs(self, selector): + return [] + + +class CertStoreSpi(Stub): + pass + + +# ============================================================ +# CRL / X509CRL (stubs) +# ============================================================ + +class CRL(Stub): + def __init__(self): + super().__init__() + + def isRevoked(self, cert): + return False + + def toString(self): + return "CRL" + + +class X509CRL(CRL): + def __init__(self): + super().__init__() + + def getEncoded(self): + return b"" + + def verify(self, key): + return True + + def getIssuerX500Principal(self): + return Principal() + + def toString(self): + return "X509CRL" + + +class X509Extension(Stub): + pass + + +# ============================================================ +# Guard / GuardedObject (stubs) +# ============================================================ + +class Guard(Stub): + def checkGuard(self, object): + pass + + +class GuardedObject(Stub): + def __init__(self, object, guard): + super().__init__() + self._object = object + + def getObject(self): + return self._object + + +# ============================================================ +# Identity / IdentityScope / Signer (stubs) +# ============================================================ + +class Identity(Stub): + def __init__(self, name, scope=None): + super().__init__() + self._name = name + + def getName(self): + return self._name + + def getPublicKey(self): + return PublicKey() + + def setPublicKey(self, key): + pass + + def toString(self): + return self._name + + def getScope(self): + return None + + +class IdentityScope(Identity): + def __init__(self, name): + super().__init__(name) + + def getIdentity(self, name): + return None + + def addIdentity(self, identity): + pass + + def removeIdentity(self, identity): + pass + + def identities(self): + return [] + + @staticmethod + def getSystemScope(): + return None + + def size(self): + return 0 + + def toString(self): + return self._name + + +class Signer(Identity): + def __init__(self, name): + super().__init__(name) + + def getPrivateKey(self): + return PrivateKey() + + def setKeyPair(self, pair): + pass + + def toString(self): + return self._name + + +# ============================================================ +# CodeSigner / Timestamp (stubs) +# ============================================================ + +class Timestamp(Stub): + def __init__(self, timestamp, signer_cert_path): + super().__init__() + self._timestamp = timestamp + + def getTimestamp(self): + return self._timestamp + + def getSignerCertPath(self): + return CertPath("X.509") + + def equals(self, other): + return isinstance(other, Timestamp) + + def hashCode(self): + return 0 + + def toString(self): + return "Timestamp" + + +class CodeSigner(Stub): + def __init__(self, signer_cert_path, timestamp): + super().__init__() + self._signer_cert_path = signer_cert_path + self._timestamp = timestamp + + def getSignerCertPath(self): + return self._signer_cert_path + + def getTimestamp(self): + return self._timestamp + + def equals(self, other): + return isinstance(other, CodeSigner) + + def hashCode(self): + return 0 + + def toString(self): + return "CodeSigner" \ No newline at end of file diff --git a/android_pyapi/java/text.py b/android_pyapi/java/text.py new file mode 100644 index 0000000..3e36ffa --- /dev/null +++ b/android_pyapi/java/text.py @@ -0,0 +1,1041 @@ +""" +Java text standard library APIs implemented in Python. +java.text package - DateFormat, SimpleDateFormat, DecimalFormat, NumberFormat, etc. +""" + +from .._core import Stub +from .lang import RuntimeException +import datetime as _datetime +import time as _time +import re as _re +import math as _py_math +import locale as _locale +from decimal import Decimal as _Decimal + +IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {}) + +__all__ = [ + "SimpleDateFormat", "DateFormat", "DateFormatSymbols", + "DecimalFormat", "DecimalFormatSymbols", "NumberFormat", + "ChoiceFormat", "MessageFormat", "FieldPosition", "ParsePosition", + "ParseException", "Normalizer", + "StringCharacterIterator", "CharacterIterator", + "BreakIterator", "Collator", "CollationKey", "RuleBasedCollator", + "Annotation", "AttributedCharacterIterator", "AttributedString", + "Bidi", "Format", +] + + +# ============================================================ +# ParseException +# ============================================================ + +class ParseException(Exception): + def __init__(self, s, error_offset): + super().__init__(s) + self._error_offset = error_offset + + def getErrorOffset(self): + return self._error_offset + + +# ============================================================ +# ParsePosition +# ============================================================ + +class ParsePosition(Stub): + def __init__(self, index): + super().__init__() + self._index = index + self._error_index = -1 + + def getIndex(self): + return self._index + + def setIndex(self, index): + self._index = index + + def getErrorIndex(self): + return self._error_index + + def setErrorIndex(self, ei): + self._error_index = ei + + +# ============================================================ +# FieldPosition +# ============================================================ + +class FieldPosition(Stub): + def __init__(self, field): + super().__init__() + self._field = field + self._begin = 0 + self._end = 0 + + def getField(self): + return self._field + + def getBeginIndex(self): + return self._begin + + def getEndIndex(self): + return self._end + + def setBeginIndex(self, bi): + self._begin = bi + + def setEndIndex(self, ei): + self._end = ei + + +# ============================================================ +# Format (base) +# ============================================================ + +class Format(Stub): + def format(self, obj, to_append_to=None, pos=None): + raise NotImplementedError + + def parseObject(self, source, pos=None): + raise NotImplementedError + + +# ============================================================ +# DateFormat +# ============================================================ + +class DateFormat(Format): + SHORT = 3 + MEDIUM = 2 + LONG = 1 + FULL = 0 + DEFAULT = 2 + + def __init__(self): + super().__init__() + self._calendar = None + self._number_format = None + self._lenient = False + self._timezone = None + + @staticmethod + def getDateInstance(style=2, locale=None): + return SimpleDateFormat("yyyy-MM-dd") + + @staticmethod + def getTimeInstance(style=2, locale=None): + return SimpleDateFormat("HH:mm:ss") + + @staticmethod + def getDateTimeInstance(date_style=2, time_style=2, locale=None): + return SimpleDateFormat("yyyy-MM-dd HH:mm:ss") + + @staticmethod + def getInstance(): + return SimpleDateFormat("yyyy-MM-dd HH:mm:ss") + + def format(self, date, to_append_to=None, pos=None): + raise NotImplementedError + + def parse(self, source, pos=None): + raise NotImplementedError + + def setLenient(self, lenient): + self._lenient = lenient + + def isLenient(self): + return self._lenient + + def getCalendar(self): + return self._calendar + + def setCalendar(self, cal): + self._calendar = cal + + def getNumberFormat(self): + return self._number_format + + def setNumberFormat(self, nf): + self._number_format = nf + + def getTimeZone(self): + return self._timezone + + def setTimeZone(self, tz): + self._timezone = tz + + +# ============================================================ +# DateFormatSymbols +# ============================================================ + +class DateFormatSymbols(Stub): + def __init__(self, locale=None): + super().__init__() + self._months = [ + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December", + ] + self._short_months = [ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + ] + self._weekdays = [ + "", "Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday", + ] + self._short_weekdays = [ + "", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", + ] + self._ampm = ["AM", "PM"] + self._eras = ["BC", "AD"] + self._zone_strings = [] + self._local_pattern_chars = "GyMdkHmsSEDFwWahKzZ" + + def getMonths(self): + return self._months + + def setMonths(self, months): + self._months = months + + def getShortMonths(self): + return self._short_months + + def setShortMonths(self, months): + self._short_months = months + + def getWeekdays(self): + return self._weekdays + + def setWeekdays(self, wd): + self._weekdays = wd + + def getShortWeekdays(self): + return self._short_weekdays + + def setShortWeekdays(self, wd): + self._short_weekdays = wd + + def getAmPmStrings(self): + return self._ampm + + def setAmPmStrings(self, ap): + self._ampm = ap + + def getZoneStrings(self): + return self._zone_strings + + def setZoneStrings(self, zs): + self._zone_strings = zs + + def getLocalPatternChars(self): + return self._local_pattern_chars + + def setLocalPatternChars(self, chars): + self._local_pattern_chars = chars + + def getEras(self): + return self._eras + + def setEras(self, eras): + self._eras = eras + + +# ============================================================ +# SimpleDateFormat +# ============================================================ + +_JAVA_TO_PYTHON_FORMAT = { + "yyyy": "%Y", "yy": "%y", + "MM": "%m", + "dd": "%d", + "HH": "%H", + "mm": "%M", + "ss": "%S", + "SSS": "%f", + "a": "%p", + "EEE": "%a", "EEEE": "%A", + "D": "%j", + "zzz": "%Z", "z": "%Z", + "Z": "%z", +} + +class SimpleDateFormat(DateFormat): + def __init__(self, pattern, locale=None): + super().__init__() + self._pattern = pattern + self._locale = locale + self._dfs = DateFormatSymbols(locale) + self._lenient = False + + def applyPattern(self, pattern): + self._pattern = pattern + + def toPattern(self): + return self._pattern + + def toLocalizedPattern(self): + return self._pattern + + def applyLocalizedPattern(self, pattern): + self._pattern = pattern + + def _java_to_py(self, pattern): + py_pattern = pattern + sorted_keys = sorted(_JAVA_TO_PYTHON_FORMAT.keys(), key=lambda x: -len(x)) + for java_fmt in sorted_keys: + py_fmt = _JAVA_TO_PYTHON_FORMAT[java_fmt] + py_pattern = py_pattern.replace(java_fmt, py_fmt) + py_pattern = py_pattern.replace("'", "") + return py_pattern + + def _to_datetime(self, date): + if isinstance(date, _datetime.datetime): + return date + if isinstance(date, _datetime.date): + return _datetime.datetime.combine(date, _datetime.time()) + try: + ts = date.getTime() / 1000.0 + return _datetime.datetime.fromtimestamp(ts) + except AttributeError: + return _datetime.datetime.now() + + def format(self, date, to_append_to=None, pos=None): + py_pattern = self._java_to_py(self._pattern) + dt = self._to_datetime(date) + result = dt.strftime(py_pattern) + if to_append_to is not None: + sb = str(to_append_to) + idx = len(sb) + to_append_to.append(result) + if pos is not None: + pos.setBeginIndex(idx) + pos.setEndIndex(idx + len(result)) + return to_append_to + return result + + def parse(self, source, pos=None): + py_pattern = self._java_to_py(self._pattern) + try: + dt = _datetime.datetime.strptime(source, py_pattern) + if pos is not None: + pos.setIndex(len(source)) + return dt + except ValueError: + if pos is not None: + pos.setErrorIndex(0) + raise ParseException(f"Unparseable date: {source}", 0) + + def setLenient(self, lenient): + self._lenient = lenient + + def isLenient(self): + return self._lenient + + def setTimeZone(self, tz): + self._timezone = tz + + def getTimeZone(self): + return self._timezone + + def setCalendar(self, cal): + self._calendar = cal + + def getCalendar(self): + return self._calendar + + def setNumberFormat(self, nf): + self._number_format = nf + + def getNumberFormat(self): + return self._number_format + + def getDateFormatSymbols(self): + return self._dfs + + def setDateFormatSymbols(self, dfs): + self._dfs = dfs + + +# ============================================================ +# DecimalFormatSymbols +# ============================================================ + +class DecimalFormatSymbols(Stub): + def __init__(self, locale=None): + super().__init__() + self._decimal_sep = "." + self._grouping_sep = "," + self._pattern_sep = ";" + self._minus_sign = "-" + self._infinity = "\u221e" + self._nan = "NaN" + self._percent = "%" + self._per_mill = "\u2030" + self._zero_digit = "0" + self._digit = "#" + self._exponential_sep = "E" + self._monetary_decimal_sep = "." + self._intl_currency_symbol = "USD" + self._currency = "USD" + + def getDecimalSeparator(self): return self._decimal_sep + def setDecimalSeparator(self, c): self._decimal_sep = c + def getGroupingSeparator(self): return self._grouping_sep + def setGroupingSeparator(self, c): self._grouping_sep = c + def getPatternSeparator(self): return self._pattern_sep + def setPatternSeparator(self, c): self._pattern_sep = c + def getMinusSign(self): return self._minus_sign + def setMinusSign(self, c): self._minus_sign = c + def getInfinity(self): return self._infinity + def setInfinity(self, s): self._infinity = s + def getNaN(self): return self._nan + def setNaN(self, s): self._nan = s + def getPercent(self): return self._percent + def setPercent(self, c): self._percent = c + def getPerMill(self): return self._per_mill + def setPerMill(self, c): self._per_mill = c + def getZeroDigit(self): return self._zero_digit + def setZeroDigit(self, c): self._zero_digit = c + def getDigit(self): return self._digit + def setDigit(self, c): self._digit = c + def getExponentialSeparator(self): return self._exponential_sep + def setExponentialSeparator(self, s): self._exponential_sep = s + def getMonetaryDecimalSeparator(self): return self._monetary_decimal_sep + def setMonetaryDecimalSeparator(self, c): self._monetary_decimal_sep = c + def getInternationalCurrencySymbol(self): return self._intl_currency_symbol + def setInternationalCurrencySymbol(self, s): self._intl_currency_symbol = s + def getCurrency(self): return self._currency + def setCurrency(self, c): self._currency = c + + +# ============================================================ +# DecimalFormat +# ============================================================ + +class NumberFormat(Format): + def __init__(self): + super().__init__() + self._grouping_used = True + self._max_int_digits = 2000000000 + self._min_int_digits = 1 + self._max_frac_digits = 3 + self._min_frac_digits = 0 + self._parse_integer_only = False + self._rounding_mode = 4 + + @staticmethod + def getInstance(locale=None): + return DecimalFormat("#,##0.###") + + @staticmethod + def getNumberInstance(locale=None): + return DecimalFormat("#,##0.###") + + @staticmethod + def getCurrencyInstance(locale=None): + return DecimalFormat("#,##0.00") + + @staticmethod + def getPercentInstance(locale=None): + return DecimalFormat("#,##0%") + + @staticmethod + def getIntegerInstance(locale=None): + nf = DecimalFormat("#,##0") + nf.setMaximumFractionDigits(0) + return nf + + @staticmethod + def getCompactNumberInstance(locale=None, style=None): + return DecimalFormat("#,##0") + + def format(self, number, to_append_to=None, pos=None): + raise NotImplementedError + + def parse(self, source, pos=None): + raise NotImplementedError + + def isGroupingUsed(self): return self._grouping_used + def setGroupingUsed(self, v): self._grouping_used = v + def getMaximumIntegerDigits(self): return self._max_int_digits + def setMaximumIntegerDigits(self, v): self._max_int_digits = v + def getMinimumIntegerDigits(self): return self._min_int_digits + def setMinimumIntegerDigits(self, v): self._min_int_digits = v + def getMaximumFractionDigits(self): return self._max_frac_digits + def setMaximumFractionDigits(self, v): self._max_frac_digits = v + def getMinimumFractionDigits(self): return self._min_frac_digits + def setMinimumFractionDigits(self, v): self._min_frac_digits = v + def setParseIntegerOnly(self, v): self._parse_integer_only = v + def isParseIntegerOnly(self): return self._parse_integer_only + def setRoundingMode(self, v): self._rounding_mode = v + def getRoundingMode(self): return self._rounding_mode + def getCurrency(self): return "USD" + def setCurrency(self, c): pass + + @staticmethod + def getAvailableLocales(): + return list(_locale.locale_alias.keys()) + + +# ============================================================ +# ChoiceFormat +# ============================================================ + +class ChoiceFormat(NumberFormat): + def __init__(self, formats=None, limits=None): + super().__init__() + self._formats = formats if formats is not None else [] + self._limits = limits if limits is not None else [] + + def format(self, number, to_append_to=None, pos=None): + val = float(number) + idx = 0 + for i, lim in enumerate(self._limits): + if val >= lim: + idx = i + result = str(self._formats[idx]) if idx < len(self._formats) else "" + if to_append_to is not None: + sb = str(to_append_to) + idx2 = len(sb) + to_append_to.append(result) + if pos is not None: + pos.setBeginIndex(idx2) + pos.setEndIndex(idx2 + len(result)) + return to_append_to + return result + + def parse(self, source, pos=None): + raise NotImplementedError + + @staticmethod + def nextDouble(d, upwards=True): + if upwards: + return d + _py_math.ulp(d) + return d - _py_math.ulp(d) + + @staticmethod + def previousDouble(d): + return ChoiceFormat.nextDouble(d, False) + + def getLimits(self): + return self._limits + + def getFormats(self): + return self._formats + + +# ============================================================ +# MessageFormat +# ============================================================ +# NumberFormat +# ============================================================ + +# ============================================================ +# DecimalFormat +# ============================================================ + +class DecimalFormat(NumberFormat): + def __init__(self, pattern=None, symbols=None): + super().__init__() + self._pattern = pattern or "#,##0.###" + self._symbols = symbols or DecimalFormatSymbols() + self._decimal_sep_always = False + self._grouping_size = 3 + self._grouping_used = True + self._max_int_digits = 2000000000 + self._min_int_digits = 1 + self._max_frac_digits = 2000000000 + self._min_frac_digits = 0 + self._neg_prefix = "-" + self._neg_suffix = "" + self._pos_prefix = "" + self._pos_suffix = "" + self._multiplier = 1 + self._rounding_mode = 4 + self._parse_bigdecimal = False + + def applyPattern(self, pattern): + self._pattern = pattern + + def toPattern(self): + return self._pattern + + def toLocalizedPattern(self): + return self._pattern + + def applyLocalizedPattern(self, pattern): + self._pattern = pattern + + def format(self, number, to_append_to=None, pos=None): + if isinstance(number, (int, float)): + val = number * self._multiplier + elif isinstance(number, _Decimal): + val = float(str(number)) * self._multiplier + else: + val = float(str(number)) * self._multiplier + if self._grouping_used and self._grouping_size > 0: + result = f"{val:,.{self._max_frac_digits}f}" + else: + fmt = "{{:.{}f}}".format(self._max_frac_digits) + result = fmt.format(val) + prefix = self._pos_prefix if val >= 0 else self._neg_prefix + suffix = self._pos_suffix if val >= 0 else self._neg_suffix + result = prefix + result + suffix + if to_append_to is not None: + sb = str(to_append_to) + idx = len(sb) + to_append_to.append(result) + if pos is not None: + pos.setBeginIndex(idx) + pos.setEndIndex(idx + len(result)) + return to_append_to + return result + + def parse(self, source, pos=None): + text = source.strip() + try: + val = float(text) + if pos is not None: + pos.setIndex(len(source)) + if self._parse_bigdecimal: + return _Decimal(str(val)) + return val + except ValueError: + if pos is not None: + pos.setErrorIndex(0) + raise Exception(f"Unparseable: {source}") + + def setDecimalSeparatorAlwaysShown(self, v): self._decimal_sep_always = v + def getDecimalSeparatorAlwaysShown(self): return self._decimal_sep_always + def setGroupingSize(self, v): self._grouping_size = v + def getGroupingSize(self): return self._grouping_size + def setGroupingUsed(self, v): self._grouping_used = v + def isGroupingUsed(self): return self._grouping_used + def setMaximumIntegerDigits(self, v): self._max_int_digits = v + def getMaximumIntegerDigits(self): return self._max_int_digits + def setMinimumIntegerDigits(self, v): self._min_int_digits = v + def getMinimumIntegerDigits(self): return self._min_int_digits + def setMaximumFractionDigits(self, v): self._max_frac_digits = v + def getMaximumFractionDigits(self): return self._max_frac_digits + def setMinimumFractionDigits(self, v): self._min_frac_digits = v + def getMinimumFractionDigits(self): return self._min_frac_digits + def setNegativePrefix(self, v): self._neg_prefix = v + def getNegativePrefix(self): return self._neg_prefix + def setNegativeSuffix(self, v): self._neg_suffix = v + def getNegativeSuffix(self): return self._neg_suffix + def setPositivePrefix(self, v): self._pos_prefix = v + def getPositivePrefix(self): return self._pos_prefix + def setPositiveSuffix(self, v): self._pos_suffix = v + def getPositiveSuffix(self): return self._pos_suffix + def setDecimalFormatSymbols(self, v): self._symbols = v + def getDecimalFormatSymbols(self): return self._symbols + def setParseBigDecimal(self, v): self._parse_bigdecimal = v + def isParseBigDecimal(self): return self._parse_bigdecimal + def setMultiplier(self, v): self._multiplier = v + def getMultiplier(self): return self._multiplier + def setRoundingMode(self, v): self._rounding_mode = v + def getRoundingMode(self): return self._rounding_mode + + +# ============================================================ +# MessageFormat +# ============================================================ + +_MSG_PATTERN_RE = _re.compile(r"\{(\d+)(?:,(\w+)(?:,(.+))?)?\}") + +class MessageFormat(Format): + def __init__(self, pattern, locale=None): + super().__init__() + self._pattern = pattern + self._locale = locale + self._formats = {} + + def applyPattern(self, pattern): + self._pattern = pattern + + def toPattern(self): + return self._pattern + + def _format_impl(self, objects): + def replacer(m): + idx = int(m.group(1)) + if idx < len(objects): + return str(objects[idx]) + return m.group(0) + return _MSG_PATTERN_RE.sub(replacer, self._pattern) + + def format(self, objects, to_append_to=None, pos=None): + result = self._format_impl(objects) + if to_append_to is not None: + sb = str(to_append_to) + idx = len(sb) + to_append_to.append(result) + if pos is not None: + pos.setBeginIndex(idx) + pos.setEndIndex(idx + len(result)) + return to_append_to + return result + + @staticmethod + def format_static(pattern, *args): + mf = MessageFormat(pattern) + return mf.format(args) + + def parse(self, source, pos=None): + parts = _MSG_PATTERN_RE.split(self._pattern) + result = [] + idx = 0 + for i, part in enumerate(parts): + if i % 2 == 0: + if not source[idx:].startswith(part): + if pos is not None: + pos.setErrorIndex(idx) + return result + idx += len(part) + else: + arg_idx = int(part) + remaining = source[idx:] + next_part = parts[i + 1] if i + 1 < len(parts) else "" + end = remaining.find(next_part) if next_part else len(remaining) + if end < 0: + end = len(remaining) + result.append(remaining[:end]) + idx += end + if pos is not None: + pos.setIndex(idx) + return result + + def setLocale(self, locale): + self._locale = locale + + def getLocale(self): + return self._locale + + def setFormats(self, formats): + self._formats = formats + + def getFormats(self): + return self._formats + + def setFormat(self, idx, fmt): + self._formats[idx] = fmt + + def getFormat(self, idx): + return self._formats.get(idx) + + +# ============================================================ +# CharacterIterator +# ============================================================ + +class CharacterIterator(Stub): + DONE = chr(0xFFFF) + + def first(self): + raise NotImplementedError + + def last(self): + raise NotImplementedError + + def current(self): + raise NotImplementedError + + def next(self): + raise NotImplementedError + + def previous(self): + raise NotImplementedError + + def setIndex(self, position): + raise NotImplementedError + + def getBeginIndex(self): + raise NotImplementedError + + def getEndIndex(self): + raise NotImplementedError + + def getIndex(self): + raise NotImplementedError + + def clone(self): + raise NotImplementedError + + +# ============================================================ +# StringCharacterIterator +# ============================================================ + +class StringCharacterIterator(CharacterIterator): + def __init__(self, text): + super().__init__() + self._text = text + self._begin = 0 + self._end = len(text) + self._pos = 0 + + def first(self): + self._pos = self._begin + return self.current() + + def last(self): + self._pos = self._end - 1 if self._end > self._begin else self._begin + return self.current() + + def current(self): + if self._pos >= self._begin and self._pos < self._end: + return self._text[self._pos] + return CharacterIterator.DONE + + def next(self): + self._pos += 1 + if self._pos >= self._end: + self._pos = self._end + return CharacterIterator.DONE + return self._text[self._pos] + + def previous(self): + self._pos -= 1 + if self._pos < self._begin: + self._pos = self._begin + return CharacterIterator.DONE + return self._text[self._pos] + + def setIndex(self, pos): + self._pos = max(self._begin, min(pos, self._end)) + return self.current() + + def getBeginIndex(self): + return self._begin + + def getEndIndex(self): + return self._end + + def getIndex(self): + return self._pos + + def clone(self): + return StringCharacterIterator(self._text) + + +# ============================================================ +# BreakIterator +# ============================================================ + +class BreakIterator(Stub): + DONE = -1 + + @staticmethod + def getWordInstance(locale=None): + return BreakIterator() + + @staticmethod + def getLineInstance(locale=None): + return BreakIterator() + + @staticmethod + def getCharacterInstance(locale=None): + return BreakIterator() + + @staticmethod + def getSentenceInstance(locale=None): + return BreakIterator() + + def setText(self, text): + self._text = text + + def first(self): + return 0 + + def last(self): + return len(self._text) if hasattr(self, "_text") else 0 + + def next(self, n=1): + return BreakIterator.DONE + + def previous(self): + return BreakIterator.DONE + + def current(self): + return 0 + + def following(self, offset): + return BreakIterator.DONE + + def preceding(self, offset): + return BreakIterator.DONE + + def isBoundary(self, offset): + return True + + +# ============================================================ +# Collator / CollationKey / RuleBasedCollator +# ============================================================ + +class CollationKey(Stub): + def __init__(self, source, key): + super().__init__() + self._source = source + self._key = key + + def compareTo(self, other): + if isinstance(other, CollationKey): + return (self._key > other._key) - (self._key < other._key) + return -1 + + def getSourceString(self): + return self._source + + def toByteArray(self): + return self._key.encode("utf-8") + + +class Collator(Stub): + PRIMARY = 0 + SECONDARY = 1 + TERTIARY = 2 + IDENTICAL = 3 + NO_DECOMPOSITION = 0 + CANONICAL_DECOMPOSITION = 1 + FULL_DECOMPOSITION = 2 + + @staticmethod + def getInstance(locale=None): + return RuleBasedCollator() + + def compare(self, source, target): + return (source > target) - (source < target) + + def getCollationKey(self, source): + return CollationKey(source, source) + + def setStrength(self, strength): + self._strength = strength + + def getStrength(self): + return getattr(self, "_strength", Collator.TERTIARY) + + def setDecomposition(self, decomposition): + self._decomposition = decomposition + + def getDecomposition(self): + return getattr(self, "_decomposition", Collator.NO_DECOMPOSITION) + + +class RuleBasedCollator(Collator): + def __init__(self, rules=None): + super().__init__() + self._rules = rules or "" + + +# ============================================================ +# Normalizer +# ============================================================ + +class Normalizer(Stub): + class Form(Stub): + NFC = "NFC" + NFD = "NFD" + NFKC = "NFKC" + NFKD = "NFKD" + + @staticmethod + def normalize(src, form): + import unicodedata + form_map = { + Normalizer.Form.NFC: "NFC", + Normalizer.Form.NFD: "NFD", + Normalizer.Form.NFKC: "NFKC", + Normalizer.Form.NFKD: "NFKD", + } + return unicodedata.normalize(form_map.get(form, "NFC"), src) + + @staticmethod + def isNormalized(src, form): + return Normalizer.normalize(src, form) == src + + +# ============================================================ +# Annotation / AttributedCharacterIterator / AttributedString +# ============================================================ + +class Annotation(Stub): + def __init__(self, value): + super().__init__() + self._value = value + + def getValue(self): + return self._value + + def toString(self): + return str(self._value) + + +class AttributedCharacterIterator(CharacterIterator): + pass + + +class AttributedString(Stub): + def __init__(self, text, attributes=None): + super().__init__() + self._text = text + + def getIterator(self): + return StringCharacterIterator(self._text) + + +# ============================================================ +# Bidi +# ============================================================ + +class Bidi(Stub): + DIRECTION_LEFT_TO_RIGHT = 0 + DIRECTION_RIGHT_TO_LEFT = 1 + DIRECTION_DEFAULT_LEFT_TO_RIGHT = -2 + DIRECTION_DEFAULT_RIGHT_TO_LEFT = -1 + + def __init__(self, text, flags=0): + super().__init__() + self._text = text + self._base_level = 0 if flags >= 0 else 1 + + def isLeftToRight(self): + return self._base_level == 0 + + def isRightToLeft(self): + return self._base_level == 1 + + def getLength(self): + return len(self._text) + + def getLevelAt(self, offset): + return self._base_level + + def getRunCount(self): + return 1 + + def getRunLevel(self, run): + return self._base_level + + def getRunLimit(self, run): + return len(self._text) + + def getRunStart(self, run): + return 0 + + def requiresBidi(self, text): + for ch in text: + if ord(ch) > 0x590: + return True + return False + + @staticmethod + def reorderVisually(levels, objects, index, count): + pass + + @staticmethod + def baseIsLeftToRight(): + return True \ No newline at end of file diff --git a/android_pyapi/java/time.py b/android_pyapi/java/time.py new file mode 100644 index 0000000..c68f1cc --- /dev/null +++ b/android_pyapi/java/time.py @@ -0,0 +1,1619 @@ +""" +Java time standard library APIs implemented in Python. +java.time package - LocalDate, LocalTime, LocalDateTime, ZonedDateTime, Instant, Duration, Period, etc. +""" + +from .._core import Stub +from .lang import RuntimeException, Comparable +import datetime as _dt +import re as _re +import math as _py_math + +IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {}) + +__all__ = [ + "LocalDate", "LocalTime", "LocalDateTime", + "ZonedDateTime", "OffsetDateTime", "OffsetTime", + "Instant", "Duration", "Period", + "ZoneId", "ZoneOffset", "ZoneRules", + "DayOfWeek", "Month", "Year", "YearMonth", "MonthDay", + "Clock", + "DateTimeFormatter", "DateTimeFormatterBuilder", + "TemporalAdjusters", + "Temporal", "TemporalAccessor", "TemporalUnit", + "ChronoUnit", "ChronoField", +] + + +# ============================================================ +# TemporalAccessor / Temporal / TemporalUnit (base interfaces) +# ============================================================ + +class TemporalAccessor(Stub): + def get(self, field): + raise NotImplementedError + + def getLong(self, field): + raise NotImplementedError + + def isSupported(self, field): + raise NotImplementedError + + def range(self, field): + raise NotImplementedError + + +class Temporal(TemporalAccessor): + def plus(self, amount, unit=None): + raise NotImplementedError + + def minus(self, amount, unit=None): + raise NotImplementedError + + def with_(self, field, new_value): + raise NotImplementedError + + def until(self, end_exclusive, unit): + raise NotImplementedError + + +class TemporalUnit(Stub): + def getDuration(self): + raise NotImplementedError + + def isDurationEstimated(self): + raise NotImplementedError + + def isDateBased(self): + raise NotImplementedError + + def isTimeBased(self): + raise NotImplementedError + + def between(self, temporal1, temporal2): + raise NotImplementedError + + +# ============================================================ +# ChronoField +# ============================================================ + +class ChronoField(Stub): + NANO_OF_SECOND = "NanoOfSecond" + NANO_OF_DAY = "NanoOfDay" + MICRO_OF_SECOND = "MicroOfSecond" + MICRO_OF_DAY = "MicroOfDay" + MILLI_OF_SECOND = "MilliOfSecond" + MILLI_OF_DAY = "MilliOfDay" + SECOND_OF_MINUTE = "SecondOfMinute" + SECOND_OF_DAY = "SecondOfDay" + MINUTE_OF_HOUR = "MinuteOfHour" + MINUTE_OF_DAY = "MinuteOfDay" + HOUR_OF_AMPM = "HourOfAmPm" + CLOCK_HOUR_OF_AMPM = "ClockHourOfAmPm" + HOUR_OF_DAY = "HourOfDay" + CLOCK_HOUR_OF_DAY = "ClockHourOfDay" + AMPM_OF_DAY = "AmPmOfDay" + DAY_OF_WEEK = "DayOfWeek" + ALIGNED_DAY_OF_WEEK_IN_MONTH = "AlignedDayOfWeekInMonth" + ALIGNED_DAY_OF_WEEK_IN_YEAR = "AlignedDayOfWeekInYear" + DAY_OF_MONTH = "DayOfMonth" + DAY_OF_YEAR = "DayOfYear" + EPOCH_DAY = "EpochDay" + ALIGNED_WEEK_OF_MONTH = "AlignedWeekOfMonth" + ALIGNED_WEEK_OF_YEAR = "AlignedWeekOfYear" + MONTH_OF_YEAR = "MonthOfYear" + PROLEPTIC_MONTH = "ProlepticMonth" + YEAR_OF_ERA = "YearOfEra" + YEAR = "Year" + ERA = "Era" + INSTANT_SECONDS = "InstantSeconds" + OFFSET_SECONDS = "OffsetSeconds" + + def __init__(self, name): + super().__init__() + self._name = name + + def toString(self): + return self._name + + def getDisplayName(self, locale=None): + return self._name + + +# ============================================================ +# ChronoUnit +# ============================================================ + +class ChronoUnit(Stub): + NANOS = None + MICROS = None + MILLIS = None + SECONDS = None + MINUTES = None + HOURS = None + HALF_DAYS = None + DAYS = None + WEEKS = None + MONTHS = None + YEARS = None + DECADES = None + CENTURIES = None + MILLENNIA = None + ERAS = None + FOREVER = None + + def __init__(self, name, duration, estimated=False): + self._name = name + self._duration = duration + self._estimated = estimated + + def toString(self): + return self._name + + def getDuration(self): + return self._duration + + def isDurationEstimated(self): + return self._estimated + + def isDateBased(self): + return self in (ChronoUnit.WEEKS, ChronoUnit.MONTHS, ChronoUnit.YEARS, + ChronoUnit.DECADES, ChronoUnit.CENTURIES, + ChronoUnit.MILLENNIA, ChronoUnit.ERAS) + + def isTimeBased(self): + return self in (ChronoUnit.NANOS, ChronoUnit.MICROS, ChronoUnit.MILLIS, + ChronoUnit.SECONDS, ChronoUnit.MINUTES, ChronoUnit.HOURS, + ChronoUnit.HALF_DAYS) + + def between(self, temporal1, temporal2): + return temporal1.until(temporal2, self) + + @staticmethod + def from_(temporal): + if isinstance(temporal, ChronoUnit): + return temporal + raise Exception(f"Cannot convert to ChronoUnit: {temporal}") + + +# ChronoUnit 常量在 Duration 定义之后初始化(见下方) + + +# ============================================================ +# DayOfWeek +# ============================================================ + +class DayOfWeek(Stub): + MONDAY = None + TUESDAY = None + WEDNESDAY = None + THURSDAY = None + FRIDAY = None + SATURDAY = None + SUNDAY = None + + def __init__(self, value, name): + self._value = value + self._name = name + + def getValue(self): + return self._value + + def getDisplayName(self, style, locale=None): + return self._name + + def plus(self, days): + idx = ((self._value - 1 + days) % 7) + 1 + return DayOfWeek._from_value(idx) + + def minus(self, days): + return self.plus(-days) + + @staticmethod + def _from_value(v): + mapping = {1: DayOfWeek.MONDAY, 2: DayOfWeek.TUESDAY, 3: DayOfWeek.WEDNESDAY, + 4: DayOfWeek.THURSDAY, 5: DayOfWeek.FRIDAY, 6: DayOfWeek.SATURDAY, + 7: DayOfWeek.SUNDAY} + return mapping.get(v) + + def toString(self): + return self._name + + def compareTo(self, other): + if isinstance(other, DayOfWeek): + return self._value - other._value + return -1 + + +DayOfWeek.MONDAY = DayOfWeek(1, "MONDAY") +DayOfWeek.TUESDAY = DayOfWeek(2, "TUESDAY") +DayOfWeek.WEDNESDAY = DayOfWeek(3, "WEDNESDAY") +DayOfWeek.THURSDAY = DayOfWeek(4, "THURSDAY") +DayOfWeek.FRIDAY = DayOfWeek(5, "FRIDAY") +DayOfWeek.SATURDAY = DayOfWeek(6, "SATURDAY") +DayOfWeek.SUNDAY = DayOfWeek(7, "SUNDAY") + + +# ============================================================ +# Month +# ============================================================ + +class Month(Stub): + JANUARY = None + FEBRUARY = None + MARCH = None + APRIL = None + MAY = None + JUNE = None + JULY = None + AUGUST = None + SEPTEMBER = None + OCTOBER = None + NOVEMBER = None + DECEMBER = None + + def __init__(self, value, name): + self._value = value + self._name = name + + def getValue(self): + return self._value + + def getDisplayName(self, style, locale=None): + return self._name + + def plus(self, months): + idx = ((self._value - 1 + months) % 12) + 1 + return Month._from_value(idx) + + def minus(self, months): + return self.plus(-months) + + def length(self, leap_year=False): + if self._value == 2: + return 29 if leap_year else 28 + if self._value in (4, 6, 9, 11): + return 30 + return 31 + + def minLength(self): + return 29 if self._value == 2 else (30 if self._value in (4, 6, 9, 11) else 31) + + def maxLength(self): + return 29 if self._value == 2 else (30 if self._value in (4, 6, 9, 11) else 31) + + def firstDayOfYear(self, leap_year=False): + days = 0 + for m in range(1, self._value): + days += Month._from_value(m).length(leap_year) + return days + 1 + + def firstMonthOfQuarter(self): + q = (self._value - 1) // 3 + return Month._from_value(q * 3 + 1) + + @staticmethod + def _from_value(v): + values = [None, Month.JANUARY, Month.FEBRUARY, Month.MARCH, Month.APRIL, + Month.MAY, Month.JUNE, Month.JULY, Month.AUGUST, + Month.SEPTEMBER, Month.OCTOBER, Month.NOVEMBER, Month.DECEMBER] + return values[v] if 0 < v <= 12 else None + + def toString(self): + return self._name + + def compareTo(self, other): + if isinstance(other, Month): + return self._value - other._value + return -1 + + +Month.JANUARY = Month(1, "JANUARY") +Month.FEBRUARY = Month(2, "FEBRUARY") +Month.MARCH = Month(3, "MARCH") +Month.APRIL = Month(4, "APRIL") +Month.MAY = Month(5, "MAY") +Month.JUNE = Month(6, "JUNE") +Month.JULY = Month(7, "JULY") +Month.AUGUST = Month(8, "AUGUST") +Month.SEPTEMBER = Month(9, "SEPTEMBER") +Month.OCTOBER = Month(10, "OCTOBER") +Month.NOVEMBER = Month(11, "NOVEMBER") +Month.DECEMBER = Month(12, "DECEMBER") + + +# ============================================================ +# ZoneId / ZoneOffset / ZoneRules +# ============================================================ + +class ZoneId(Stub): + SHORT_IDS = { + "ACT": "Australia/Darwin", + "AET": "Australia/Sydney", + "AGT": "America/Argentina/Buenos_Aires", + "ART": "Africa/Cairo", + "AST": "America/Anchorage", + "BET": "America/Sao_Paulo", + "BST": "Asia/Dhaka", + "CAT": "Africa/Harare", + "CNT": "America/St_Johns", + "CST": "America/Chicago", + "CTT": "Asia/Shanghai", + "EAT": "Africa/Addis_Ababa", + "ECT": "Europe/Paris", + "EST": "America/New_York", + "HST": "Pacific/Honolulu", + "IET": "America/Indianapolis", + "IST": "Asia/Kolkata", + "JST": "Asia/Tokyo", + "MIT": "Pacific/Apia", + "MST": "America/Denver", + "NET": "Asia/Yerevan", + "NST": "Pacific/Auckland", + "PLT": "Asia/Karachi", + "PNT": "America/Phoenix", + "PRT": "America/Puerto_Rico", + "PST": "America/Los_Angeles", + "SST": "Pacific/Guadalcanal", + "VST": "Asia/Ho_Chi_Minh", + } + + _cache = {} + + def __init__(self, zone_id): + super().__init__() + self._id = zone_id + + @staticmethod + def systemDefault(): + import time as _t + idx = 1 if _t.daylight else 0 + tz_name = _t.tzname[idx] if idx < len(_t.tzname) and _t.tzname[idx] else _t.tzname[0] + return ZoneId.of(tz_name) if tz_name else ZoneId("UTC") + + @staticmethod + def of(zone_id): + if zone_id in ZoneId.SHORT_IDS: + zone_id = ZoneId.SHORT_IDS[zone_id] + if zone_id not in ZoneId._cache: + ZoneId._cache[zone_id] = ZoneId(zone_id) + return ZoneId._cache[zone_id] + + @staticmethod + def getAvailableZoneIds(): + import time as _t + return set(_t.tzname) + + def getDisplayName(self, style, locale=None): + return self._id + + def getRules(self): + return ZoneRules() + + def toString(self): + return self._id + + def equals(self, other): + if isinstance(other, ZoneId): + return self._id == other._id + return False + + def hashCode(self): + return hash(self._id) + + +class ZoneOffset(ZoneId): + MAX = None + MIN = None + UTC = None + + _cache = {} + + def __init__(self, total_seconds): + hours = total_seconds // 3600 + mins = (abs(total_seconds) % 3600) // 60 + sign = "+" if total_seconds >= 0 else "-" + if mins == 0: + zone_id = f"{sign}{abs(hours):02d}:00" + else: + zone_id = f"{sign}{abs(hours):02d}:{mins:02d}" + super().__init__(zone_id) + self._total_seconds = total_seconds + + @staticmethod + def of(offset_id): + if offset_id in ZoneOffset._cache: + return ZoneOffset._cache[offset_id] + if offset_id == "Z" or offset_id == "UTC" or offset_id == "GMT": + z = ZoneOffset(0) + ZoneOffset._cache[offset_id] = z + return z + sign = 1 if offset_id[0] == "+" else -1 + parts = offset_id[1:].split(":") + h = int(parts[0]) + m = int(parts[1]) if len(parts) > 1 else 0 + total = sign * (h * 3600 + m * 60) + z = ZoneOffset(total) + ZoneOffset._cache[offset_id] = z + return z + + @staticmethod + def ofHours(hours): + return ZoneOffset(hours * 3600) + + @staticmethod + def ofHoursMinutes(hours, minutes): + sign = 1 if hours >= 0 else -1 + return ZoneOffset(sign * (abs(hours) * 3600 + minutes * 60)) + + @staticmethod + def ofTotalSeconds(total_seconds): + if total_seconds in ZoneOffset._cache: + return ZoneOffset._cache[total_seconds] + z = ZoneOffset(total_seconds) + ZoneOffset._cache[total_seconds] = z + return z + + def getTotalSeconds(self): + return self._total_seconds + + def toString(self): + if self._total_seconds == 0: + return "Z" + return super().toString() + + def equals(self, other): + if isinstance(other, ZoneOffset): + return self._total_seconds == other._total_seconds + return False + + def hashCode(self): + return self._total_seconds + + +ZoneOffset.MAX = ZoneOffset(18 * 3600) +ZoneOffset.MIN = ZoneOffset(-18 * 3600) +ZoneOffset.UTC = ZoneOffset(0) + + +class ZoneRules(Stub): + def isFixedOffset(self): + return True + + def getOffset(self, instant): + return ZoneOffset.UTC + + def getDaylightSavings(self, instant): + return Duration.ZERO + + def isDaylightSavings(self, instant): + return False + + def isValidOffset(self, local_date_time, offset): + return True + + def nextTransition(self, instant): + return None + + def previousTransition(self, instant): + return None + + def getTransitions(self): + return [] + + def getTransitionRules(self): + return [] + + +# ============================================================ +# Instant +# ============================================================ + +class Instant(Temporal, Comparable): + EPOCH = None + + def __init__(self, epoch_second, nano=0): + super().__init__() + self._epoch_second = int(epoch_second) + self._nano = int(nano) + + @staticmethod + def now(): + import time as _t + t = _t.time() + sec = int(t) + nano = int((t - sec) * 1_000_000_000) + return Instant(sec, nano) + + @staticmethod + def ofEpochMilli(epoch_milli): + return Instant(epoch_milli // 1000, (epoch_milli % 1000) * 1_000_000) + + @staticmethod + def ofEpochSecond(epoch_second, nano_adjustment=0): + return Instant(epoch_second + nano_adjustment // 1_000_000_000, + nano_adjustment % 1_000_000_000) + + def getEpochSecond(self): + return self._epoch_second + + def getNano(self): + return self._nano + + def toEpochMilli(self): + return self._epoch_second * 1000 + self._nano // 1_000_000 + + def plus(self, seconds, nano=0): + return Instant(self._epoch_second + seconds, self._nano + nano) + + def minus(self, seconds, nano=0): + return self.plus(-seconds, -nano) + + def isBefore(self, other): + if isinstance(other, Instant): + return (self._epoch_second, self._nano) < (other._epoch_second, other._nano) + return False + + def isAfter(self, other): + if isinstance(other, Instant): + return (self._epoch_second, self._nano) > (other._epoch_second, other._nano) + return False + + def compareTo(self, other): + if isinstance(other, Instant): + diff = self._epoch_second - other._epoch_second + if diff != 0: + return -1 if diff < 0 else 1 + diff = self._nano - other._nano + return -1 if diff < 0 else (1 if diff > 0 else 0) + return -1 + + def equals(self, other): + if isinstance(other, Instant): + return self._epoch_second == other._epoch_second and self._nano == other._nano + return False + + def hashCode(self): + return hash((self._epoch_second, self._nano)) + + def toString(self): + return f"Instant({self._epoch_second},{self._nano})" + + +Instant.EPOCH = Instant(0, 0) + + +# ============================================================ +# Duration +# ============================================================ + +class Duration(Temporal, Comparable): + ZERO = None + + def __init__(self, seconds, nanos=0): + super().__init__() + self._seconds = int(seconds) + self._nanos = int(nanos) + + @staticmethod + def ofDays(days): + return Duration(days * 86400) + + @staticmethod + def ofHours(hours): + return Duration(hours * 3600) + + @staticmethod + def ofMinutes(minutes): + return Duration(minutes * 60) + + @staticmethod + def ofSeconds(seconds): + return Duration(seconds) + + @staticmethod + def ofMillis(millis): + return Duration(millis // 1000, (millis % 1000) * 1_000_000) + + @staticmethod + def ofNanos(nanos): + return Duration(nanos // 1_000_000_000, nanos % 1_000_000_000) + + @staticmethod + def between(start, end): + if isinstance(start, Instant) and isinstance(end, Instant): + return Duration(end.getEpochSecond() - start.getEpochSecond(), + end.getNano() - start.getNano()) + return Duration.ZERO + + def getSeconds(self): + return self._seconds + + def getNano(self): + return self._nanos + + def toDays(self): + return self._seconds // 86400 + + def toHours(self): + return self._seconds // 3600 + + def toMinutes(self): + return self._seconds // 60 + + def toSeconds(self): + return self._seconds + + def toMillis(self): + return self._seconds * 1000 + self._nanos // 1_000_000 + + def toNanos(self): + return self._seconds * 1_000_000_000 + self._nanos + + def plus(self, duration): + if isinstance(duration, Duration): + return Duration(self._seconds + duration._seconds, + self._nanos + duration._nanos) + return self + + def minus(self, duration): + if isinstance(duration, Duration): + return Duration(self._seconds - duration._seconds, + self._nanos - duration._nanos) + return self + + def isNegative(self): + return self._seconds < 0 or (self._seconds == 0 and self._nanos < 0) + + def isZero(self): + return self._seconds == 0 and self._nanos == 0 + + def compareTo(self, other): + if isinstance(other, Duration): + diff = self._seconds - other._seconds + if diff != 0: + return -1 if diff < 0 else 1 + diff = self._nanos - other._nanos + return -1 if diff < 0 else (1 if diff > 0 else 0) + return -1 + + def equals(self, other): + if isinstance(other, Duration): + return self._seconds == other._seconds and self._nanos == other._nanos + return False + + def toString(self): + return f"PT{self._seconds}S" + + def hashCode(self): + return hash((self._seconds, self._nanos)) + + +Duration.ZERO = Duration(0, 0) + + +# === 延迟初始化 ChronoUnit 常量(必须在 Duration 定义之后) === +ChronoUnit.NANOS = ChronoUnit("Nanos", Duration.ofNanos(1)) +ChronoUnit.MICROS = ChronoUnit("Micros", Duration.ofNanos(1000)) +ChronoUnit.MILLIS = ChronoUnit("Millis", Duration.ofNanos(1_000_000)) +ChronoUnit.SECONDS = ChronoUnit("Seconds", Duration.ofSeconds(1)) +ChronoUnit.MINUTES = ChronoUnit("Minutes", Duration.ofMinutes(1)) +ChronoUnit.HOURS = ChronoUnit("Hours", Duration.ofHours(1)) +ChronoUnit.HALF_DAYS = ChronoUnit("HalfDays", Duration.ofHours(12)) +ChronoUnit.DAYS = ChronoUnit("Days", Duration.ofDays(1)) +ChronoUnit.WEEKS = ChronoUnit("Weeks", Duration.ofDays(7)) +ChronoUnit.MONTHS = ChronoUnit("Months", Duration.ofDays(30), estimated=True) +ChronoUnit.YEARS = ChronoUnit("Years", Duration.ofDays(365), estimated=True) +ChronoUnit.DECADES = ChronoUnit("Decades", Duration.ofDays(365 * 10), estimated=True) +ChronoUnit.CENTURIES = ChronoUnit("Centuries", Duration.ofDays(365 * 100), estimated=True) +ChronoUnit.MILLENNIA = ChronoUnit("Millennia", Duration.ofDays(365 * 1000), estimated=True) +ChronoUnit.ERAS = ChronoUnit("Eras", Duration.ofDays(365 * 1_000_000_000), estimated=True) +ChronoUnit.FOREVER = ChronoUnit("Forever", Duration.ofSeconds(1_000_000_000 * 86400 * 365), estimated=True) + + +# ============================================================ +# Period +# ============================================================ + +class Period(Stub): + ZERO = None + + def __init__(self, years, months, days): + super().__init__() + self._years = years + self._months = months + self._days = days + + @staticmethod + def ofDays(days): + return Period(0, 0, days) + + @staticmethod + def ofWeeks(weeks): + return Period(0, 0, weeks * 7) + + @staticmethod + def ofMonths(months): + return Period(0, months, 0) + + @staticmethod + def ofYears(years): + return Period(years, 0, 0) + + @staticmethod + def between(start, end): + if isinstance(start, LocalDate) and isinstance(end, LocalDate): + return Period(end._year - start._year, + end._month - start._month, + end._day - start._day) + return Period.ZERO + + def getYears(self): + return self._years + + def getMonths(self): + return self._months + + def getDays(self): + return self._days + + def plus(self, period): + if isinstance(period, Period): + return Period(self._years + period._years, + self._months + period._months, + self._days + period._days) + return self + + def minus(self, period): + if isinstance(period, Period): + return Period(self._years - period._years, + self._months - period._months, + self._days - period._days) + return self + + def isZero(self): + return self._years == 0 and self._months == 0 and self._days == 0 + + def isNegative(self): + return self._years < 0 or self._months < 0 or self._days < 0 + + def equals(self, other): + if isinstance(other, Period): + return (self._years == other._years and + self._months == other._months and + self._days == other._days) + return False + + def toString(self): + return f"P{self._years}Y{self._months}M{self._days}D" + + def hashCode(self): + return hash((self._years, self._months, self._days)) + + +Period.ZERO = Period(0, 0, 0) + + +# ============================================================ +# LocalDate +# ============================================================ + +class LocalDate(Temporal, Comparable): + MIN = None + MAX = None + + def __init__(self, year, month, day): + super().__init__() + self._year = year + self._month = month + self._day = day + + @staticmethod + def now(zone=None): + today = _dt.date.today() + return LocalDate(today.year, today.month, today.day) + + @staticmethod + def of(year, month, dayOfMonth): + if isinstance(month, Month): + month = month.getValue() + return LocalDate(year, month, dayOfMonth) + + @staticmethod + def parse(text, formatter=None): + if formatter is None: + parts = text.split("-") + return LocalDate(int(parts[0]), int(parts[1]), int(parts[2])) + return formatter.parse(text, LocalDate) + + def getYear(self): + return self._year + + def getMonth(self): + return Month._from_value(self._month) + + def getMonthValue(self): + return self._month + + def getDayOfMonth(self): + return self._day + + def getDayOfWeek(self): + import time as _t + import datetime as _dt2 + d = _dt2.date(self._year, self._month, self._day) + return DayOfWeek._from_value(d.isoweekday()) + + def getDayOfYear(self): + d = _dt.date(self._year, self._month, self._day) + return d.timetuple().tm_yday + + def getEra(self): + return "CE" if self._year > 0 else "BCE" + + def isLeapYear(self): + return _dt.date(self._year, 3, 1) - _dt.date(self._year, 2, 28) == _dt.timedelta(days=1) + + def lengthOfMonth(self): + m = self._month + if m == 2: + return 29 if self.isLeapYear() else 28 + if m in (4, 6, 9, 11): + return 30 + return 31 + + def lengthOfYear(self): + return 366 if self.isLeapYear() else 365 + + def toEpochDay(self): + d = _dt.date(self._year, self._month, self._day) + return d.toordinal() + + def plusDays(self, days): + d = _dt.date(self._year, self._month, self._day) + _dt.timedelta(days=days) + return LocalDate(d.year, d.month, d.day) + + def plusWeeks(self, weeks): + return self.plusDays(weeks * 7) + + def plusMonths(self, months): + total = self._year * 12 + (self._month - 1) + months + y = total // 12 + m = (total % 12) + 1 + d = min(self._day, 29 if m == 2 and _dt.date(y, 3, 1) - _dt.date(y, 2, 28) == _dt.timedelta(days=1) else + 30 if m in (4, 6, 9, 11) else 31) + return LocalDate(y, m, d) + + def plusYears(self, years): + return self.plusMonths(years * 12) + + def minusDays(self, days): + return self.plusDays(-days) + + def minusWeeks(self, weeks): + return self.plusDays(-weeks * 7) + + def minusMonths(self, months): + return self.plusMonths(-months) + + def minusYears(self, years): + return self.plusYears(-years) + + def isBefore(self, other): + if isinstance(other, LocalDate): + return (self._year, self._month, self._day) < (other._year, other._month, other._day) + return False + + def isAfter(self, other): + if isinstance(other, LocalDate): + return (self._year, self._month, self._day) > (other._year, other._month, other._day) + return False + + def isEqual(self, other): + if isinstance(other, LocalDate): + return (self._year, self._month, self._day) == (other._year, other._month, other._day) + return False + + def withDayOfMonth(self, day): + return LocalDate(self._year, self._month, day) + + def withMonth(self, month): + if isinstance(month, Month): + month = month.getValue() + return LocalDate(self._year, month, self._day) + + def withYear(self, year): + return LocalDate(year, self._month, self._day) + + def atTime(self, hour, minute=0, second=0, nano=0): + return LocalDateTime(self, LocalTime(hour, minute, second, nano)) + + def atStartOfDay(self, zone=None): + return LocalDateTime(self, LocalTime.MIDNIGHT) + + def format(self, formatter): + return formatter.format(self) + + def compareTo(self, other): + if isinstance(other, LocalDate): + v = self._year - other._year + if v != 0: + return -1 if v < 0 else 1 + v = self._month - other._month + if v != 0: + return -1 if v < 0 else 1 + v = self._day - other._day + return -1 if v < 0 else (1 if v > 0 else 0) + return -1 + + def equals(self, other): + if isinstance(other, LocalDate): + return (self._year, self._month, self._day) == (other._year, other._month, other._day) + return False + + def toString(self): + return f"{self._year:04d}-{self._month:02d}-{self._day:02d}" + + def hashCode(self): + return hash((self._year, self._month, self._day)) + + +LocalDate.MIN = LocalDate(-999999999, 1, 1) +LocalDate.MAX = LocalDate(999999999, 12, 31) + + +# ============================================================ +# LocalTime +# ============================================================ + +class LocalTime(Temporal, Comparable): + MIDNIGHT = None + NOON = None + MIN = None + MAX = None + + def __init__(self, hour, minute=0, second=0, nano=0): + super().__init__() + self._hour = hour + self._minute = minute + self._second = second + self._nano = nano + + @staticmethod + def now(zone=None): + t = _dt.datetime.now().time() + return LocalTime(t.hour, t.minute, t.second, t.microsecond * 1000) + + @staticmethod + def of(hour, minute=0, second=0, nano=0): + return LocalTime(hour, minute, second, nano) + + @staticmethod + def parse(text, formatter=None): + parts = text.split(":") + h = int(parts[0]) + m = int(parts[1]) + s = int(parts[2]) if len(parts) > 2 else 0 + return LocalTime(h, m, s) + + def getHour(self): + return self._hour + + def getMinute(self): + return self._minute + + def getSecond(self): + return self._second + + def getNano(self): + return self._nano + + def toSecondOfDay(self): + return self._hour * 3600 + self._minute * 60 + self._second + + def toNanoOfDay(self): + return self.toSecondOfDay() * 1_000_000_000 + self._nano + + def plusHours(self, h): + total = self._hour + h + return LocalTime(total % 24, self._minute, self._second, self._nano) + + def plusMinutes(self, m): + total = self.toSecondOfDay() + m * 60 + total %= 86400 + h = total // 3600 + total %= 3600 + mi = total // 60 + s = total % 60 + return LocalTime(h, mi, s, self._nano) + + def plusSeconds(self, s): + total = self.toSecondOfDay() + s + total %= 86400 + h = total // 3600 + total %= 3600 + mi = total // 60 + sec = total % 60 + return LocalTime(h, mi, sec, self._nano) + + def plusNanos(self, n): + return LocalTime(self._hour, self._minute, self._second, self._nano + n) + + def minusHours(self, h): + return self.plusHours(-h) + + def minusMinutes(self, m): + return self.plusMinutes(-m) + + def minusSeconds(self, s): + return self.plusSeconds(-s) + + def withHour(self, h): + return LocalTime(h, self._minute, self._second, self._nano) + + def withMinute(self, m): + return LocalTime(self._hour, m, self._second, self._nano) + + def withSecond(self, s): + return LocalTime(self._hour, self._minute, s, self._nano) + + def withNano(self, n): + return LocalTime(self._hour, self._minute, self._second, n) + + def atDate(self, date): + return LocalDateTime(date, self) + + def isBefore(self, other): + if isinstance(other, LocalTime): + return self.toNanoOfDay() < other.toNanoOfDay() + return False + + def isAfter(self, other): + if isinstance(other, LocalTime): + return self.toNanoOfDay() > other.toNanoOfDay() + return False + + def format(self, formatter): + return formatter.format(self) + + def compareTo(self, other): + if isinstance(other, LocalTime): + diff = self.toNanoOfDay() - other.toNanoOfDay() + return -1 if diff < 0 else (1 if diff > 0 else 0) + return -1 + + def equals(self, other): + if isinstance(other, LocalTime): + return (self._hour, self._minute, self._second, self._nano) == \ + (other._hour, other._minute, other._second, other._nano) + return False + + def toString(self): + return f"{self._hour:02d}:{self._minute:02d}:{self._second:02d}" + + def hashCode(self): + return hash((self._hour, self._minute, self._second, self._nano)) + + +LocalTime.MIDNIGHT = LocalTime(0, 0, 0, 0) +LocalTime.NOON = LocalTime(12, 0, 0, 0) +LocalTime.MIN = LocalTime(0, 0, 0, 0) +LocalTime.MAX = LocalTime(23, 59, 59, 999999999) + + +# ============================================================ +# LocalDateTime +# ============================================================ + +class LocalDateTime(Temporal, Comparable): + MIN = None + MAX = None + + def __init__(self, date, time): + super().__init__() + self._date = date + self._time = time + + @staticmethod + def now(zone=None): + now = _dt.datetime.now() + date = LocalDate(now.year, now.month, now.day) + time = LocalTime(now.hour, now.minute, now.second, now.microsecond * 1000) + return LocalDateTime(date, time) + + @staticmethod + def of(date_or_year, month=None, dayOfMonth=None, hour=None, minute=0, second=0, nano=0): + if isinstance(date_or_year, LocalDate): + return LocalDateTime(date_or_year, LocalTime(month or 0, dayOfMonth or 0, hour or 0)) + return LocalDateTime(LocalDate(date_or_year, month, dayOfMonth), + LocalTime(hour, minute, second, nano)) + + @staticmethod + def parse(text, formatter=None): + parts = text.split("T") + date = LocalDate.parse(parts[0]) + time = LocalTime.parse(parts[1]) if len(parts) > 1 else LocalTime.MIDNIGHT + return LocalDateTime(date, time) + + def toLocalDate(self): + return self._date + + def toLocalTime(self): + return self._time + + def getYear(self): + return self._date.getYear() + + def getMonth(self): + return self._date.getMonth() + + def getMonthValue(self): + return self._date.getMonthValue() + + def getDayOfMonth(self): + return self._date.getDayOfMonth() + + def getDayOfWeek(self): + return self._date.getDayOfWeek() + + def getDayOfYear(self): + return self._date.getDayOfYear() + + def getHour(self): + return self._time.getHour() + + def getMinute(self): + return self._time.getMinute() + + def getSecond(self): + return self._time.getSecond() + + def getNano(self): + return self._time.getNano() + + def plusDays(self, d): + return LocalDateTime(self._date.plusDays(d), self._time) + + def plusHours(self, h): + return LocalDateTime(self._date, self._time.plusHours(h)) + + def plusMinutes(self, m): + return LocalDateTime(self._date, self._time.plusMinutes(m)) + + def plusSeconds(self, s): + return LocalDateTime(self._date, self._time.plusSeconds(s)) + + def minusDays(self, d): + return self.plusDays(-d) + + def minusHours(self, h): + return self.plusHours(-h) + + def minusMinutes(self, m): + return self.plusMinutes(-m) + + def minusSeconds(self, s): + return self.plusSeconds(-s) + + def withYear(self, y): + return LocalDateTime(self._date.withYear(y), self._time) + + def withMonth(self, m): + return LocalDateTime(self._date.withMonth(m), self._time) + + def withDayOfMonth(self, d): + return LocalDateTime(self._date.withDayOfMonth(d), self._time) + + def atZone(self, zone): + return ZonedDateTime(self, zone) + + def isBefore(self, other): + if isinstance(other, LocalDateTime): + if not self._date.isEqual(other._date): + return self._date.isBefore(other._date) + return self._time.isBefore(other._time) + return False + + def isAfter(self, other): + if isinstance(other, LocalDateTime): + if not self._date.isEqual(other._date): + return self._date.isAfter(other._date) + return self._time.isAfter(other._time) + return False + + def format(self, formatter): + return formatter.format(self) + + def compareTo(self, other): + if isinstance(other, LocalDateTime): + c = self._date.compareTo(other._date) + return c if c != 0 else self._time.compareTo(other._time) + return -1 + + def equals(self, other): + if isinstance(other, LocalDateTime): + return self._date.equals(other._date) and self._time.equals(other._time) + return False + + def toString(self): + return f"{self._date}T{self._time}" + + def hashCode(self): + return hash((self._date, self._time)) + + +LocalDateTime.MIN = LocalDateTime(LocalDate.MIN, LocalTime.MIN) +LocalDateTime.MAX = LocalDateTime(LocalDate.MAX, LocalTime.MAX) + + +# ============================================================ +# ZonedDateTime +# ============================================================ + +class ZonedDateTime(Temporal, Comparable): + def __init__(self, date_time, zone): + super().__init__() + self._date_time = date_time + self._zone = zone + + @staticmethod + def now(zone=None): + ldt = LocalDateTime.now() + z = zone or ZoneId.systemDefault() + return ZonedDateTime(ldt, z) + + @staticmethod + def of(date, time=None, zone=None, offset=None): + if isinstance(date, LocalDateTime): + return ZonedDateTime(date, time or ZoneId.systemDefault()) + return ZonedDateTime(LocalDateTime.of(date, time), zone) + + @staticmethod + def parse(text, formatter=None): + return ZonedDateTime(LocalDateTime.now(), ZoneId.systemDefault()) + + def getZone(self): + return self._zone + + def getOffset(self): + return ZoneOffset.UTC + + def withZoneSameInstant(self, zone): + return ZonedDateTime(self._date_time, zone) + + def withZoneSameLocal(self, zone): + return ZonedDateTime(self._date_time, zone) + + def toLocalDate(self): + return self._date_time.toLocalDate() + + def toLocalTime(self): + return self._date_time.toLocalTime() + + def toLocalDateTime(self): + return self._date_time + + def toInstant(self): + return Instant.now() + + def plusDays(self, d): + return ZonedDateTime(self._date_time.plusDays(d), self._zone) + + def plusHours(self, h): + return ZonedDateTime(self._date_time.plusHours(h), self._zone) + + def minusDays(self, d): + return self.plusDays(-d) + + def minusHours(self, h): + return self.plusHours(-h) + + def format(self, formatter): + return formatter.format(self) + + def compareTo(self, other): + if isinstance(other, ZonedDateTime): + return self._date_time.compareTo(other._date_time) + return -1 + + def equals(self, other): + if isinstance(other, ZonedDateTime): + return self._date_time.equals(other._date_time) and str(self._zone) == str(other._zone) + return False + + def toString(self): + return f"{self._date_time}[{self._zone}]" + + def hashCode(self): + return hash((self._date_time, str(self._zone))) + + +# ============================================================ +# OffsetDateTime / OffsetTime (stubs) +# ============================================================ + +class OffsetDateTime(Temporal): + def __init__(self, date_time, offset): + super().__init__() + self._date_time = date_time + self._offset = offset + + @staticmethod + def now(): + return OffsetDateTime(LocalDateTime.now(), ZoneOffset.UTC) + + def toLocalDate(self): + return self._date_time.toLocalDate() + + def toLocalTime(self): + return self._date_time.toLocalTime() + + def toString(self): + return f"{self._date_time}{self._offset}" + + +class OffsetTime(Stub): + def __init__(self, time, offset): + super().__init__() + self._time = time + self._offset = offset + + @staticmethod + def now(): + return OffsetTime(LocalTime.now(), ZoneOffset.UTC) + + def toString(self): + return f"{self._time}{self._offset}" + + +# ============================================================ +# Year / YearMonth / MonthDay +# ============================================================ + +class Year(Temporal, Comparable): + def __init__(self, year): + super().__init__() + self._year = year + + @staticmethod + def now(): + return Year(_dt.date.today().year) + + @staticmethod + def of(year): + return Year(year) + + def getValue(self): + return self._year + + def isLeap(self): + return _dt.date(self._year, 3, 1) - _dt.date(self._year, 2, 28) == _dt.timedelta(days=1) + + def length(self): + return 366 if self.isLeap() else 365 + + def atMonth(self, month): + if isinstance(month, Month): + month = month.getValue() + return YearMonth(self._year, month) + + def compareTo(self, other): + if isinstance(other, Year): + return -1 if self._year < other._year else (1 if self._year > other._year else 0) + return -1 + + def toString(self): + return str(self._year) + + +class YearMonth(Temporal, Comparable): + def __init__(self, year, month): + super().__init__() + self._year = year + self._month = month + + @staticmethod + def now(): + d = _dt.date.today() + return YearMonth(d.year, d.month) + + @staticmethod + def of(year, month): + return YearMonth(year, month) + + def getYear(self): + return self._year + + def getMonth(self): + return self._month + + def getMonthValue(self): + return self._month if isinstance(self._month, int) else self._month.getValue() + + def lengthOfMonth(self): + m = self.getMonthValue() + if m == 2: + return 29 if _dt.date(self._year, 3, 1) - _dt.date(self._year, 2, 28) == _dt.timedelta(days=1) else 28 + return 30 if m in (4, 6, 9, 11) else 31 + + def lengthOfYear(self): + return 366 if _dt.date(self._year, 3, 1) - _dt.date(self._year, 2, 28) == _dt.timedelta(days=1) else 365 + + def atDay(self, day): + return LocalDate(self._year, self.getMonthValue(), day) + + def compareTo(self, other): + if isinstance(other, YearMonth): + diff = self._year - other._year + if diff != 0: + return -1 if diff < 0 else 1 + diff = self.getMonthValue() - other.getMonthValue() + return -1 if diff < 0 else (1 if diff > 0 else 0) + return -1 + + def toString(self): + return f"{self._year}-{self.getMonthValue():02d}" + + +class MonthDay(Stub): + def __init__(self, month, day): + super().__init__() + self._month = month + self._day = day + + @staticmethod + def now(): + d = _dt.date.today() + return MonthDay(d.month, d.day) + + @staticmethod + def of(month, day): + return MonthDay(month, day) + + def getMonth(self): + return self._month + + def getMonthValue(self): + return self._month if isinstance(self._month, int) else self._month.getValue() + + def getDayOfMonth(self): + return self._day + + def atYear(self, year): + return LocalDate.of(year, self.getMonthValue(), self._day) + + def toString(self): + return f"--{self.getMonthValue():02d}-{self._day:02d}" + + +# ============================================================ +# Clock +# ============================================================ + +class Clock(Stub): + @staticmethod + def systemUTC(): + return Clock() + + @staticmethod + def systemDefaultZone(): + return Clock() + + @staticmethod + def system(zone): + return Clock() + + def millis(self): + import time as _t + return int(_t.time() * 1000) + + def instant(self): + return Instant.now() + + def getZone(self): + return ZoneId.systemDefault() + + +# ============================================================ +# DateTimeFormatter +# ============================================================ + +class DateTimeFormatter(Stub): + ISO_LOCAL_DATE = None + ISO_LOCAL_TIME = None + ISO_LOCAL_DATE_TIME = None + ISO_INSTANT = None + ISO_ZONED_DATE_TIME = None + ISO_OFFSET_DATE_TIME = None + + def __init__(self, pattern): + super().__init__() + self._pattern = pattern + + @staticmethod + def ofPattern(pattern): + return DateTimeFormatter(pattern) + + def format(self, temporal): + if isinstance(temporal, LocalDate): + return temporal.toString() + if isinstance(temporal, LocalTime): + return temporal.toString() + if isinstance(temporal, LocalDateTime): + return temporal.toString() + if isinstance(temporal, ZonedDateTime): + return temporal.toString() + if isinstance(temporal, Instant): + return temporal.toString() + return str(temporal) + + def parse(self, text, query=None): + if query is LocalDate or query is None: + return LocalDate.parse(text) + if query is LocalTime: + return LocalTime.parse(text) + if query is LocalDateTime: + return LocalDateTime.parse(text) + return text + + +DateTimeFormatter.ISO_LOCAL_DATE = DateTimeFormatter("yyyy-MM-dd") +DateTimeFormatter.ISO_LOCAL_TIME = DateTimeFormatter("HH:mm:ss") +DateTimeFormatter.ISO_LOCAL_DATE_TIME = DateTimeFormatter("yyyy-MM-ddTHH:mm:ss") +DateTimeFormatter.ISO_INSTANT = DateTimeFormatter("yyyy-MM-ddTHH:mm:ssZ") +DateTimeFormatter.ISO_ZONED_DATE_TIME = DateTimeFormatter("yyyy-MM-ddTHH:mm:ssZ") +DateTimeFormatter.ISO_OFFSET_DATE_TIME = DateTimeFormatter("yyyy-MM-ddTHH:mm:ssZ") + + +# ============================================================ +# DateTimeFormatterBuilder (stub) +# ============================================================ + +class DateTimeFormatterBuilder(Stub): + def __init__(self): + super().__init__() + self._pattern_parts = [] + + def appendValue(self, field, width=1): + return self + + def appendLiteral(self, literal): + return self + + def appendPattern(self, pattern): + self._pattern_parts.append(pattern) + return self + + def toFormatter(self): + return DateTimeFormatter("".join(self._pattern_parts)) + + +# ============================================================ +# TemporalAdjusters (stub) +# ============================================================ + +class TemporalAdjusters(Stub): + @staticmethod + def firstDayOfMonth(): + return lambda d: d.withDayOfMonth(1) + + @staticmethod + def lastDayOfMonth(): + return lambda d: d.withDayOfMonth(d.lengthOfMonth()) + + @staticmethod + def firstDayOfYear(): + return lambda d: d.withDayOfMonth(1).withMonth(1) + + @staticmethod + def lastDayOfYear(): + return lambda d: d.withDayOfMonth(31).withMonth(12) + + @staticmethod + def firstInMonth(day_of_week): + return lambda d: d + + @staticmethod + def next(day_of_week): + return lambda d: d.plusDays(1) \ No newline at end of file diff --git a/android_pyapi/java/util.py b/android_pyapi/java/util.py new file mode 100644 index 0000000..5273186 --- /dev/null +++ b/android_pyapi/java/util.py @@ -0,0 +1,3693 @@ +""" +Java java.util standard library APIs implemented in Python. +Android-pyApi: desktop Python translation of Android Java APIs. +""" + +from .._core import Stub +from .lang import RuntimeException +import heapq as _heapq +import bisect as _bisect +import datetime as _datetime +import uuid as _uuid +import random as _py_random +import math as _py_math +import copy as _copy +import re as _re +import io as _io +import threading as _threading +import time as _time +from typing import Any, Dict, List, Callable, Tuple, Iterable as _IterableType +import typing as _typing +from collections.abc import Iterable as _IterableABC + +__all__ = [ + "ArrayList", "LinkedList", "HashMap", "LinkedHashMap", "TreeMap", + "HashSet", "TreeSet", "LinkedHashSet", + "Collections", "Arrays", "Date", "Calendar", "GregorianCalendar", + "UUID", "StringTokenizer", "Optional", "Random", + "Properties", "EnumSet", "EnumMap", + "Iterator", "ListIterator", "Enumeration", + "Objects", "Locale", "BitSet", "PriorityQueue", + "Vector", "Stack", + "ConcurrentModificationException", "NoSuchElementException", + "EmptyStackException", "TooManyListenersException", + "MissingResourceException", "IllegalFormatException", + "DuplicateFormatFlagsException", "FormatFlagsConversionMismatchException", + "FormatterClosedException", +] + +# ============================================================ +# Exceptions +# ============================================================ + +# Define exception classes in correct dependency order. +# IllegalArgumentException comes before IllegalFormatException. +IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {}) + +ConcurrentModificationException = type( + "ConcurrentModificationException", (RuntimeException,), {} +) +NoSuchElementException = type("NoSuchElementException", (RuntimeException,), {}) +EmptyStackException = type("EmptyStackException", (RuntimeException,), {}) +TooManyListenersException = type("TooManyListenersException", (Exception,), {}) +MissingResourceException = type("MissingResourceException", (RuntimeException,), {}) +IllegalFormatException = type("IllegalFormatException", (IllegalArgumentException,), {}) +DuplicateFormatFlagsException = type( + "DuplicateFormatFlagsException", (IllegalFormatException,), {} +) +FormatFlagsConversionMismatchException = type( + "FormatFlagsConversionMismatchException", (IllegalFormatException,), {} +) + +# We need IllegalStateException for FormatterClosedException +from .lang import RuntimeException as _RuntimeException +IllegalStateException = type("IllegalStateException", (_RuntimeException,), {}) + +FormatterClosedException = type("FormatterClosedException", (IllegalStateException,), {}) +UnsupportedOperationException = type("UnsupportedOperationException", (RuntimeException,), {}) + +# ============================================================ +# Iterator / ListIterator / Enumeration +# ============================================================ + +class Iterator(Stub): + """java.util.Iterator""" + def __init__(self, data): + super().__init__() + self._data = list(data) + self._index = 0 + self._mod_count = 0 + + def hasNext(self) -> bool: + return self._index < len(self._data) + + def next(self): + if not self.hasNext(): + raise NoSuchElementException() + val = self._data[self._index] + self._index += 1 + return val + + def remove(self): + raise UnsupportedOperationException("remove") + + def forEachRemaining(self, action): + while self.hasNext(): + action(self.next()) + + +class ListIterator(Iterator): + """java.util.ListIterator""" + def __init__(self, data): + super().__init__(data) + + def hasPrevious(self) -> bool: + return self._index > 0 + + def previous(self): + if not self.hasPrevious(): + raise NoSuchElementException() + self._index -= 1 + return self._data[self._index] + + def nextIndex(self) -> int: + return self._index + + def previousIndex(self) -> int: + return self._index - 1 + + def set(self, obj): + self._data[self._index - 1] = obj + + def add(self, obj): + self._data.insert(self._index, obj) + self._index += 1 + + +class Enumeration(Stub): + """java.util.Enumeration""" + def __init__(self, data): + super().__init__() + self._it = iter(data) + + def hasMoreElements(self) -> bool: + try: + self._next = next(self._it) + return True + except StopIteration: + return False + + def nextElement(self): + if hasattr(self, '_next'): + val = self._next + del self._next + return val + return next(self._it) + + def asIterator(self) -> Iterator: + return Iterator(self._it) + + +# ============================================================ +# ArrayList +# ============================================================ + +class ArrayList(Stub): + """java.util.ArrayList — backed by Python list""" + def __init__(self, initial_capacity: Any = None): + super().__init__() + if initial_capacity is None: + self._data = [] + elif isinstance(initial_capacity, int): + self._data = [] + elif isinstance(initial_capacity, _IterableABC): + self._data = list(initial_capacity) + else: + self._data = [] + + def add(self, obj) -> bool: + self._data.append(obj) + return True + + def addAt(self, index: int, obj) -> None: + self._data.insert(index, obj) + + def addAll(self, c) -> bool: + if isinstance(c, ArrayList): + self._data.extend(c._data) + else: + self._data.extend(c) + return True + + def addAllAt(self, index: int, c) -> bool: + if isinstance(c, ArrayList): + self._data[index:index] = c._data + else: + self._data[index:index] = list(c) + return True + + def clear(self) -> None: + self._data.clear() + + def clone(self) -> 'ArrayList': + return ArrayList(self._data) + + def contains(self, obj) -> bool: + return obj in self._data + + def containsAll(self, c) -> bool: + if isinstance(c, ArrayList): + return all(x in self._data for x in c._data) + return all(x in self._data for x in c) + + def ensureCapacity(self, min_capacity: int) -> None: + pass # Python list handles resizing automatically + + def get(self, index: int): + return self._data[index] + + def indexOf(self, obj) -> int: + try: + return self._data.index(obj) + except ValueError: + return -1 + + def isEmpty(self) -> bool: + return len(self._data) == 0 + + def iterator(self) -> Iterator: + return Iterator(self._data) + + def listIterator(self) -> ListIterator: + return ListIterator(self._data) + + def lastIndexOf(self, obj) -> int: + try: + return len(self._data) - 1 - self._data[::-1].index(obj) + except ValueError: + return -1 + + def remove(self, obj) -> bool: + if obj in self._data: + self._data.remove(obj) + return True + return False + + def removeAt(self, index: int): + return self._data.pop(index) + + def removeAll(self, c) -> bool: + if isinstance(c, ArrayList): + items = c._data + else: + items = list(c) + old_len = len(self._data) + self._data = [x for x in self._data if x not in items] + return len(self._data) != old_len + + def retainAll(self, c) -> bool: + if isinstance(c, ArrayList): + items = set(c._data) + else: + items = set(c) + old_len = len(self._data) + self._data = [x for x in self._data if x in items] + return len(self._data) != old_len + + def set(self, index: int, obj): + old = self._data[index] + self._data[index] = obj + return old + + def size(self) -> int: + return len(self._data) + + def sort(self, key=None, reverse: bool = False) -> None: + self._data.sort(key=key, reverse=reverse) + + def subList(self, from_index: int, to_index: int) -> 'ArrayList': + return ArrayList(self._data[from_index:to_index]) + + def toArray(self, a=None): + if a is None: + return list(self._data) + if len(a) < len(self._data): + return list(self._data) + for i, v in enumerate(self._data): + a[i] = v + if len(self._data) < len(a): + a[len(self._data)] = None + return a + + def trimToSize(self) -> None: + pass + + def forEach(self, action) -> None: + for item in self._data: + action(item) + + def removeIf(self, predicate) -> bool: + old_len = len(self._data) + self._data = [x for x in self._data if not predicate(x)] + return len(self._data) != old_len + + def replaceAll(self, operator) -> None: + self._data = [operator(x) for x in self._data] + + def equals(self, other) -> bool: + if isinstance(other, ArrayList): + return self._data == other._data + return False + + def hashCode(self) -> int: + h = 1 + for v in self._data: + h = (31 * h + hash(v)) & 0x7FFFFFFF + return h + + def toString(self) -> str: + items = ", ".join(str(x) for x in self._data) + return f"[{items}]" + + def __len__(self): + return len(self._data) + + def __getitem__(self, idx): + return self._data[idx] + + def __setitem__(self, idx, val): + self._data[idx] = val + + def __delitem__(self, idx): + del self._data[idx] + + def __iter__(self): + return iter(self._data) + + def __repr__(self): + return self.toString() + + def __contains__(self, obj): + return obj in self._data + + def __add__(self, other): + if isinstance(other, ArrayList): + result = ArrayList(self._data) + result._data.extend(other._data) + return result + return NotImplemented + + def __eq__(self, other): + return self.equals(other) + + +# ============================================================ +# LinkedList +# ============================================================ + +class LinkedList(Stub): + """java.util.LinkedList — backed by Python list""" + def __init__(self, c=None): + super().__init__() + if c is None: + self._data = [] + elif isinstance(c, _IterableABC): + self._data = list(c) + else: + self._data = [] + + def add(self, obj) -> bool: + self._data.append(obj) + return True + + def addFirst(self, obj) -> None: + self._data.insert(0, obj) + + def addLast(self, obj) -> None: + self._data.append(obj) + + def addAt(self, index: int, obj) -> None: + self._data.insert(index, obj) + + def addAll(self, c) -> bool: + if isinstance(c, LinkedList): + self._data.extend(c._data) + else: + self._data.extend(c) + return True + + def clear(self) -> None: + self._data.clear() + + def clone(self) -> 'LinkedList': + return LinkedList(self._data) + + def contains(self, obj) -> bool: + return obj in self._data + + def descendingIterator(self) -> Iterator: + return Iterator(reversed(self._data)) + + def element(self): + if not self._data: + raise NoSuchElementException() + return self._data[0] + + def get(self, index: int): + return self._data[index] + + def getFirst(self): + if not self._data: + raise NoSuchElementException() + return self._data[0] + + def getLast(self): + if not self._data: + raise NoSuchElementException() + return self._data[-1] + + def indexOf(self, obj) -> int: + try: + return self._data.index(obj) + except ValueError: + return -1 + + def isEmpty(self) -> bool: + return len(self._data) == 0 + + def iterator(self) -> Iterator: + return Iterator(self._data) + + def lastIndexOf(self, obj) -> int: + try: + return len(self._data) - 1 - self._data[::-1].index(obj) + except ValueError: + return -1 + + def listIterator(self, index: int = 0) -> ListIterator: + li = ListIterator(self._data) + li._index = index + return li + + def offer(self, obj) -> bool: + self._data.append(obj) + return True + + def offerFirst(self, obj) -> bool: + self._data.insert(0, obj) + return True + + def offerLast(self, obj) -> bool: + self._data.append(obj) + return True + + def peek(self): + return self._data[0] if self._data else None + + def peekFirst(self): + return self._data[0] if self._data else None + + def peekLast(self): + return self._data[-1] if self._data else None + + def poll(self): + return self._data.pop(0) if self._data else None + + def pollFirst(self): + return self._data.pop(0) if self._data else None + + def pollLast(self): + return self._data.pop() if self._data else None + + def pop(self): + if not self._data: + raise NoSuchElementException() + return self._data.pop() + + def push(self, obj) -> None: + self._data.insert(0, obj) + + def remove(self, obj) -> bool: + if obj in self._data: + self._data.remove(obj) + return True + return False + + def removeAt(self, index: int): + return self._data.pop(index) + + def removeFirst(self): + if not self._data: + raise NoSuchElementException() + return self._data.pop(0) + + def removeFirstOccurrence(self, obj) -> bool: + try: + idx = self._data.index(obj) + self._data.pop(idx) + return True + except ValueError: + return False + + def removeLast(self): + if not self._data: + raise NoSuchElementException() + return self._data.pop() + + def removeLastOccurrence(self, obj) -> bool: + try: + idx = len(self._data) - 1 - self._data[::-1].index(obj) + self._data.pop(idx) + return True + except ValueError: + return False + + def set(self, index: int, obj): + old = self._data[index] + self._data[index] = obj + return old + + def size(self) -> int: + return len(self._data) + + def toArray(self, a=None): + if a is None: + return list(self._data) + if len(a) < len(self._data): + return list(self._data) + for i, v in enumerate(self._data): + a[i] = v + if len(self._data) < len(a): + a[len(self._data)] = None + return a + + def forEach(self, action) -> None: + for item in self._data: + action(item) + + def equals(self, other) -> bool: + if isinstance(other, LinkedList): + return self._data == other._data + return False + + def hashCode(self) -> int: + h = 1 + for v in self._data: + h = (31 * h + hash(v)) & 0x7FFFFFFF + return h + + def toString(self) -> str: + items = ", ".join(str(x) for x in self._data) + return f"[{items}]" + + def __len__(self): + return len(self._data) + + def __getitem__(self, idx): + return self._data[idx] + + def __iter__(self): + return iter(self._data) + + def __repr__(self): + return self.toString() + + def __eq__(self, other): + return self.equals(other) + + +# ============================================================ +# HashMap +# ============================================================ + +class HashMap(Stub): + """java.util.HashMap — backed by Python dict""" + def __init__(self, initial_capacity: Any = None, load_factor: float = 0.75): + super().__init__() + self._data: Dict = {} + if initial_capacity is not None and not isinstance(initial_capacity, (int, float)): + if isinstance(initial_capacity, HashMap): + self._data.update(initial_capacity._data) + elif isinstance(initial_capacity, dict): + self._data.update(initial_capacity) + elif isinstance(initial_capacity, _IterableABC): + for item in initial_capacity: + if hasattr(item, 'getKey') and hasattr(item, 'getValue'): + k, v = item.getKey(), item.getValue() + elif isinstance(item, tuple) and len(item) >= 2: + k, v = item[0], item[1] + else: + continue + self._data[k] = v + + def clear(self) -> None: + self._data.clear() + + def clone(self) -> 'HashMap': + return HashMap(self._data) + + def compute(self, key, remapping_function): + old = self._data.get(key) + new_val = remapping_function(key, old) + if new_val is not None: + self._data[key] = new_val + return new_val + self._data.pop(key, None) + return None + + def computeIfAbsent(self, key, mapping_function): + if key not in self._data: + new_val = mapping_function(key) + if new_val is not None: + self._data[key] = new_val + return new_val + return self._data.get(key) + + def computeIfPresent(self, key, remapping_function): + if key in self._data: + new_val = remapping_function(key, self._data[key]) + if new_val is not None: + self._data[key] = new_val + return new_val + self._data.pop(key, None) + return None + + def containsKey(self, key) -> bool: + return key in self._data + + def containsValue(self, value) -> bool: + return value in self._data.values() + + def entrySet(self) -> set: + return self._data.items() + + def forEach(self, action) -> None: + for k, v in self._data.items(): + action(k, v) + + def get(self, key): + return self._data.get(key) + + def getOrDefault(self, key, default): + return self._data.get(key, default) + + def isEmpty(self) -> bool: + return len(self._data) == 0 + + def keySet(self) -> set: + return set(self._data.keys()) + + def merge(self, key, value, remapping_function): + old = self._data.get(key) + if old is None: + self._data[key] = value + return value + new_val = remapping_function(old, value) + if new_val is not None: + self._data[key] = new_val + return new_val + self._data.pop(key, None) + return None + + def put(self, key, value): + old = self._data.get(key) + self._data[key] = value + return old + + def putAll(self, m) -> None: + if isinstance(m, HashMap): + self._data.update(m._data) + else: + self._data.update(m) + + def putIfAbsent(self, key, value): + if key not in self._data: + self._data[key] = value + return None + return self._data[key] + + def remove(self, key, value=None) -> bool: + if value is None: + return self._data.pop(key, None) is not None or key in self._data + if key in self._data and self._data[key] == value: + del self._data[key] + return True + return False + + def replace(self, key, old_value=None, new_value=None): + if old_value is None and new_value is None: + return None + if new_value is None: + # replace(key, value) form + if key in self._data: + old = self._data[key] + self._data[key] = old_value # old_value is actually new value here + return old + return None + # replace(key, oldValue, newValue) form + if key in self._data and self._data[key] == old_value: + self._data[key] = new_value + return True + return False + + def replaceAll(self, function) -> None: + for k in list(self._data.keys()): + self._data[k] = function(k, self._data[k]) + + def size(self) -> int: + return len(self._data) + + def values(self): + return list(self._data.values()) + + def equals(self, other) -> bool: + if isinstance(other, HashMap): + return self._data == other._data + return False + + def hashCode(self) -> int: + h = 0 + for k, v in self._data.items(): + h = (h + (hash(k) ^ hash(v))) & 0x7FFFFFFF + return h + + def toString(self) -> str: + items = ", ".join(f"{k}={v}" for k, v in self._data.items()) + return f"{{{items}}}" + + def __len__(self): + return len(self._data) + + def __getitem__(self, key): + return self._data[key] + + def __setitem__(self, key, value): + self._data[key] = value + + def __delitem__(self, key): + del self._data[key] + + def __iter__(self): + return iter(self._data) + + def __contains__(self, key): + return key in self._data + + def __repr__(self): + return self.toString() + + def __eq__(self, other): + return self.equals(other) + + +# ============================================================ +# LinkedHashMap +# ============================================================ + +class LinkedHashMap(HashMap): + """java.util.LinkedHashMap — backed by dict (preserves order in Python 3.7+)""" + def __init__(self, initial_capacity: Any = None, load_factor: float = 0.75, + access_order: bool = False): + super().__init__(initial_capacity, load_factor) + self._access_order = access_order + + def get(self, key): + val = self._data.get(key) + if val is not None and self._access_order: + # Move to end (most recently accessed) by delete/reinsert + del self._data[key] + self._data[key] = val + return val + + def getOrDefault(self, key, default): + val = self._data.get(key, default) + if key in self._data and self._access_order: + del self._data[key] + self._data[key] = val + return val + + def put(self, key, value): + old = self._data.get(key) + if key in self._data: + del self._data[key] + self._data[key] = value + return old + + def removeEldestEntry(self, eldest) -> bool: + return False + + def eldest(self): + if not self._data: + return None + key = next(iter(self._data)) + return (key, self._data[key]) + + +# ============================================================ +# TreeMap +# ============================================================ + +class TreeMap(Stub): + """java.util.TreeMap — sorted map using Python's sorted()""" + def __init__(self, comparator=None, m=None): + super().__init__() + self._data: Dict = {} + self._comparator = comparator + if m is not None: + if isinstance(m, TreeMap): + self._data.update(m._data) + elif isinstance(m, dict): + self._data.update(m) + elif isinstance(m, _IterableABC): + for item in m: + if isinstance(item, tuple) and len(item) >= 2: + self._data[item[0]] = item[1] + + def _sorted_keys(self): + key_fn = self._comparator if self._comparator else None + return sorted(self._data.keys(), key=key_fn) + + def _get_entry_by_key(self, key): + if key in self._data: + return (key, self._data[key]) + return None + + def clear(self) -> None: + self._data.clear() + + def clone(self) -> 'TreeMap': + return TreeMap(self._comparator, self._data) + + def comparator(self): + return self._comparator + + def containsKey(self, key) -> bool: + return key in self._data + + def containsValue(self, value) -> bool: + return value in self._data.values() + + def ceilingKey(self, key): + keys = self._sorted_keys() + for k in keys: + if (self._comparator(k, key) >= 0 if self._comparator else k >= key): + return k + return None + + def ceilingEntry(self, key): + k = self.ceilingKey(key) + return (k, self._data[k]) if k is not None else None + + def floorKey(self, key): + keys = self._sorted_keys() + for k in reversed(keys): + if (self._comparator(k, key) <= 0 if self._comparator else k <= key): + return k + return None + + def floorEntry(self, key): + k = self.floorKey(key) + return (k, self._data[k]) if k is not None else None + + def higherKey(self, key): + keys = self._sorted_keys() + for k in keys: + if (self._comparator(k, key) > 0 if self._comparator else k > key): + return k + return None + + def higherEntry(self, key): + k = self.higherKey(key) + return (k, self._data[k]) if k is not None else None + + def lowerKey(self, key): + keys = self._sorted_keys() + for k in reversed(keys): + if (self._comparator(k, key) < 0 if self._comparator else k < key): + return k + return None + + def lowerEntry(self, key): + k = self.lowerKey(key) + return (k, self._data[k]) if k is not None else None + + def firstKey(self): + if not self._data: + raise NoSuchElementException() + return self._sorted_keys()[0] + + def firstEntry(self): + k = self.firstKey() + return (k, self._data[k]) + + def lastKey(self): + if not self._data: + raise NoSuchElementException() + return self._sorted_keys()[-1] + + def lastEntry(self): + k = self.lastKey() + return (k, self._data[k]) + + def pollFirstEntry(self): + if not self._data: + return None + k = self.firstKey() + v = self._data.pop(k) + return (k, v) + + def pollLastEntry(self): + if not self._data: + return None + k = self.lastKey() + v = self._data.pop(k) + return (k, v) + + def entrySet(self): + return [(k, self._data[k]) for k in self._sorted_keys()] + + def forEach(self, action) -> None: + for k in self._sorted_keys(): + action(k, self._data[k]) + + def get(self, key): + return self._data.get(key) + + def getOrDefault(self, key, default): + return self._data.get(key, default) + + def headMap(self, to_key, inclusive: bool = False) -> 'TreeMap': + result = TreeMap(self._comparator) + for k in self._sorted_keys(): + cmp = self._comparator(k, to_key) if self._comparator else (k < to_key) + if cmp or (inclusive and (self._comparator(k, to_key) == 0 if self._comparator else k == to_key)): + result._data[k] = self._data[k] + elif not inclusive and (self._comparator(k, to_key) < 0 if self._comparator else k < to_key): + result._data[k] = self._data[k] + else: + break + return result + + def tailMap(self, from_key, inclusive: bool = True) -> 'TreeMap': + result = TreeMap(self._comparator) + started = False + for k in self._sorted_keys(): + cmp = self._comparator(k, from_key) if self._comparator else (k >= from_key) + if cmp or (inclusive and (self._comparator(k, from_key) == 0 if self._comparator else k == from_key)): + started = True + if started: + result._data[k] = self._data[k] + return result + + def subMap(self, from_key, to_key, from_inclusive: bool = True, to_inclusive: bool = False) -> 'TreeMap': + result = TreeMap(self._comparator) + for k in self._sorted_keys(): + cmp_from = self._comparator(k, from_key) if self._comparator else (k >= from_key if from_inclusive else k > from_key) + cmp_to = self._comparator(k, to_key) if self._comparator else (k <= to_key if to_inclusive else k < to_key) + if cmp_from and cmp_to: + result._data[k] = self._data[k] + return result + + def isEmpty(self) -> bool: + return len(self._data) == 0 + + def keySet(self): + return self._sorted_keys() + + def put(self, key, value): + old = self._data.get(key) + self._data[key] = value + return old + + def putAll(self, m) -> None: + if isinstance(m, TreeMap): + self._data.update(m._data) + else: + self._data.update(m) + + def putIfAbsent(self, key, value): + if key not in self._data: + self._data[key] = value + return None + return self._data[key] + + def remove(self, key): + return self._data.pop(key, None) + + def replace(self, key, old_value=None, new_value=None): + if new_value is None: + if key in self._data: + old = self._data[key] + self._data[key] = old_value + return old + return None + if key in self._data and self._data[key] == old_value: + self._data[key] = new_value + return True + return False + + def size(self) -> int: + return len(self._data) + + def values(self): + return [self._data[k] for k in self._sorted_keys()] + + def navigableKeySet(self): + return self._sorted_keys() + + def descendingKeySet(self): + return list(reversed(self._sorted_keys())) + + def descendingMap(self) -> 'TreeMap': + result = TreeMap(self._comparator) + for k in reversed(self._sorted_keys()): + result._data[k] = self._data[k] + return result + + def equals(self, other) -> bool: + if isinstance(other, TreeMap): + return self._data == other._data + return False + + def hashCode(self) -> int: + h = 0 + for k, v in self._data.items(): + h = (h + (hash(k) ^ hash(v))) & 0x7FFFFFFF + return h + + def toString(self) -> str: + items = ", ".join(f"{k}={v}" for k in self._data.items()) + return f"{{{items}}}" + + def __len__(self): + return len(self._data) + + def __getitem__(self, key): + return self._data[key] + + def __setitem__(self, key, value): + self._data[key] = value + + def __delitem__(self, key): + del self._data[key] + + def __iter__(self): + return iter(self._sorted_keys()) + + def __repr__(self): + return self.toString() + + def __eq__(self, other): + return self.equals(other) + + +# ============================================================ +# HashSet +# ============================================================ + +class HashSet(Stub): + """java.util.HashSet — backed by Python set""" + def __init__(self, c=None): + super().__init__() + if c is None: + self._data: set = set() + elif isinstance(c, _IterableABC): + self._data = set(c) + else: + self._data = set() + + def add(self, obj) -> bool: + if obj in self._data: + return False + self._data.add(obj) + return True + + def addAll(self, c) -> bool: + old_size = len(self._data) + if isinstance(c, HashSet): + self._data.update(c._data) + elif isinstance(c, set): + self._data.update(c) + else: + self._data.update(c) + return len(self._data) != old_size + + def clear(self) -> None: + self._data.clear() + + def clone(self) -> 'HashSet': + return HashSet(self._data) + + def contains(self, obj) -> bool: + return obj in self._data + + def containsAll(self, c) -> bool: + if isinstance(c, HashSet): + return all(x in self._data for x in c._data) + return all(x in self._data for x in c) + + def isEmpty(self) -> bool: + return len(self._data) == 0 + + def iterator(self) -> Iterator: + return Iterator(list(self._data)) + + def remove(self, obj) -> bool: + if obj in self._data: + self._data.remove(obj) + return True + return False + + def removeAll(self, c) -> bool: + old_size = len(self._data) + if isinstance(c, HashSet): + self._data.difference_update(c._data) + else: + self._data.difference_update(c) + return len(self._data) != old_size + + def retainAll(self, c) -> bool: + old_size = len(self._data) + if isinstance(c, HashSet): + self._data.intersection_update(c._data) + else: + self._data.intersection_update(c) + return len(self._data) != old_size + + def size(self) -> int: + return len(self._data) + + def toArray(self, a=None): + items = list(self._data) + if a is None: + return items + if len(a) < len(items): + return items + for i, v in enumerate(items): + a[i] = v + if len(items) < len(a): + a[len(items)] = None + return a + + def forEach(self, action) -> None: + for item in list(self._data): + action(item) + + def equals(self, other) -> bool: + if isinstance(other, HashSet): + return self._data == other._data + return False + + def hashCode(self) -> int: + h = 0 + for v in self._data: + h = (h + hash(v)) & 0x7FFFFFFF + return h + + def toString(self) -> str: + items = ", ".join(str(x) for x in self._data) + return f"[{items}]" + + def __len__(self): + return len(self._data) + + def __iter__(self): + return iter(self._data) + + def __contains__(self, obj): + return obj in self._data + + def __repr__(self): + return self.toString() + + def __eq__(self, other): + return self.equals(other) + + +# ============================================================ +# LinkedHashSet +# ============================================================ + +class LinkedHashSet(HashSet): + """java.util.LinkedHashSet — ordered set (preserves insertion order)""" + def __init__(self, c=None): + # dict keys preserve insertion order in Python 3.7+ + self._order: Dict[Any, None] = {} + if c is not None: + if isinstance(c, _IterableABC): + for item in c: + self._order[item] = None + self._data = set(self._order.keys()) + + def add(self, obj) -> bool: + if obj in self._order: + return False + self._order[obj] = None + self._data.add(obj) + return True + + def remove(self, obj) -> bool: + if obj in self._order: + del self._order[obj] + self._data.discard(obj) + return True + return False + + def clear(self) -> None: + self._order.clear() + self._data.clear() + + def iterator(self) -> Iterator: + return Iterator(list(self._order.keys())) + + def toArray(self, a=None): + items = list(self._order.keys()) + if a is None: + return items + if len(a) < len(items): + return items + for i, v in enumerate(items): + a[i] = v + if len(items) < len(a): + a[len(items)] = None + return a + + def forEach(self, action) -> None: + for item in list(self._order.keys()): + action(item) + + def __iter__(self): + return iter(self._order.keys()) + + +# ============================================================ +# TreeSet +# ============================================================ + +class TreeSet(Stub): + """java.util.TreeSet — sorted set""" + def __init__(self, comparator=None, c=None): + super().__init__() + self._data: set = set() + self._comparator = comparator + if c is not None: + if isinstance(c, _IterableABC): + self._data = set(c) + + def _sorted(self): + key_fn = self._comparator if self._comparator else None + return sorted(self._data, key=key_fn) + + def add(self, obj) -> bool: + if obj in self._data: + return False + self._data.add(obj) + return True + + def addAll(self, c) -> bool: + old_size = len(self._data) + if isinstance(c, TreeSet): + self._data.update(c._data) + else: + self._data.update(c) + return len(self._data) != old_size + + def ceiling(self, obj): + sorted_items = self._sorted() + for item in sorted_items: + if (self._comparator(item, obj) >= 0 if self._comparator else item >= obj): + return item + return None + + def floor(self, obj): + sorted_items = self._sorted() + for item in reversed(sorted_items): + if (self._comparator(item, obj) <= 0 if self._comparator else item <= obj): + return item + return None + + def higher(self, obj): + sorted_items = self._sorted() + for item in sorted_items: + if (self._comparator(item, obj) > 0 if self._comparator else item > obj): + return item + return None + + def lower(self, obj): + sorted_items = self._sorted() + for item in reversed(sorted_items): + if (self._comparator(item, obj) < 0 if self._comparator else item < obj): + return item + return None + + def clear(self) -> None: + self._data.clear() + + def clone(self) -> 'TreeSet': + return TreeSet(self._comparator, self._data) + + def comparator(self): + return self._comparator + + def contains(self, obj) -> bool: + return obj in self._data + + def containsAll(self, c) -> bool: + if isinstance(c, TreeSet): + return all(x in self._data for x in c._data) + return all(x in self._data for x in c) + + def first(self): + if not self._data: + raise NoSuchElementException() + return self._sorted()[0] + + def last(self): + if not self._data: + raise NoSuchElementException() + return self._sorted()[-1] + + def pollFirst(self): + if not self._data: + return None + val = self._sorted()[0] + self._data.discard(val) + return val + + def pollLast(self): + if not self._data: + return None + val = self._sorted()[-1] + self._data.discard(val) + return val + + def headSet(self, to_element, inclusive: bool = False) -> 'TreeSet': + result = TreeSet(self._comparator) + for item in self._data: + cmp = self._comparator(item, to_element) if self._comparator else (item < to_element) + if cmp or (inclusive and item == to_element): + result._data.add(item) + return result + + def tailSet(self, from_element, inclusive: bool = True) -> 'TreeSet': + result = TreeSet(self._comparator) + for item in self._data: + cmp = self._comparator(item, from_element) if self._comparator else (item > from_element) + if cmp or (inclusive and item == from_element): + result._data.add(item) + return result + + def subSet(self, from_element, to_element, + from_inclusive: bool = True, to_inclusive: bool = False) -> 'TreeSet': + result = TreeSet(self._comparator) + for item in self._data: + cmp_from = self._comparator(item, from_element) if self._comparator else ( + item >= from_element if from_inclusive else item > from_element) + cmp_to = self._comparator(item, to_element) if self._comparator else ( + item <= to_element if to_inclusive else item < to_element) + if cmp_from and cmp_to: + result._data.add(item) + return result + + def descendingIterator(self) -> Iterator: + return Iterator(reversed(self._sorted())) + + def descendingSet(self) -> 'TreeSet': + result = TreeSet(self._comparator) + result._data = set(self._data) + return result + + def isEmpty(self) -> bool: + return len(self._data) == 0 + + def iterator(self) -> Iterator: + return Iterator(self._sorted()) + + def remove(self, obj) -> bool: + if obj in self._data: + self._data.discard(obj) + return True + return False + + def removeAll(self, c) -> bool: + old_size = len(self._data) + if isinstance(c, TreeSet): + self._data.difference_update(c._data) + else: + self._data.difference_update(c) + return len(self._data) != old_size + + def retainAll(self, c) -> bool: + old_size = len(self._data) + if isinstance(c, TreeSet): + self._data.intersection_update(c._data) + else: + self._data.intersection_update(c) + return len(self._data) != old_size + + def size(self) -> int: + return len(self._data) + + def toArray(self, a=None): + items = self._sorted() + if a is None: + return items + if len(a) < len(items): + return items + for i, v in enumerate(items): + a[i] = v + if len(items) < len(a): + a[len(items)] = None + return a + + def forEach(self, action) -> None: + for item in self._sorted(): + action(item) + + def equals(self, other) -> bool: + if isinstance(other, TreeSet): + return self._data == other._data + return False + + def hashCode(self) -> int: + h = 0 + for v in self._data: + h = (h + hash(v)) & 0x7FFFFFFF + return h + + def toString(self) -> str: + items = ", ".join(str(x) for x in self._sorted()) + return f"[{items}]" + + def __len__(self): + return len(self._data) + + def __iter__(self): + return iter(self._sorted()) + + def __contains__(self, obj): + return obj in self._data + + def __repr__(self): + return self.toString() + + def __eq__(self, other): + return self.equals(other) + + +# ============================================================ +# Collections +# ============================================================ + +class Collections: + """java.util.Collections — static utility methods""" + EMPTY_LIST = [] + EMPTY_SET = set() + EMPTY_MAP = {} + + @staticmethod + def sort(data, key=None, reverse: bool = False) -> None: + if key is not None: + if isinstance(data, ArrayList): + data._data.sort(key=key, reverse=reverse) + elif isinstance(data, Vector): + data._data.sort(key=key, reverse=reverse) + else: + data.sort(key=key, reverse=reverse) + else: + data.sort(reverse=reverse) + + @staticmethod + def reverse(data) -> None: + if isinstance(data, ArrayList): + data._data.reverse() + elif isinstance(data, Vector): + data._data.reverse() + else: + data.reverse() + + @staticmethod + def shuffle(data, rnd: _typing.Optional['Random'] = None) -> None: + if isinstance(data, ArrayList): + lst = data._data + elif isinstance(data, Vector): + lst = data._data + else: + lst = data + if rnd: + _py_random.shuffle(lst) + else: + _py_random.shuffle(lst) + + @staticmethod + def swap(data, i: int, j: int) -> None: + if isinstance(data, ArrayList): + data._data[i], data._data[j] = data._data[j], data._data[i] + elif isinstance(data, Vector): + data._data[i], data._data[j] = data._data[j], data._data[i] + else: + data[i], data[j] = data[j], data[i] + + @staticmethod + def fill(data, obj) -> None: + if isinstance(data, ArrayList): + for i in range(len(data._data)): + data._data[i] = obj + elif isinstance(data, Vector): + for i in range(len(data._data)): + data._data[i] = obj + else: + for i in range(len(data)): + data[i] = obj + + @staticmethod + def copy(dest, src) -> None: + if isinstance(dest, ArrayList) and isinstance(src, ArrayList): + dest._data[:len(src._data)] = src._data[:] + elif isinstance(dest, Vector) and isinstance(src, Vector): + dest._data[:len(src._data)] = src._data[:] + else: + dest[:len(src)] = src[:] + + @staticmethod + def min(coll, comp=None): + if comp: + return min(coll, key=comp) + return min(coll) + + @staticmethod + def max(coll, comp=None): + if comp: + return max(coll, key=comp) + return max(coll) + + @staticmethod + def rotate(data, distance: int) -> None: + if isinstance(data, ArrayList): + lst = data._data + elif isinstance(data, Vector): + lst = data._data + else: + lst = data + n = len(lst) + if n == 0: + return + d = distance % n + lst[:] = lst[-d:] + lst[:-d] + + @staticmethod + def indexOfSubList(source, target) -> int: + if isinstance(source, ArrayList): + src = source._data + else: + src = list(source) + if isinstance(target, ArrayList): + tgt = target._data + else: + tgt = list(target) + for i in range(len(src) - len(tgt) + 1): + if src[i:i + len(tgt)] == tgt: + return i + return -1 + + @staticmethod + def lastIndexOfSubList(source, target) -> int: + if isinstance(source, ArrayList): + src = source._data + else: + src = list(source) + if isinstance(target, ArrayList): + tgt = target._data + else: + tgt = list(target) + for i in range(len(src) - len(tgt), -1, -1): + if src[i:i + len(tgt)] == tgt: + return i + return -1 + + @staticmethod + def replaceAll(data, old_val, new_val) -> bool: + if isinstance(data, ArrayList): + lst = data._data + elif isinstance(data, Vector): + lst = data._data + else: + lst = data + found = False + for i, v in enumerate(lst): + if v == old_val: + lst[i] = new_val + found = True + return found + + @staticmethod + def binarySearch(data, key): + if isinstance(data, ArrayList): + lst = data._data + else: + lst = list(data) + idx = _bisect.bisect_left(lst, key) + if idx < len(lst) and lst[idx] == key: + return idx + return -1 + + @staticmethod + def disjoint(c1, c2) -> bool: + set1 = set(c1) + set2 = set(c2) + return set1.isdisjoint(set2) + + @staticmethod + def frequency(c, obj) -> int: + count = 0 + for item in c: + if item == obj: + count += 1 + return count + + @staticmethod + def nCopies(n: int, obj) -> list: + return [obj] * n + + @staticmethod + def singleton(obj) -> set: + return {obj} + + @staticmethod + def singletonList(obj) -> list: + return [obj] + + @staticmethod + def singletonMap(key, value) -> dict: + return {key: value} + + @staticmethod + def unmodifiableList(data) -> list: + if isinstance(data, ArrayList): + return tuple(data._data) + return tuple(data) + + @staticmethod + def unmodifiableSet(data) -> frozenset: + if isinstance(data, HashSet): + return frozenset(data._data) + return frozenset(data) + + @staticmethod + def unmodifiableMap(data) -> dict: + if isinstance(data, HashMap): + return dict(data._data) + return dict(data) + + @staticmethod + def synchronizedList(data): + lock = _threading.Lock() + + class _SyncList: + def __init__(self, lst): + self._lock = lock + self._lst = lst + + def __getattr__(self, name): + with self._lock: + return getattr(self._lst, name) + + def add(self, obj): + with self._lock: + return self._lst.add(obj) + + def get(self, idx): + with self._lock: + return self._lst[idx] + + def set(self, idx, val): + with self._lock: + old = self._lst[idx] + self._lst[idx] = val + return old + + def remove(self, obj): + with self._lock: + return self._lst.remove(obj) + + def size(self): + with self._lock: + return len(self._lst) + + def __len__(self): + with self._lock: + return len(self._lst) + + def __getitem__(self, idx): + with self._lock: + return self._lst[idx] + + def __setitem__(self, idx, val): + with self._lock: + self._lst[idx] = val + + def __iter__(self): + with self._lock: + return iter(list(self._lst)) + + return _SyncList(data) + + @staticmethod + def synchronizedSet(data): + lock = _threading.Lock() + + class _SyncSet: + def __init__(self, s): + self._lock = lock + self._s = s + + def add(self, obj): + with self._lock: + return self._s.add(obj) + + def remove(self, obj): + with self._lock: + return self._s.remove(obj) + + def __contains__(self, obj): + with self._lock: + return obj in self._s + + def __iter__(self): + with self._lock: + return iter(list(self._s)) + + def __len__(self): + with self._lock: + return len(self._s) + + return _SyncSet(data) + + @staticmethod + def synchronizedMap(data): + lock = _threading.Lock() + + class _SyncMap: + def __init__(self, m): + self._lock = lock + self._m = m + + def put(self, k, v): + with self._lock: + return self._m.put(k, v) + + def get(self, k): + with self._lock: + return self._m.get(k) + + def remove(self, k): + with self._lock: + return self._m.remove(k) + + def __getitem__(self, k): + with self._lock: + return self._m[k] + + def __setitem__(self, k, v): + with self._lock: + self._m[k] = v + + def __contains__(self, k): + with self._lock: + return k in self._m + + def __len__(self): + with self._lock: + return len(self._m) + + def keys(self): + with self._lock: + return list(self._m.keys()) + + return _SyncMap(data) + + @staticmethod + def enumaration(c) -> Enumeration: + return Enumeration(c) + + @staticmethod + def list(enum: Enumeration) -> ArrayList: + items = [] + while enum.hasMoreElements(): + items.append(enum.nextElement()) + return ArrayList(items) + + @staticmethod + def newSetFromMap(m) -> HashSet: + class _SetFromMap(HashSet): + def __init__(self, backing_map): + super().__init__() + self._backing = backing_map + + def add(self, obj): + return self._backing.put(obj, True) is None + + def iterator(self): + return Iterator(self._backing.keySet()) + + def size(self): + return self._backing.size() + + def remove(self, obj): + return self._backing.remove(obj) is not None + + def contains(self, obj): + return self._backing.containsKey(obj) + + def clear(self): + self._backing.clear() + + return _SetFromMap(m) + + @staticmethod + def asLifoQueue(deque) -> 'Collections._LifoQueue': + class _LifoQueue: + def __init__(self, dq): + self._dq = dq + + def add(self, e): + self._dq.addFirst(e) + return True + + def offer(self, e): + return self.add(e) + + def remove(self): + if len(self._dq) == 0: + raise NoSuchElementException() + return self._dq.removeFirst() + + def poll(self): + return self._dq.pollFirst() + + def element(self): + if len(self._dq) == 0: + raise NoSuchElementException() + return self._dq.getFirst() + + def peek(self): + return self._dq.peekFirst() + + def size(self): + return self._dq.size() + + def isEmpty(self): + return self._dq.isEmpty() + + return _LifoQueue(deque) + + +# ============================================================ +# Arrays +# ============================================================ + +class Arrays: + """java.util.Arrays — static utility methods""" + @staticmethod + def asList(*args) -> ArrayList: + if len(args) == 1 and isinstance(args[0], (list, tuple)): + return ArrayList(args[0]) + return ArrayList(list(args)) + + @staticmethod + def sort(a, from_index: int = 0, to_index: int = None) -> None: + if to_index is None: + a.sort() + else: + a[from_index:to_index] = sorted(a[from_index:to_index]) + + @staticmethod + def parallelSort(a, from_index: int = 0, to_index: int = None) -> None: + Arrays.sort(a, from_index, to_index) + + @staticmethod + def binarySearch(a, key, from_index: int = 0, to_index: int = None) -> int: + if to_index is None: + lst = a + else: + lst = a[from_index:to_index] + idx = _bisect.bisect_left(lst, key) + if idx < len(lst) and lst[idx] == key: + return idx + from_index + return -(idx + from_index + 1) + + @staticmethod + def fill(a, val, from_index: int = 0, to_index: int = None) -> None: + if to_index is None: + for i in range(len(a)): + a[i] = val + else: + for i in range(from_index, to_index): + a[i] = val + + @staticmethod + def copyOf(original, new_length: int) -> list: + return list(original[:new_length]) + [None] * max(0, new_length - len(original)) + + @staticmethod + def copyOfRange(original, from_index: int, to_index: int) -> list: + return list(original[from_index:to_index]) + + @staticmethod + def equals(a, b) -> bool: + if len(a) != len(b): + return False + return all(x == y for x, y in zip(a, b)) + + @staticmethod + def deepEquals(a, b) -> bool: + if len(a) != len(b): + return False + return all(_deep_eq(x, y) for x, y in zip(a, b)) + + @staticmethod + def hashCode(a) -> int: + if a is None: + return 0 + h = 1 + for v in a: + h = (31 * h + hash(v)) & 0x7FFFFFFF + return h + + @staticmethod + def deepHashCode(a) -> int: + if a is None: + return 0 + h = 1 + for v in a: + if isinstance(v, (list, tuple)): + h = (31 * h + Arrays.deepHashCode(v)) & 0x7FFFFFFF + else: + h = (31 * h + hash(v)) & 0x7FFFFFFF + return h + + @staticmethod + def toString(a) -> str: + if a is None: + return "null" + return "[" + ", ".join(str(x) for x in a) + "]" + + @staticmethod + def deepToString(a) -> str: + if a is None: + return "null" + return "[" + ", ".join(_deep_str(x) for x in a) + "]" + + @staticmethod + def stream(a) -> 'Arrays._ArrayStream': + class _ArrayStream: + def __init__(self, arr): + self._arr = arr + self._idx = 0 + + def filter(self, pred): + return _ArrayStream([x for x in self._arr if pred(x)]) + + def map(self, func): + return _ArrayStream([func(x) for x in self._arr]) + + def forEach(self, action): + for x in self._arr: + action(x) + + def toArray(self): + return list(self._arr) + + def count(self): + return len(self._arr) + + return _ArrayStream(a) + + @staticmethod + def setAll(a, generator) -> None: + for i in range(len(a)): + a[i] = generator(i) + + @staticmethod + def parallelSetAll(a, generator) -> None: + Arrays.setAll(a, generator) + + @staticmethod + def spliterator(a) -> Iterator: + return Iterator(a) + + +def _deep_eq(x, y) -> bool: + if isinstance(x, (list, tuple)) and isinstance(y, (list, tuple)): + return Arrays.deepEquals(x, y) + return x == y + + +def _deep_str(x) -> str: + if isinstance(x, (list, tuple)): + return Arrays.deepToString(x) + return str(x) + + +# ============================================================ +# Date +# ============================================================ + +class Date(Stub): + """java.util.Date — wraps Python datetime""" + def __init__(self, *args): + super().__init__() + if len(args) == 0: + self._dt = _datetime.datetime.now() + elif len(args) == 1: + if isinstance(args[0], (int, float)): + self._dt = _datetime.datetime.fromtimestamp(args[0] / 1000.0) + elif isinstance(args[0], str): + self._dt = _datetime.datetime.fromisoformat(args[0]) + elif isinstance(args[0], _datetime.datetime): + self._dt = args[0] + elif isinstance(args[0], Date): + self._dt = args[0]._dt + else: + self._dt = _datetime.datetime.now() + elif len(args) >= 6: + year = args[0] - 1900 + month = args[1] + day = args[2] + hour = args[3] if len(args) > 3 else 0 + minute = args[4] if len(args) > 4 else 0 + second = args[5] if len(args) > 5 else 0 + self._dt = _datetime.datetime(year + 1900, month + 1, day, hour, minute, second) + else: + self._dt = _datetime.datetime.now() + + def getTime(self) -> int: + return int(self._dt.timestamp() * 1000) + + def setTime(self, time_millis: int) -> None: + self._dt = _datetime.datetime.fromtimestamp(time_millis / 1000.0) + + def before(self, when: 'Date') -> bool: + return self._dt < when._dt + + def after(self, when: 'Date') -> bool: + return self._dt > when._dt + + def compareTo(self, another: 'Date') -> int: + return (self._dt > another._dt) - (self._dt < another._dt) + + def equals(self, other) -> bool: + if isinstance(other, Date): + return self._dt == other._dt + return False + + def hashCode(self) -> int: + return hash(self._dt) & 0x7FFFFFFF + + def toString(self) -> str: + return self._dt.strftime("%a %b %d %H:%M:%S %z %Y") + + def clone(self) -> 'Date': + return Date(self._dt) + + def getYear(self) -> int: + return self._dt.year - 1900 + + def getMonth(self) -> int: + return self._dt.month - 1 + + def getDay(self) -> int: + return self._dt.weekday() + + def getHours(self) -> int: + return self._dt.hour + + def getMinutes(self) -> int: + return self._dt.minute + + def getSeconds(self) -> int: + return self._dt.second + + def getDate(self) -> int: + return self._dt.day + + def setYear(self, year: int) -> None: + self._dt = self._dt.replace(year=year + 1900) + + def setMonth(self, month: int) -> None: + self._dt = self._dt.replace(month=month + 1) + + def setDate(self, date: int) -> None: + self._dt = self._dt.replace(day=date) + + def setHours(self, hours: int) -> None: + self._dt = self._dt.replace(hour=hours) + + def setMinutes(self, minutes: int) -> None: + self._dt = self._dt.replace(minute=minutes) + + def setSeconds(self, seconds: int) -> None: + self._dt = self._dt.replace(second=seconds) + + def toGMTString(self) -> str: + return self._dt.strftime("%d %b %Y %H:%M:%S GMT") + + def toLocaleString(self) -> str: + return self._dt.strftime("%Y-%m-%d %H:%M:%S") + + @staticmethod + def parse(s: str) -> 'Date': + try: + return Date(_datetime.datetime.fromisoformat(s)) + except ValueError: + return Date() + + @staticmethod + def fromInstant(instant) -> 'Date': + return Date(instant.toEpochMilli()) + + def toInstant(self): + return self._dt + + def __str__(self): + return self.toString() + + def __repr__(self): + return f"Date({self.getTime()})" + + +# ============================================================ +# Calendar / GregorianCalendar +# ============================================================ + +class Calendar(Stub): + """java.util.Calendar — wraps Python datetime with Java field system""" + # Field constants + YEAR = 1 + MONTH = 2 + DAY_OF_MONTH = 5 + DAY_OF_YEAR = 6 + DAY_OF_WEEK = 7 + DAY_OF_WEEK_IN_MONTH = 8 + AM_PM = 9 + HOUR = 10 + HOUR_OF_DAY = 11 + MINUTE = 12 + SECOND = 13 + MILLISECOND = 14 + WEEK_OF_MONTH = 15 + WEEK_OF_YEAR = 16 + ERA = 17 + ZONE_OFFSET = 18 + DST_OFFSET = 19 + FIELD_COUNT = 20 + + # Day of week constants + SUNDAY = 1 + MONDAY = 2 + TUESDAY = 3 + WEDNESDAY = 4 + THURSDAY = 5 + FRIDAY = 6 + SATURDAY = 7 + + # Month constants + JANUARY = 0 + FEBRUARY = 1 + MARCH = 2 + APRIL = 3 + MAY = 4 + JUNE = 5 + JULY = 6 + AUGUST = 7 + SEPTEMBER = 8 + OCTOBER = 9 + NOVEMBER = 10 + DECEMBER = 11 + UNDECIMBER = 12 + + # AM/PM + AM = 0 + PM = 1 + + def __init__(self): + super().__init__() + self._dt = _datetime.datetime.now() + self._fields: Dict[int, int] = {} + self._is_set: Dict[int, bool] = {} + self._lenient = True + self._first_day_of_week = Calendar.SUNDAY + self._minimal_days_in_first_week = 1 + self._timezone = _datetime.timezone.utc + self._set_fields_from_dt() + + def _set_fields_from_dt(self) -> None: + self._fields[Calendar.YEAR] = self._dt.year + self._fields[Calendar.MONTH] = self._dt.month - 1 + self._fields[Calendar.DAY_OF_MONTH] = self._dt.day + self._fields[Calendar.DAY_OF_YEAR] = self._dt.timetuple().tm_yday + self._fields[Calendar.DAY_OF_WEEK] = self._dt.isoweekday() % 7 + 1 + self._fields[Calendar.HOUR] = self._dt.hour % 12 + self._fields[Calendar.HOUR_OF_DAY] = self._dt.hour + self._fields[Calendar.MINUTE] = self._dt.minute + self._fields[Calendar.SECOND] = self._dt.second + self._fields[Calendar.MILLISECOND] = self._dt.microsecond // 1000 + self._fields[Calendar.AM_PM] = Calendar.AM if self._dt.hour < 12 else Calendar.PM + self._fields[Calendar.WEEK_OF_YEAR] = self._dt.isocalendar()[1] + self._fields[Calendar.ERA] = 1 # AD = 1 + + def get(self, field: int) -> int: + return self._fields.get(field, 0) + + def set(self, field: int, value: int) -> None: + self._fields[field] = value + self._is_set[field] = True + + def setTime(self, date: 'Date') -> None: + self._dt = date._dt + self._set_fields_from_dt() + + def getTime(self) -> Date: + return Date(self._dt) + + def getTimeInMillis(self) -> int: + return int(self._dt.timestamp() * 1000) + + def setTimeInMillis(self, millis: int) -> None: + self._dt = _datetime.datetime.fromtimestamp(millis / 1000.0) + self._set_fields_from_dt() + + def add(self, field: int, amount: int) -> None: + if field == Calendar.YEAR: + self._dt = self._dt.replace(year=self._dt.year + amount) + elif field == Calendar.MONTH: + new_month = self._dt.month - 1 + amount + new_year = self._dt.year + new_month // 12 + new_month = new_month % 12 + try: + self._dt = self._dt.replace(year=new_year, month=new_month + 1) + except ValueError: + self._dt = self._dt.replace(year=new_year, month=new_month + 1, day=28) + elif field == Calendar.DAY_OF_MONTH: + self._dt += _datetime.timedelta(days=amount) + elif field == Calendar.HOUR_OF_DAY or field == Calendar.HOUR: + self._dt += _datetime.timedelta(hours=amount) + elif field == Calendar.MINUTE: + self._dt += _datetime.timedelta(minutes=amount) + elif field == Calendar.SECOND: + self._dt += _datetime.timedelta(seconds=amount) + elif field == Calendar.MILLISECOND: + self._dt += _datetime.timedelta(milliseconds=amount) + elif field == Calendar.WEEK_OF_YEAR or field == Calendar.WEEK_OF_MONTH: + self._dt += _datetime.timedelta(weeks=amount) + self._set_fields_from_dt() + + def roll(self, field: int, amount: int) -> None: + if field == Calendar.MONTH: + old_month = self._dt.month + new_month = ((old_month - 1 + amount) % 12) + 1 + try: + self._dt = self._dt.replace(month=new_month) + except ValueError: + self._dt = self._dt.replace(month=new_month, day=28) + elif field == Calendar.DAY_OF_MONTH: + import calendar as _cal + max_day = _cal.monthrange(self._dt.year, self._dt.month)[1] + new_day = ((self._dt.day - 1 + amount) % max_day) + 1 + self._dt = self._dt.replace(day=new_day) + else: + self.add(field, amount) + self._set_fields_from_dt() + + def before(self, when: 'Calendar') -> bool: + return self._dt < when._dt + + def after(self, when: 'Calendar') -> bool: + return self._dt > when._dt + + def compareTo(self, another: 'Calendar') -> int: + return (self._dt > another._dt) - (self._dt < another._dt) + + def equals(self, other) -> bool: + if isinstance(other, Calendar): + return self._dt == other._dt + return False + + def hashCode(self) -> int: + return hash(self._dt) & 0x7FFFFFFF + + def toString(self) -> str: + return str(self._dt) + + def isLenient(self) -> bool: + return self._lenient + + def setLenient(self, lenient: bool) -> None: + self._lenient = lenient + + def getFirstDayOfWeek(self) -> int: + return self._first_day_of_week + + def setFirstDayOfWeek(self, value: int) -> None: + self._first_day_of_week = value + + def getMinimalDaysInFirstWeek(self) -> int: + return self._minimal_days_in_first_week + + def setMinimalDaysInFirstWeek(self, value: int) -> None: + self._minimal_days_in_first_week = value + + def getActualMaximum(self, field: int) -> int: + import calendar as _cal + if field == Calendar.DAY_OF_MONTH: + return _cal.monthrange(self._dt.year, self._dt.month)[1] + if field == Calendar.DAY_OF_YEAR: + return 366 if self._dt.year % 4 == 0 and (self._dt.year % 100 != 0 or self._dt.year % 400 == 0) else 365 + if field == Calendar.MONTH: + return 11 + if field == Calendar.HOUR: + return 11 + if field == Calendar.HOUR_OF_DAY: + return 23 + if field == Calendar.MINUTE: + return 59 + if field == Calendar.SECOND: + return 59 + if field == Calendar.MILLISECOND: + return 999 + return 0 + + def getActualMinimum(self, field: int) -> int: + return 0 + + def getDisplayName(self, field: int, style: int, locale: 'Locale') -> str: + return str(self.get(field)) + + @staticmethod + def getInstance() -> 'GregorianCalendar': + return GregorianCalendar() + + +class GregorianCalendar(Calendar): + """java.util.GregorianCalendar""" + BC = 0 + AD = 1 + + def __init__(self, *args): + super().__init__() + if len(args) == 0: + self._dt = _datetime.datetime.now() + elif len(args) == 3: + year, month, day = args + self._dt = _datetime.datetime(year, month + 1, day) + elif len(args) >= 5: + year, month, day, hour, minute = args[:5] + second = args[5] if len(args) > 5 else 0 + self._dt = _datetime.datetime(year, month + 1, day, hour, minute, second) + else: + self._dt = _datetime.datetime.now() + self._set_fields_from_dt() + + def isLeapYear(self, year: int) -> bool: + return (year % 4 == 0 and year % 100 != 0) or year % 400 == 0 + + def getGreatestMinimum(self, field: int) -> int: + return self.getActualMinimum(field) + + def getLeastMaximum(self, field: int) -> int: + return self.getActualMaximum(field) + + def getMaximum(self, field: int) -> int: + if field == Calendar.DAY_OF_MONTH: + return 31 + if field == Calendar.DAY_OF_YEAR: + return 366 + if field == Calendar.MONTH: + return 11 + return self.getActualMaximum(field) + + def getMinimum(self, field: int) -> int: + return 0 + + +# ============================================================ +# UUID +# ============================================================ + +class UUID(Stub): + """java.util.UUID — wraps Python uuid module""" + def __init__(self, most_sig_bits: int, least_sig_bits: int): + super().__init__() + self._most_sig_bits = most_sig_bits & 0xFFFFFFFFFFFFFFFF + self._least_sig_bits = least_sig_bits & 0xFFFFFFFFFFFFFFFF + # Rebuild a Python UUID from the bits for string representation + hex_str = f"{self._most_sig_bits:016x}{self._least_sig_bits:016x}" + self._uuid = _uuid.UUID(hex_str) + + @staticmethod + def randomUUID() -> 'UUID': + u = _uuid.uuid4() + most = u.int >> 64 + least = u.int & 0xFFFFFFFFFFFFFFFF + return UUID(most, least) + + @staticmethod + def nameUUIDFromBytes(name: bytes) -> 'UUID': + u = _uuid.uuid3(_uuid.NAMESPACE_DNS, name.decode('utf-8', errors='replace')) + most = u.int >> 64 + least = u.int & 0xFFFFFFFFFFFFFFFF + return UUID(most, least) + + @staticmethod + def fromString(name: str) -> 'UUID': + u = _uuid.UUID(name) + most = u.int >> 64 + least = u.int & 0xFFFFFFFFFFFFFFFF + return UUID(most, least) + + def getMostSignificantBits(self) -> int: + return self._most_sig_bits + + def getLeastSignificantBits(self) -> int: + return self._least_sig_bits + + def version(self) -> int: + return self._uuid.version + + def variant(self) -> int: + return self._uuid.variant + + def timestamp(self) -> int: + if self.version() != 1: + raise UnsupportedOperationException("Not a time-based UUID") + return self._uuid.time + + def clockSequence(self) -> int: + if self.version() != 1: + raise UnsupportedOperationException("Not a time-based UUID") + return self._uuid.clock_seq + + def node(self) -> int: + if self.version() != 1: + raise UnsupportedOperationException("Not a time-based UUID") + return self._uuid.node + + def compareTo(self, other: 'UUID') -> int: + return (self._uuid.int > other._uuid.int) - (self._uuid.int < other._uuid.int) + + def equals(self, other) -> bool: + if isinstance(other, UUID): + return self._uuid == other._uuid + return False + + def hashCode(self) -> int: + return hash(self._uuid) & 0x7FFFFFFF + + def toString(self) -> str: + return str(self._uuid) + + def __str__(self): + return str(self._uuid) + + def __repr__(self): + return f"UUID({str(self._uuid)})" + + +# ============================================================ +# StringTokenizer +# ============================================================ + +class StringTokenizer(Stub): + """java.util.StringTokenizer — simple tokenizer""" + def __init__(self, s: str, delim: str = " \t\n\r\f", return_delims: bool = False): + super().__init__() + self._str = str(s) + self._delim = delim + self._return_delims = return_delims + self._tokens = self._tokenize() + self._index = 0 + + def _tokenize(self): + if self._return_delims: + tokens = [] + buf = "" + for ch in self._str: + if ch in self._delim: + if buf: + tokens.append(buf) + buf = "" + tokens.append(ch) + else: + buf += ch + if buf: + tokens.append(buf) + return tokens + import re as _re + pattern = f"[{_re.escape(self._delim)}]+" + return [t for t in _re.split(pattern, self._str) if t] + + def countTokens(self) -> int: + return len(self._tokens) + + def hasMoreTokens(self) -> bool: + return self._index < len(self._tokens) + + def nextToken(self, delim: str = None) -> str: + if delim is not None: + self._delim = delim + remaining = self._str[self._index:] + st = StringTokenizer(remaining, delim, self._return_delims) + self._tokens = st._tokens + self._index = 0 + if not self.hasMoreTokens(): + raise NoSuchElementException() + tok = self._tokens[self._index] + self._index += 1 + return tok + + def hasMoreElements(self) -> bool: + return self.hasMoreTokens() + + def nextElement(self): + return self.nextToken() + + def __iter__(self): + return self + + def __next__(self): + if not self.hasMoreTokens(): + raise StopIteration + return self.nextToken() + + +# ============================================================ +# Optional +# ============================================================ + +class Optional(Stub): + """java.util.Optional — wraps value or None""" + def __init__(self, value=None): + super().__init__() + self._value = value + + @staticmethod + def empty() -> 'Optional': + return Optional(None) + + @staticmethod + def of(value) -> 'Optional': + if value is None: + raise TypeError("value is null") + return Optional(value) + + @staticmethod + def ofNullable(value) -> 'Optional': + return Optional(value) + + def get(self): + if self._value is None: + raise NoSuchElementException("No value present") + return self._value + + def isPresent(self) -> bool: + return self._value is not None + + def isEmpty(self) -> bool: + return self._value is None + + def ifPresent(self, consumer) -> None: + if self._value is not None: + consumer(self._value) + + def ifPresentOrElse(self, action, empty_action) -> None: + if self._value is not None: + action(self._value) + else: + empty_action() + + def filter(self, predicate) -> 'Optional': + if self._value is None: + return self + return Optional.ofNullable(self._value) if predicate(self._value) else Optional.empty() + + def map(self, mapper) -> 'Optional': + if self._value is None: + return Optional.empty() + return Optional.ofNullable(mapper(self._value)) + + def flatMap(self, mapper) -> 'Optional': + if self._value is None: + return Optional.empty() + return mapper(self._value) + + def orElse(self, other): + return self._value if self._value is not None else other + + def orElseGet(self, supplier): + if self._value is not None: + return self._value + return supplier() + + def orElseThrow(self, exception_supplier=None): + if self._value is not None: + return self._value + if exception_supplier: + raise exception_supplier() + raise NoSuchElementException("No value present") + + def stream(self): + if self._value is not None: + return [self._value] + return [] + + def equals(self, other) -> bool: + if isinstance(other, Optional): + return self._value == other._value + return False + + def hashCode(self) -> int: + return hash(self._value) & 0x7FFFFFFF if self._value is not None else 0 + + def toString(self) -> str: + if self._value is None: + return "Optional.empty" + return f"Optional[{self._value}]" + + def __str__(self): + return self.toString() + + def __repr__(self): + return self.toString() + + def __bool__(self): + return self._value is not None + + +# ============================================================ +# Random +# ============================================================ + +class Random(Stub): + """java.util.Random — wraps Python random module""" + def __init__(self, seed: _typing.Optional[int] = None): + super().__init__() + self._rng = _py_random.Random(seed) + + def setSeed(self, seed: int) -> None: + self._rng.seed(seed) + + def nextInt(self, bound: _typing.Optional[int] = None) -> int: + if bound is None: + return self._rng.randint(-2147483648, 2147483647) + if bound <= 0: + raise IllegalArgumentException("bound must be positive") + return self._rng.randrange(0, bound) + + def nextLong(self) -> int: + return (self._rng.randint(-2147483648, 2147483647) << 32) | self._rng.randint(0, 0xFFFFFFFF) + + def nextBoolean(self) -> bool: + return self._rng.random() < 0.5 + + def nextFloat(self) -> float: + return self._rng.random() + + def nextDouble(self) -> float: + return self._rng.random() + + def nextGaussian(self) -> float: + return self._rng.gauss(0.0, 1.0) + + def nextBytes(self, bytes_arr: bytearray) -> None: + for i in range(len(bytes_arr)): + bytes_arr[i] = self._rng.randint(0, 255) + + def next(self, bits: int) -> int: + return self._rng.getrandbits(bits) & ((1 << bits) - 1) + + @staticmethod + def create() -> 'Random': + return Random() + + +# ============================================================ +# Properties +# ============================================================ + +class Properties(HashMap): + """java.util.Properties — extends Hashtable-like dict with load/store""" + def __init__(self, defaults: 'Properties' = None): + super().__init__() + self._defaults = defaults + + def setProperty(self, key: str, value: str) -> None: + self.put(str(key), str(value)) + + def getProperty(self, key: str, default_value: str = None) -> _typing.Optional[str]: + val = self._data.get(str(key)) + if val is not None: + return str(val) + if self._defaults is not None: + return self._defaults.getProperty(key, default_value) + return default_value + + def load(self, in_stream) -> None: + if isinstance(in_stream, (str, bytes)): + lines = in_stream.splitlines() if isinstance(in_stream, str) else in_stream.decode('utf-8').splitlines() + else: + lines = in_stream.read().splitlines() if hasattr(in_stream, 'read') else in_stream.splitlines() + for line in lines: + line = line.strip() + if not line or line.startswith('#') or line.startswith('!'): + continue + if '=' in line: + key, val = line.split('=', 1) + elif ':' in line: + key, val = line.split(':', 1) + else: + key = line + val = "" + key = key.strip() + val = val.strip() + self._data[key] = val + + def store(self, out_stream, comments: str = None) -> None: + lines = [] + if comments: + lines.append(f"#{comments}") + lines.append(f"# {_datetime.datetime.now().isoformat()}") + for k, v in self._data.items(): + escaped_val = str(v).replace("\\", "\\\\").replace(" ", "\\ ") + lines.append(f"{k}={escaped_val}") + content = "\n".join(lines) + "\n" + if isinstance(out_stream, _io.IOBase): + out_stream.write(content) + else: + out_stream.write(content) # type: ignore + + def list(self, out_stream) -> None: + for k, v in self._data.items(): + out_stream.write(f"{k}={v}\n") + + def propertyNames(self): + return Iterator(self._data.keys()) + + def stringPropertyNames(self) -> set: + names = set() + for k in self._data.keys(): + if isinstance(k, str): + names.add(k) + if self._defaults: + names.update(self._defaults.stringPropertyNames()) + return names + + def loadFromXML(self, in_stream) -> None: + import xml.etree.ElementTree as _ET + tree = _ET.parse(in_stream) + root = tree.getroot() + for entry in root.findall('entry'): + key = entry.get('key', '') + self._data[key] = entry.text or '' + + def storeToXML(self, out_stream, comment: str = None) -> None: + import xml.etree.ElementTree as _ET + root = _ET.Element('properties') + if comment: + c = _ET.SubElement(root, 'comment') + c.text = comment + for k, v in self._data.items(): + entry = _ET.SubElement(root, 'entry') + entry.set('key', str(k)) + entry.text = str(v) + tree = _ET.ElementTree(root) + tree.write(out_stream, xml_declaration=True, encoding='UTF-8') + + def get(self, key): + val = self._data.get(key) + if val is not None: + return val + return self._defaults.get(key) if self._defaults else None + + def put(self, key, value): + return super().put(str(key), str(value)) + + def toString(self) -> str: + items = ", ".join(f"{k}={v}" for k, v in self._data.items()) + return f"{{{items}}}" + + +# ============================================================ +# EnumSet +# ============================================================ + +class EnumSet(HashSet): + """java.util.EnumSet — simple implementation using set""" + def __init__(self, c=None): + super().__init__() + self._element_type = None + if c is not None: + if isinstance(c, EnumSet): + self._data = set(c._data) + self._element_type = c._element_type + elif isinstance(c, type): + self._element_type = c + elif isinstance(c, _IterableABC): + for item in c: + self._data.add(item) + if self._element_type is None: + self._element_type = item.__class__ + + @staticmethod + def allOf(element_type) -> 'EnumSet': + es = EnumSet() + es._element_type = element_type + if hasattr(element_type, '_values'): + for v in element_type._values(): + es._data.add(v) + elif hasattr(element_type, '__members__'): + for name, member in element_type.__members__.items(): + es._data.add(member) + return es + + @staticmethod + def noneOf(element_type) -> 'EnumSet': + es = EnumSet() + es._element_type = element_type + return es + + @staticmethod + def range(element_type, from_val, to_val) -> 'EnumSet': + es = EnumSet() + es._element_type = element_type + started = False + if hasattr(element_type, '_values'): + for v in element_type._values(): + if v == from_val: + started = True + if started: + es._data.add(v) + if v == to_val: + break + elif hasattr(element_type, '__members__'): + for name, member in element_type.__members__.items(): + if member == from_val: + started = True + if started: + es._data.add(member) + if member == to_val: + break + return es + + @staticmethod + def copyOf(es) -> 'EnumSet': + return EnumSet(es) + + def complementOf(self, other: 'EnumSet') -> 'EnumSet': + result = EnumSet.noneOf(self._element_type) + if self._element_type and hasattr(self._element_type, '_values'): + for v in self._element_type._values(): + if v not in other._data: + result._data.add(v) + return result + + +# ============================================================ +# EnumMap +# ============================================================ + +class EnumMap(HashMap): + """java.util.EnumMap — simple implementation using dict""" + def __init__(self, key_type=None, m=None): + super().__init__() + self._key_type = key_type + if m is not None: + if isinstance(m, EnumMap): + self._data.update(m._data) + self._key_type = m._key_type + elif isinstance(m, dict): + self._data.update(m) + else: + for k, v in m.items(): + self._data[k] = v + + def put(self, key, value): + if self._key_type is None: + self._key_type = key.__class__ + return super().put(key, value) + + def keySet(self): + return set(self._data.keys()) + + +# ============================================================ +# Objects +# ============================================================ + +class Objects: + """java.util.Objects — static helper methods""" + @staticmethod + def equals(a, b) -> bool: + return a == b + + @staticmethod + def deepEquals(a, b) -> bool: + return _deep_eq(a, b) + + @staticmethod + def hashCode(o) -> int: + return 0 if o is None else (hash(o) & 0x7FFFFFFF) + + @staticmethod + def hash(*values) -> int: + return Arrays.hashCode(values) + + @staticmethod + def toString(o, null_default: str = "null") -> str: + if o is None: + return null_default + return str(o) + + @staticmethod + def compare(a, b, cmp) -> int: + if cmp: + return cmp(a, b) + return (a > b) - (a < b) + + @staticmethod + def requireNonNull(obj, message: Any = None): + if obj is None: + if message is None: + raise TypeError("null") + raise TypeError(str(message) if not callable(message) else message.get()) + return obj + + @staticmethod + def nonNull(obj) -> bool: + return obj is not None + + @staticmethod + def isNull(obj) -> bool: + return obj is None + + @staticmethod + def requireNonNullElse(obj, default): + return obj if obj is not None else default + + @staticmethod + def requireNonNullElseGet(obj, supplier): + return obj if obj is not None else supplier() + + @staticmethod + def checkIndex(index: int, length: int) -> int: + if index < 0 or index >= length: + raise IndexError(f"Index {index} out of bounds [0, {length})") + return index + + @staticmethod + def checkFromToIndex(from_index: int, to_index: int, length: int) -> int: + if from_index < 0 or from_index > to_index or to_index > length: + raise IndexError(f"Range [{from_index}, {to_index}) out of bounds [0, {length})") + return from_index + + @staticmethod + def checkFromIndexSize(from_index: int, size: int, length: int) -> int: + if from_index < 0 or size < 0 or from_index + size > length: + raise IndexError(f"Range [{from_index}, {from_index + size}) out of bounds [0, {length})") + return from_index + + +# ============================================================ +# Locale +# ============================================================ + +class Locale(Stub): + """java.util.Locale — basic locale representation""" + ENGLISH = None + FRENCH = None + GERMAN = None + ITALIAN = None + JAPANESE = None + KOREAN = None + CHINESE = None + SIMPLIFIED_CHINESE = None + TRADITIONAL_CHINESE = None + FRANCE = None + GERMANY = None + ITALY = None + JAPAN = None + KOREA = None + CHINA = None + UK = None + US = None + CANADA = None + CANADA_FRENCH = None + ROOT = None + + def __init__(self, language: str = "", country: str = "", variant: str = ""): + super().__init__() + self._language = language + self._country = country + self._variant = variant + + @staticmethod + def getDefault() -> 'Locale': + return Locale("en", "US") + + @staticmethod + def setDefault(locale: 'Locale') -> None: + pass + + def getLanguage(self) -> str: + return self._language + + def getCountry(self) -> str: + return self._country + + def getVariant(self) -> str: + return self._variant + + def getDisplayLanguage(self, locale: 'Locale' = None) -> str: + return self._language + + def getDisplayCountry(self, locale: 'Locale' = None) -> str: + return self._country + + def getDisplayName(self, locale: 'Locale' = None) -> str: + parts = [self._language] + if self._country: + parts.append(self._country) + if self._variant: + parts.append(self._variant) + return "_".join(parts) + + def toString(self) -> str: + return self.getDisplayName() + + def toLanguageTag(self) -> str: + tag = self._language + if self._country: + tag += "-" + self._country + if self._variant: + tag += "-" + self._variant + return tag + + @staticmethod + def forLanguageTag(tag: str) -> 'Locale': + parts = tag.replace("-", "_").split("_") + lang = parts[0] if len(parts) > 0 else "" + country = parts[1] if len(parts) > 1 else "" + variant = parts[2] if len(parts) > 2 else "" + return Locale(lang, country, variant) + + def equals(self, other) -> bool: + if isinstance(other, Locale): + return (self._language == other._language and + self._country == other._country and + self._variant == other._variant) + return False + + def hashCode(self) -> int: + return hash((self._language, self._country, self._variant)) & 0x7FFFFFFF + + +# Initialize Locale constants +Locale.ENGLISH = Locale("en") +Locale.FRENCH = Locale("fr") +Locale.GERMAN = Locale("de") +Locale.ITALIAN = Locale("it") +Locale.JAPANESE = Locale("ja") +Locale.KOREAN = Locale("ko") +Locale.CHINESE = Locale("zh") +Locale.SIMPLIFIED_CHINESE = Locale("zh", "CN") +Locale.TRADITIONAL_CHINESE = Locale("zh", "TW") +Locale.FRANCE = Locale("fr", "FR") +Locale.GERMANY = Locale("de", "DE") +Locale.ITALY = Locale("it", "IT") +Locale.JAPAN = Locale("ja", "JP") +Locale.KOREA = Locale("ko", "KR") +Locale.CHINA = Locale("zh", "CN") +Locale.UK = Locale("en", "GB") +Locale.US = Locale("en", "US") +Locale.CANADA = Locale("en", "CA") +Locale.CANADA_FRENCH = Locale("fr", "CA") +Locale.ROOT = Locale("", "") + + +# ============================================================ +# BitSet +# ============================================================ + +class BitSet(Stub): + """java.util.BitSet — bit manipulation""" + def __init__(self, nbits: int = 0): + super().__init__() + if isinstance(nbits, int): + self._bits = 0 + elif isinstance(nbits, BitSet): + self._bits = nbits._bits + else: + self._bits = 0 + + def _check(self, bit_index: int) -> None: + if bit_index < 0: + raise IndexError(f"bitIndex < 0: {bit_index}") + + def get(self, bit_index: int) -> bool: + self._check(bit_index) + return bool((self._bits >> bit_index) & 1) + + def getRange(self, from_index: int, to_index: int) -> 'BitSet': + self._check(from_index) + mask = ((1 << (to_index - from_index)) - 1) << from_index + result = BitSet() + result._bits = (self._bits & mask) >> from_index + return result + + def set(self, bit_index: int, value: bool = True) -> None: + self._check(bit_index) + if value: + self._bits |= (1 << bit_index) + else: + self._bits &= ~(1 << bit_index) + + def setRange(self, from_index: int, to_index: int, value: bool = True) -> None: + self._check(from_index) + mask = ((1 << (to_index - from_index)) - 1) << from_index + if value: + self._bits |= mask + else: + self._bits &= ~mask + + def clear(self, bit_index: int = None) -> None: + if bit_index is None: + self._bits = 0 + else: + self._check(bit_index) + self._bits &= ~(1 << bit_index) + + def clearRange(self, from_index: int, to_index: int) -> None: + self.setRange(from_index, to_index, False) + + def flip(self, bit_index: int) -> None: + self._check(bit_index) + self._bits ^= (1 << bit_index) + + def flipRange(self, from_index: int, to_index: int) -> None: + self._check(from_index) + mask = ((1 << (to_index - from_index)) - 1) << from_index + self._bits ^= mask + + def cardinality(self) -> int: + return self._bits.bit_count() + + def intersects(self, bs: 'BitSet') -> bool: + return (self._bits & bs._bits) != 0 + + def and_(self, bs: 'BitSet') -> None: + self._bits &= bs._bits + + def or_(self, bs: 'BitSet') -> None: + self._bits |= bs._bits + + def xor(self, bs: 'BitSet') -> None: + self._bits ^= bs._bits + + def andNot(self, bs: 'BitSet') -> None: + self._bits &= ~bs._bits + + def size(self) -> int: + return self._bits.bit_length() + (8 - (self._bits.bit_length() % 8)) % 8 + + def length(self) -> int: + return self._bits.bit_length() + + def isEmpty(self) -> bool: + return self._bits == 0 + + def nextSetBit(self, from_index: int) -> int: + self._check(from_index) + bits = self._bits >> from_index + if bits == 0: + return -1 + return (bits & -bits).bit_length() - 1 + from_index + + def nextClearBit(self, from_index: int) -> int: + self._check(from_index) + bits = ~(self._bits >> from_index) + if bits == 0: + return -1 + return (bits & -bits).bit_length() - 1 + from_index + + def previousSetBit(self, from_index: int) -> int: + if from_index < 0: + return -1 + mask = (1 << (from_index + 1)) - 1 + bits = self._bits & mask + if bits == 0: + return -1 + return bits.bit_length() - 1 + + def previousClearBit(self, from_index: int) -> int: + if from_index < 0: + return -1 + mask = (1 << (from_index + 1)) - 1 + bits = (~self._bits) & mask + if bits == 0: + return -1 + return bits.bit_length() - 1 + + def toByteArray(self) -> bytearray: + if self._bits == 0: + return bytearray() + n_bytes = (self._bits.bit_length() + 7) // 8 + return bytearray(self._bits.to_bytes(n_bytes, 'little')) + + def toLongArray(self) -> list: + if self._bits == 0: + return [] + n_longs = (self._bits.bit_length() + 63) // 64 + result = [] + for i in range(n_longs): + result.append((self._bits >> (i * 64)) & 0xFFFFFFFFFFFFFFFF) + return result + + @staticmethod + def valueOf(bytes_or_longs) -> 'BitSet': + bs = BitSet() + if isinstance(bytes_or_longs, (bytes, bytearray)): + bs._bits = int.from_bytes(bytes_or_longs, 'little') + elif isinstance(bytes_or_longs, list): + for i, v in enumerate(bytes_or_longs): + bs._bits |= (v & 0xFFFFFFFFFFFFFFFF) << (i * 64) + return bs + + def stream(self): + bits = [] + i = 0 + while True: + idx = self.nextSetBit(i) + if idx < 0: + break + bits.append(idx) + i = idx + 1 + return bits + + def equals(self, other) -> bool: + if isinstance(other, BitSet): + return self._bits == other._bits + return False + + def hashCode(self) -> int: + return hash(self._bits) & 0x7FFFFFFF + + def toString(self) -> str: + bits = [] + i = 0 + while True: + idx = self.nextSetBit(i) + if idx < 0: + break + bits.append(str(idx)) + i = idx + 1 + return "{" + ", ".join(bits) + "}" + + def clone(self) -> 'BitSet': + return BitSet(self) + + def __repr__(self): + return self.toString() + + def __eq__(self, other): + return self.equals(other) + + +# ============================================================ +# PriorityQueue +# ============================================================ + +class PriorityQueue(Stub): + """java.util.PriorityQueue — using heapq""" + def __init__(self, initial_capacity: Any = None, comparator=None): + super().__init__() + self._heap: list = [] + self._comparator = comparator + if initial_capacity is not None and not isinstance(initial_capacity, (int, float)): + if isinstance(initial_capacity, _IterableABC): + for item in initial_capacity: + self._heap.append(item) + if comparator: + self._heap.sort(key=comparator) + else: + self._heap.sort() + + def _wrap_key(self, obj): + if self._comparator: + return (self._comparator(obj), obj) + return obj + + def add(self, obj) -> bool: + _heapq.heappush(self._heap, self._wrap_key(obj)) + return True + + def offer(self, obj) -> bool: + return self.add(obj) + + def peek(self): + return self._heap[0][1] if self._heap and self._comparator else (self._heap[0] if self._heap else None) + + def element(self): + val = self.peek() + if val is None: + raise NoSuchElementException() + return val + + def poll(self): + if not self._heap: + return None + val = _heapq.heappop(self._heap) + return val[1] if self._comparator else val + + def remove(self, obj) -> bool: + if self._comparator: + wrapped = self._wrap_key(obj) + for i, v in enumerate(self._heap): + if v == wrapped: + self._heap[i] = self._heap[-1] + self._heap.pop() + _heapq.heapify(self._heap) + return True + else: + for i, v in enumerate(self._heap): + if v == obj: + self._heap[i] = self._heap[-1] + self._heap.pop() + _heapq.heapify(self._heap) + return True + return False + + def size(self) -> int: + return len(self._heap) + + def isEmpty(self) -> bool: + return len(self._heap) == 0 + + def clear(self) -> None: + self._heap.clear() + + def comparator(self): + return self._comparator + + def iterator(self) -> Iterator: + return Iterator(list(self._heap)) + + def toArray(self, a=None): + if self._comparator: + items = sorted([v[1] for v in self._heap], key=self._comparator) + else: + items = sorted(self._heap) + if a is None: + return items + return items[:len(a)] + + def forEach(self, action) -> None: + for item in list(self._heap): + action(item) + + def toString(self) -> str: + items = ", ".join(str(x) for x in self._heap) + return f"[{items}]" + + def __len__(self): + return len(self._heap) + + def __repr__(self): + return self.toString() + + +# ============================================================ +# Vector +# ============================================================ + +class Vector(Stub): + """java.util.Vector — synchronized list""" + def __init__(self, initial_capacity: Any = None, capacity_increment: int = 0): + super().__init__() + if initial_capacity is None: + self._data = [] + elif isinstance(initial_capacity, int): + self._data = [] + elif isinstance(initial_capacity, _IterableABC): + self._data = list(initial_capacity) + else: + self._data = [] + self._capacity_increment = capacity_increment + self._lock = _threading.Lock() + + def _synchronized(self, func, *args, **kwargs): + with self._lock: + return func(*args, **kwargs) + + def add(self, obj) -> bool: + with self._lock: + self._data.append(obj) + return True + + def addAt(self, index: int, obj) -> None: + with self._lock: + self._data.insert(index, obj) + + def addAll(self, c) -> bool: + with self._lock: + if isinstance(c, Vector): + self._data.extend(c._data) + else: + self._data.extend(c) + return True + + def addAllAt(self, index: int, c) -> bool: + with self._lock: + if isinstance(c, Vector): + self._data[index:index] = c._data + else: + self._data[index:index] = list(c) + return True + + def addElement(self, obj) -> None: + self.add(obj) + + def capacity(self) -> int: + with self._lock: + return len(self._data) + + def clear(self) -> None: + with self._lock: + self._data.clear() + + def clone(self) -> 'Vector': + with self._lock: + return Vector(self._data) + + def contains(self, obj) -> bool: + with self._lock: + return obj in self._data + + def containsAll(self, c) -> bool: + with self._lock: + if isinstance(c, Vector): + return all(x in self._data for x in c._data) + return all(x in self._data for x in c) + + def copyInto(self, an_array: list) -> None: + with self._lock: + for i, v in enumerate(self._data): + an_array[i] = v + + def elementAt(self, index: int): + with self._lock: + return self._data[index] + + def elements(self) -> Enumeration: + with self._lock: + return Enumeration(list(self._data)) + + def ensureCapacity(self, min_capacity: int) -> None: + pass + + def firstElement(self): + with self._lock: + if not self._data: + raise NoSuchElementException() + return self._data[0] + + def forEach(self, action) -> None: + with self._lock: + for item in list(self._data): + action(item) + + def get(self, index: int): + with self._lock: + return self._data[index] + + def indexOf(self, obj, index: int = 0) -> int: + with self._lock: + try: + return self._data.index(obj, index) + except ValueError: + return -1 + + def insertElementAt(self, obj, index: int) -> None: + with self._lock: + self._data.insert(index, obj) + + def isEmpty(self) -> bool: + with self._lock: + return len(self._data) == 0 + + def iterator(self) -> Iterator: + with self._lock: + return Iterator(list(self._data)) + + def lastElement(self): + with self._lock: + if not self._data: + raise NoSuchElementException() + return self._data[-1] + + def lastIndexOf(self, obj, index: int = None) -> int: + with self._lock: + if index is None: + index = len(self._data) - 1 + for i in range(index, -1, -1): + if self._data[i] == obj: + return i + return -1 + + def listIterator(self, index: int = 0) -> ListIterator: + with self._lock: + li = ListIterator(list(self._data)) + li._index = index + return li + + def remove(self, obj) -> bool: + with self._lock: + if obj in self._data: + self._data.remove(obj) + return True + return False + + def removeAt(self, index: int): + with self._lock: + return self._data.pop(index) + + def removeAll(self, c) -> bool: + with self._lock: + if isinstance(c, Vector): + items = c._data + else: + items = list(c) + old_len = len(self._data) + self._data = [x for x in self._data if x not in items] + return len(self._data) != old_len + + def removeAllElements(self) -> None: + with self._lock: + self._data.clear() + + def removeElement(self, obj) -> bool: + return self.remove(obj) + + def removeElementAt(self, index: int) -> None: + with self._lock: + self._data.pop(index) + + def removeIf(self, predicate) -> bool: + with self._lock: + old_len = len(self._data) + self._data = [x for x in self._data if not predicate(x)] + return len(self._data) != old_len + + def replaceAll(self, operator) -> None: + with self._lock: + self._data = [operator(x) for x in self._data] + + def retainAll(self, c) -> bool: + with self._lock: + if isinstance(c, Vector): + items = set(c._data) + else: + items = set(c) + old_len = len(self._data) + self._data = [x for x in self._data if x in items] + return len(self._data) != old_len + + def set(self, index: int, obj): + with self._lock: + old = self._data[index] + self._data[index] = obj + return old + + def setElementAt(self, obj, index: int) -> None: + with self._lock: + self._data[index] = obj + + def setSize(self, new_size: int) -> None: + with self._lock: + while len(self._data) < new_size: + self._data.append(None) + while len(self._data) > new_size: + self._data.pop() + + def size(self) -> int: + with self._lock: + return len(self._data) + + def sort(self, key=None, reverse: bool = False) -> None: + with self._lock: + self._data.sort(key=key, reverse=reverse) + + def subList(self, from_index: int, to_index: int) -> 'Vector': + with self._lock: + return Vector(self._data[from_index:to_index]) + + def toArray(self, a=None): + with self._lock: + if a is None: + return list(self._data) + if len(a) < len(self._data): + return list(self._data) + for i, v in enumerate(self._data): + a[i] = v + if len(self._data) < len(a): + a[len(self._data)] = None + return a + + def toString(self) -> str: + with self._lock: + items = ", ".join(str(x) for x in self._data) + return f"[{items}]" + + def trimToSize(self) -> None: + pass + + def __len__(self): + with self._lock: + return len(self._data) + + def __getitem__(self, idx): + with self._lock: + return self._data[idx] + + def __setitem__(self, idx, val): + with self._lock: + self._data[idx] = val + + def __delitem__(self, idx): + with self._lock: + del self._data[idx] + + def __iter__(self): + with self._lock: + return iter(list(self._data)) + + def __repr__(self): + return self.toString() + + def __contains__(self, obj): + with self._lock: + return obj in self._data + + def __eq__(self, other): + if isinstance(other, Vector): + with self._lock: + return self._data == other._data + return False + + +# ============================================================ +# Stack +# ============================================================ + +class Stack(Vector): + """java.util.Stack — extends Vector""" + def __init__(self): + super().__init__() + + def push(self, item): + with self._lock: + self._data.append(item) + return item + + def pop(self): + with self._lock: + if not self._data: + raise EmptyStackException() + return self._data.pop() + + def peek(self): + with self._lock: + if not self._data: + raise EmptyStackException() + return self._data[-1] + + def empty(self) -> bool: + with self._lock: + return len(self._data) == 0 + + def search(self, obj) -> int: + with self._lock: + try: + idx = len(self._data) - 1 - self._data[::-1].index(obj) + return len(self._data) - idx + except ValueError: + return -1 + + def add(self, obj) -> bool: + self.push(obj) + return True + + def remove(self, obj) -> bool: + with self._lock: + if obj in self._data: + self._data.remove(obj) + return True + return False \ No newline at end of file diff --git a/android_pyapi/java/util_concurrent.py b/android_pyapi/java/util_concurrent.py new file mode 100644 index 0000000..fd2c16a --- /dev/null +++ b/android_pyapi/java/util_concurrent.py @@ -0,0 +1,2037 @@ +""" +Java util.concurrent standard library APIs implemented in Python. +java.util.concurrent package - Executors, ThreadPoolExecutor, CountDownLatch, etc. +""" + +from .._core import Stub +from .lang import RuntimeException +import threading as _threading +import concurrent.futures as _futures +import time as _time +import queue as _queue +import math as _py_math + +IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {}) +CancellationException = type("CancellationException", (RuntimeException,), {}) +ExecutionException = type("ExecutionException", (RuntimeException,), {}) +RejectedExecutionException = type("RejectedExecutionException", (RuntimeException,), {}) +TimeoutException = type("TimeoutException", (RuntimeException,), {}) +BrokenBarrierException = type("BrokenBarrierException", (RuntimeException,), {}) +CompletionException = type("CompletionException", (RuntimeException,), {}) + +__all__ = [ + "Executors", "ExecutorService", "Executor", "ExecutorCompletionService", + "ThreadPoolExecutor", "AbstractExecutorService", + "ScheduledExecutorService", "ScheduledThreadPoolExecutor", + "FutureTask", "Future", "RunnableFuture", "RunnableScheduledFuture", + "Callable", "CompletionService", "CompletionStage", + "CountDownLatch", "CyclicBarrier", "Semaphore", "Phaser", "Exchanger", + "TimeUnit", + "TimeoutException", "CancellationException", "ExecutionException", + "RejectedExecutionException", "BrokenBarrierException", + "CompletionException", + "ConcurrentHashMap", "CopyOnWriteArrayList", "CopyOnWriteArraySet", + "ConcurrentLinkedQueue", "ConcurrentLinkedDeque", + "DelayQueue", "SynchronousQueue", "LinkedBlockingQueue", + "ArrayBlockingQueue", "PriorityBlockingQueue", + "BlockingQueue", "BlockingDeque", + "AtomicBoolean", "AtomicInteger", "AtomicLong", "AtomicReference", + "AtomicIntegerArray", "AtomicLongArray", "AtomicReferenceArray", + "AtomicIntegerFieldUpdater", "AtomicLongFieldUpdater", + "AtomicReferenceFieldUpdater", + "AtomicMarkableReference", "AtomicStampedReference", + "DoubleAccumulator", "DoubleAdder", + "LongAccumulator", "LongAdder", + "Lock", "ReentrantLock", "ReentrantReadWriteLock", + "ReadWriteLock", "Condition", "LockSupport", + "AbstractQueuedSynchronizer", "AbstractQueuedLongSynchronizer", + "Flow", "SubmissionPublisher", + "RecursiveAction", "RecursiveTask", "ForkJoinPool", + "ForkJoinTask", "ForkJoinWorkerThread", + "CompletableFuture", +] + + +# ============================================================ +# Interfaces +# ============================================================ + +class Callable(Stub): + def call(self): + raise NotImplementedError + + +class Future(Stub): + def get(self, timeout=None, unit=None): + raise NotImplementedError + + def cancel(self, may_interrupt): + raise NotImplementedError + + def isCancelled(self): + raise NotImplementedError + + def isDone(self): + raise NotImplementedError + + +class RunnableFuture(Future): + def run(self): + raise NotImplementedError + + +class RunnableScheduledFuture(RunnableFuture): + def isPeriodic(self): + raise NotImplementedError + + +class CompletionService(Stub): + def submit(self, task): + raise NotImplementedError + + def take(self): + raise NotImplementedError + + def poll(self, timeout=0, unit=None): + raise NotImplementedError + + +class CompletionStage(Stub): + pass + + +class Executor(Stub): + def execute(self, command): + raise NotImplementedError + + +class ExecutorService(Executor): + def submit(self, task, *args): + raise NotImplementedError + + def shutdown(self): + raise NotImplementedError + + def shutdownNow(self): + raise NotImplementedError + + def isShutdown(self): + raise NotImplementedError + + def isTerminated(self): + raise NotImplementedError + + def awaitTermination(self, timeout, unit): + raise NotImplementedError + + def invokeAll(self, tasks): + raise NotImplementedError + + def invokeAny(self, tasks): + raise NotImplementedError + + +class ScheduledExecutorService(ExecutorService): + def schedule(self, command, delay, unit): + raise NotImplementedError + + def scheduleAtFixedRate(self, command, initial_delay, period, unit): + raise NotImplementedError + + def scheduleWithFixedDelay(self, command, initial_delay, delay, unit): + raise NotImplementedError + + +# ============================================================ +# Runnable +# ============================================================ + +class Runnable(Stub): + def run(self): + raise NotImplementedError + + +# ============================================================ +# FutureTask +# ============================================================ + +class FutureTask(RunnableFuture): + def __init__(self, callable_or_runnable, result=None): + super().__init__() + self._callable = callable_or_runnable if isinstance(callable_or_runnable, Callable) else None + self._runnable = callable_or_runnable if isinstance(callable_or_runnable, Runnable) else None + self._result = result + self._done = False + self._cancelled = False + self._exception = None + self._lock = _threading.RLock() + self._done_event = _threading.Event() + + def run(self): + if self._cancelled: + return + try: + if self._callable: + self._result = self._callable.call() + elif self._runnable: + self._runnable.run() + with self._lock: + self._done = True + self._done_event.set() + except Exception as e: + with self._lock: + self._exception = e + self._done = True + self._done_event.set() + + def get(self, timeout=None, unit=None): + if timeout is not None and unit is not None: + seconds = unit.toMillis(timeout) / 1000.0 + if not self._done_event.wait(seconds): + raise TimeoutException() + else: + self._done_event.wait() + with self._lock: + if self._exception: + raise ExecutionException(str(self._exception)) + return self._result + + def cancel(self, may_interrupt): + with self._lock: + if self._done: + return False + self._cancelled = True + self._done = True + self._done_event.set() + return True + + def isCancelled(self): + return self._cancelled + + def isDone(self): + return self._done + + def doneTask(self): + pass + + def set(self, v): + with self._lock: + self._result = v + self._done = True + self._done_event.set() + + def setException(self, ex): + with self._lock: + self._exception = ex + self._done = True + self._done_event.set() + + +# ============================================================ +# CompletableFuture +# ============================================================ + +class CompletableFuture(Future): + def __init__(self): + super().__init__() + self._result = None + self._exception = None + self._done = False + self._lock = _threading.RLock() + self._done_event = _threading.Event() + self._callbacks = [] + + def _run_callbacks(self): + for cb in self._callbacks: + try: + cb(self) + except Exception: + pass + + def complete(self, result): + with self._lock: + if self._done: + return self + self._result = result + self._done = True + self._done_event.set() + self._run_callbacks() + return self + + def completeExceptionally(self, ex): + with self._lock: + if self._done: + return self + self._exception = ex + self._done = True + self._done_event.set() + self._run_callbacks() + return self + + def cancel(self, may_interrupt): + with self._lock: + if self._done: + return False + self._cancelled = True + self._done = True + self._done_event.set() + self._run_callbacks() + return True + + def get(self, timeout=None, unit=None): + if timeout is not None and unit is not None: + seconds = unit.toMillis(timeout) / 1000.0 + if not self._done_event.wait(seconds): + raise TimeoutException() + else: + self._done_event.wait() + with self._lock: + if self._exception: + raise ExecutionException(str(self._exception)) + return self._result + + def join(self): + return self.get() + + def isDone(self): + return self._done + + def isCancelled(self): + return getattr(self, "_cancelled", False) + + def thenApply(self, fn): + future = CompletableFuture() + def cb(f): + try: + future.complete(fn(f.get())) + except Exception as e: + future.completeExceptionally(e) + self._callbacks.append(cb) + return future + + def thenAccept(self, fn): + future = CompletableFuture() + def cb(f): + try: + fn(f.get()) + future.complete(None) + except Exception as e: + future.completeExceptionally(e) + self._callbacks.append(cb) + return future + + def thenRun(self, action): + future = CompletableFuture() + def cb(f): + try: + action.run() + future.complete(None) + except Exception as e: + future.completeExceptionally(e) + self._callbacks.append(cb) + return future + + def thenCombine(self, other, fn): + future = CompletableFuture() + def cb(f): + try: + other_result = other.get() + future.complete(fn(f.get(), other_result)) + except Exception as e: + future.completeExceptionally(e) + self._callbacks.append(cb) + return future + + def thenCompose(self, fn): + future = CompletableFuture() + def cb(f): + try: + next_future = fn(f.get()) + future.complete(next_future.get()) + except Exception as e: + future.completeExceptionally(e) + self._callbacks.append(cb) + return future + + def exceptionally(self, fn): + future = CompletableFuture() + def cb(f): + try: + f.get() + future.complete(f._result) + except Exception as e: + try: + fallback = fn(e) + future.complete(fallback) + except Exception as e2: + future.completeExceptionally(e2) + self._callbacks.append(cb) + return future + + @staticmethod + def completedFuture(result): + f = CompletableFuture() + f.complete(result) + return f + + @staticmethod + def failedFuture(exception): + f = CompletableFuture() + f.completeExceptionally(exception) + return f + + @staticmethod + def supplyAsync(supplier, executor=None): + f = CompletableFuture() + def run(): + try: + f.complete(supplier.get()) + except Exception as e: + f.completeExceptionally(e) + t = _threading.Thread(target=run, daemon=True) + t.start() + return f + + @staticmethod + def runAsync(runnable, executor=None): + f = CompletableFuture() + def run(): + try: + runnable.run() + f.complete(None) + except Exception as e: + f.completeExceptionally(e) + t = _threading.Thread(target=run, daemon=True) + t.start() + return f + + @staticmethod + def allOf(*futures): + result = CompletableFuture() + def check_all(): + for f in futures: + f.get() + result.complete(None) + t = _threading.Thread(target=check_all, daemon=True) + t.start() + return result + + @staticmethod + def anyOf(*futures): + result = CompletableFuture() + def check_any(): + while True: + for f in futures: + if f.isDone(): + result.complete(f.get()) + return + _time.sleep(0.01) + t = _threading.Thread(target=check_any, daemon=True) + t.start() + return result + + +# ============================================================ +# Executors +# ============================================================ + +class Executors(Stub): + @staticmethod + def newCachedThreadPool(): + pool = ThreadPoolExecutor(0, _futures.ThreadPoolExecutor(max_workers=10)) + pool._executor._max_workers = None + return pool + + @staticmethod + def newFixedThreadPool(n_threads): + return ThreadPoolExecutor(n_threads, _futures.ThreadPoolExecutor(max_workers=n_threads)) + + @staticmethod + def newSingleThreadExecutor(): + return ThreadPoolExecutor(1, _futures.ThreadPoolExecutor(max_workers=1)) + + @staticmethod + def newScheduledThreadPool(core_pool_size): + return ScheduledThreadPoolExecutor(core_pool_size) + + @staticmethod + def newWorkStealingPool(parallelism=None): + return ThreadPoolExecutor(parallelism or 4, _futures.ThreadPoolExecutor(max_workers=parallelism or 4)) + + @staticmethod + def defaultThreadFactory(): + return _threading.Thread + + @staticmethod + def privilegedThreadFactory(): + return _threading.Thread + + @staticmethod + def callable(runnable, result=None): + class _Callable(Callable): + def call(self): + if runnable: + runnable.run() + return result + return _Callable() + + +# ============================================================ +# ThreadPoolExecutor +# ============================================================ + +class ThreadPoolExecutor(ExecutorService): + def __init__(self, core_pool_size, executor=None): + super().__init__() + self._executor = executor or _futures.ThreadPoolExecutor(max_workers=core_pool_size) + self._core_pool_size = core_pool_size + self._max_pool_size = core_pool_size + self._keep_alive_time = 60 + self._allow_core_thread_timeout = False + + def execute(self, command): + def _run(): + try: + command.run() + except Exception: + pass + self._executor.submit(_run) + + def submit(self, task, *args): + if isinstance(task, Callable): + future = FutureTask(task) + self._executor.submit(future.run) + return future + elif isinstance(task, Runnable): + future = FutureTask(task) + self._executor.submit(future.run) + return future + else: + future = FutureTask(task) + self._executor.submit(future.run) + return future + + def shutdown(self): + self._executor.shutdown(wait=False) + + def shutdownNow(self): + self._executor.shutdown(wait=False) + + def isShutdown(self): + return self._executor._shutdown + + def isTerminated(self): + return self._executor._shutdown + + def awaitTermination(self, timeout, unit): + seconds = unit.toMillis(timeout) / 1000.0 + self._executor.shutdown(wait=True) + + def getPoolSize(self): + return self._core_pool_size + + def getActiveCount(self): + return 0 + + def getTaskCount(self): + return 0 + + def getCompletedTaskCount(self): + return 0 + + def getLargestPoolSize(self): + return self._core_pool_size + + def setCorePoolSize(self, size): + self._core_pool_size = size + + def setMaximumPoolSize(self, size): + self._max_pool_size = size + + def setKeepAliveTime(self, time, unit): + self._keep_alive_time = unit.toMillis(time) / 1000.0 + + def prestartAllCoreThreads(self): + pass + + def prestartCoreThread(self): + pass + + def allowsCoreThreadTimeOut(self): + return self._allow_core_thread_timeout + + def setRejectedExecutionHandler(self, handler): + pass + + def getRejectedExecutionHandler(self): + return None + + +class AbstractExecutorService(ExecutorService): + pass + + +# ============================================================ +# ScheduledThreadPoolExecutor +# ============================================================ + +class ScheduledThreadPoolExecutor(ThreadPoolExecutor, ScheduledExecutorService): + def __init__(self, core_pool_size): + super().__init__(core_pool_size) + + def schedule(self, command, delay, unit): + future = FutureTask(command) + seconds = unit.toMillis(delay) / 1000.0 + t = _threading.Timer(seconds, future.run) + t.daemon = True + t.start() + return future + + def scheduleAtFixedRate(self, command, initial_delay, period, unit): + seconds = unit.toMillis(period) / 1000.0 + init_sec = unit.toMillis(initial_delay) / 1000.0 + def _run(): + _time.sleep(init_sec) + while True: + command.run() + _time.sleep(seconds) + t = _threading.Thread(target=_run, daemon=True) + t.start() + return FutureTask(command) + + def scheduleWithFixedDelay(self, command, initial_delay, delay, unit): + return self.scheduleAtFixedRate(command, initial_delay, delay, unit) + + +class ExecutorCompletionService(CompletionService): + def __init__(self, executor): + super().__init__() + self._executor = executor + self._queue = _queue.Queue() + + def submit(self, task): + future = self._executor.submit(task) + self._queue.put(future) + return future + + def take(self): + return self._queue.get() + + def poll(self, timeout=0, unit=None): + if unit is not None: + sec = unit.toMillis(timeout) / 1000.0 + else: + sec = timeout + try: + return self._queue.get(timeout=sec) + except _queue.Empty: + return None + + +# ============================================================ +# TimeUnit +# ============================================================ + +class TimeUnit(Stub): + NANOSECONDS = None + MICROSECONDS = None + MILLISECONDS = None + SECONDS = None + MINUTES = None + HOURS = None + DAYS = None + + def __init__(self, name, scale): + self._name = name + self._scale = scale # nanos per unit + + def convert(self, source_duration, source_unit): + return int(source_duration * source_unit._scale / self._scale) + + def toNanos(self, duration): + return int(duration * self._scale) + + def toMicros(self, duration): + return int(duration * self._scale // 1000) + + def toMillis(self, duration): + return int(duration * self._scale // 1_000_000) + + def toSeconds(self, duration): + return int(duration * self._scale // 1_000_000_000) + + def toMinutes(self, duration): + return self.toSeconds(duration) // 60 + + def toHours(self, duration): + return self.toMinutes(duration) // 60 + + def toDays(self, duration): + return self.toHours(duration) // 24 + + def sleep(self, timeout): + _time.sleep(self.toMillis(timeout) / 1000.0) + + def timedJoin(self, thread, timeout): + thread.join(self.toMillis(timeout) / 1000.0) + + def timedWait(self, obj, timeout): + return True + + def toString(self): + return self._name + + +TimeUnit.NANOSECONDS = TimeUnit("NANOSECONDS", 1) +TimeUnit.MICROSECONDS = TimeUnit("MICROSECONDS", 1000) +TimeUnit.MILLISECONDS = TimeUnit("MILLISECONDS", 1_000_000) +TimeUnit.SECONDS = TimeUnit("SECONDS", 1_000_000_000) +TimeUnit.MINUTES = TimeUnit("MINUTES", 60_000_000_000) +TimeUnit.HOURS = TimeUnit("HOURS", 3_600_000_000_000) +TimeUnit.DAYS = TimeUnit("DAYS", 86_400_000_000_000) + + +# ============================================================ +# CountDownLatch +# ============================================================ + +class CountDownLatch(Stub): + def __init__(self, count): + super().__init__() + self._count = count + self._condition = _threading.Condition() + + def await_(self, timeout=None, unit=None): + with self._condition: + if self._count == 0: + return True + if timeout is not None and unit is not None: + seconds = unit.toMillis(timeout) / 1000.0 + self._condition.wait(timeout=seconds) + return self._count == 0 + self._condition.wait() + return True + + def countDown(self): + with self._condition: + if self._count > 0: + self._count -= 1 + if self._count == 0: + self._condition.notify_all() + + def getCount(self): + return self._count + + def toString(self): + return f"CountDownLatch(count={self._count})" + + +# ============================================================ +# CyclicBarrier +# ============================================================ + +class CyclicBarrier(Stub): + def __init__(self, parties, barrier_action=None): + super().__init__() + self._parties = parties + self._barrier_action = barrier_action + self._count = parties + self._broken = False + self._lock = _threading.RLock() + self._condition = _threading.Condition() + + def await_(self, timeout=None, unit=None): + with self._condition: + if self._broken: + raise BrokenBarrierException() + self._count -= 1 + if self._count == 0: + if self._barrier_action: + try: + self._barrier_action.run() + except Exception: + self._broken = True + self._condition.notify_all() + raise BrokenBarrierException() + self._count = self._parties + self._condition.notify_all() + return 0 + if timeout is not None and unit is not None: + seconds = unit.toMillis(timeout) / 1000.0 + self._condition.wait(timeout=seconds) + if self._count > 0: + self._broken = True + raise BrokenBarrierException() + else: + self._condition.wait() + return self._count + + def getNumberWaiting(self): + return self._parties - self._count + + def getParties(self): + return self._parties + + def isBroken(self): + return self._broken + + def reset(self): + with self._condition: + self._broken = False + self._count = self._parties + self._condition.notify_all() + + +# ============================================================ +# Semaphore +# ============================================================ + +class Semaphore(Stub): + def __init__(self, permits, fair=False): + super().__init__() + self._sem = _threading.Semaphore(permits) if not fair else _threading.Semaphore(permits) + self._fair = fair + self._permits = permits + self._lock = _threading.Lock() + + def acquire(self): + self._sem.acquire() + + def acquireUninterruptibly(self): + self._sem.acquire() + + def release(self): + self._sem.release() + + def tryAcquire(self, timeout=None, unit=None): + if timeout is not None and unit is not None: + seconds = unit.toMillis(timeout) / 1000.0 + return self._sem.acquire(blocking=True, timeout=seconds) + return self._sem.acquire(blocking=False) + + def availablePermits(self): + return self._sem._value + + def drainPermits(self): + with self._lock: + val = self._sem._value + for _ in range(val): + self._sem.acquire(blocking=False) + return val + + def getQueueLength(self): + return 0 + + def hasQueuedThreads(self): + return False + + +# ============================================================ +# Phaser (stub) +# ============================================================ + +class Phaser(Stub): + def __init__(self, parties=0): + super().__init__() + self._parties = parties + self._phase = 0 + self._arrived = 0 + + def register(self): + self._parties += 1 + + def arrive(self): + self._arrived += 1 + return self._phase + + def arriveAndDeregister(self): + self._arrived += 1 + self._parties -= 1 + return self._phase + + def arriveAndAwaitAdvance(self): + self._arrived += 1 + if self._arrived >= self._parties: + self._phase += 1 + self._arrived = 0 + return self._phase + + def awaitAdvance(self, phase): + return self._phase + + def getPhase(self): + return self._phase + + def getArrivedParties(self): + return self._arrived + + def getRegisteredParties(self): + return self._parties + + def getUnarrivedParties(self): + return self._parties - self._arrived + + def isTerminated(self): + return False + + def forceTermination(self): + pass + + +# ============================================================ +# Exchanger +# ============================================================ + +class Exchanger(Stub): + def __init__(self): + super().__init__() + self._lock = _threading.Lock() + self._item = None + self._other_ready = _threading.Event() + + def exchange(self, x, timeout=None, unit=None): + with self._lock: + if self._item is None: + self._item = x + self._other_ready.set() + else: + result = self._item + self._item = x + self._other_ready.set() + return result + if timeout is not None and unit is not None: + seconds = unit.toMillis(timeout) / 1000.0 + self._other_ready.wait(timeout=seconds) + else: + self._other_ready.wait() + with self._lock: + result = self._item + self._item = None + return result + + +# ============================================================ +# ConcurrentHashMap +# ============================================================ + +class ConcurrentHashMap(Stub): + def __init__(self, initial_capacity=16): + super().__init__() + self._map = {} + self._lock = _threading.RLock() + + def put(self, key, value): + with self._lock: + self._map[key] = value + return None + + def get(self, key): + with self._lock: + return self._map.get(key) + + def containsKey(self, key): + with self._lock: + return key in self._map + + def containsValue(self, value): + with self._lock: + return value in self._map.values() + + def size(self): + with self._lock: + return len(self._map) + + def isEmpty(self): + with self._lock: + return len(self._map) == 0 + + def remove(self, key): + with self._lock: + return self._map.pop(key, None) + + def clear(self): + with self._lock: + self._map.clear() + + def keySet(self): + with self._lock: + return set(self._map.keys()) + + def values(self): + with self._lock: + return list(self._map.values()) + + def entrySet(self): + with self._lock: + return set(self._map.items()) + + def putAll(self, m): + with self._lock: + if hasattr(m, "_map"): + self._map.update(m._map) + elif isinstance(m, dict): + self._map.update(m) + + def putIfAbsent(self, key, value): + with self._lock: + if key not in self._map: + self._map[key] = value + return None + return self._map[key] + + def getOrDefault(self, key, default): + with self._lock: + return self._map.get(key, default) + + def forEach(self, action): + with self._lock: + for k, v in self._map.items(): + action(k, v) + + +# ============================================================ +# CopyOnWriteArrayList / CopyOnWriteArraySet +# ============================================================ + +class CopyOnWriteArrayList(Stub): + def __init__(self, *args): + super().__init__() + self._list = [] + self._lock = _threading.RLock() + if args and isinstance(args[0], (list, tuple)): + self._list = list(args[0]) + + def add(self, e): + with self._lock: + self._list = list(self._list) + self._list.append(e) + return True + + def get(self, index): + return self._list[index] + + def set(self, index, element): + with self._lock: + old = self._list[index] + self._list = list(self._list) + self._list[index] = element + return old + + def remove(self, e): + with self._lock: + new_list = list(self._list) + try: + new_list.remove(e) + self._list = new_list + return True + except ValueError: + return False + + def size(self): + return len(self._list) + + def isEmpty(self): + return len(self._list) == 0 + + def contains(self, e): + return e in self._list + + def iterator(self): + return iter(list(self._list)) + + def toArray(self): + return list(self._list) + + def clear(self): + with self._lock: + self._list = [] + + +class CopyOnWriteArraySet(Stub): + def __init__(self): + super().__init__() + self._backend = CopyOnWriteArrayList() + + def add(self, e): + if self._backend.contains(e): + return False + return self._backend.add(e) + + def remove(self, e): + return self._backend.remove(e) + + def contains(self, e): + return self._backend.contains(e) + + def size(self): + return self._backend.size() + + def isEmpty(self): + return self._backend.isEmpty() + + def iterator(self): + return self._backend.iterator() + + +# ============================================================ +# BlockingQueue / LinkedBlockingQueue / ArrayBlockingQueue +# ============================================================ + +class BlockingQueue(Stub): + pass + + +class LinkedBlockingQueue(BlockingQueue): + def __init__(self, capacity=None): + super().__init__() + self._queue = _queue.Queue(maxsize=capacity if capacity else 0) + + def put(self, item): + self._queue.put(item) + + def take(self): + return self._queue.get() + + def offer(self, item, timeout=None, unit=None): + if timeout is not None and unit is not None: + seconds = unit.toMillis(timeout) / 1000.0 + try: + self._queue.put(item, timeout=seconds) + return True + except _queue.Full: + return False + try: + self._queue.put_nowait(item) + return True + except _queue.Full: + return False + + def poll(self, timeout=None, unit=None): + if timeout is not None and unit is not None: + seconds = unit.toMillis(timeout) / 1000.0 + try: + return self._queue.get(timeout=seconds) + except _queue.Empty: + return None + try: + return self._queue.get_nowait() + except _queue.Empty: + return None + + def size(self): + return self._queue.qsize() + + def isEmpty(self): + return self._queue.empty() + + def remainingCapacity(self): + return self._queue.maxsize - self._queue.qsize() + + def clear(self): + while not self._queue.empty(): + try: + self._queue.get_nowait() + except _queue.Empty: + break + + def contains(self, item): + return item in list(self._queue.queue) + + +class ArrayBlockingQueue(LinkedBlockingQueue): + def __init__(self, capacity, fair=False): + super().__init__(capacity) + + +class PriorityBlockingQueue(BlockingQueue): + def __init__(self, capacity=None): + super().__init__() + self._heap = [] + self._lock = _threading.RLock() + self._not_empty = _threading.Condition(self._lock) + + def put(self, item): + with self._lock: + _queue.heappush(self._heap, item) + self._not_empty.notify() + + def take(self): + with self._lock: + while not self._heap: + self._not_empty.wait() + return _queue.heappop(self._heap) + + def offer(self, item, timeout=None, unit=None): + self.put(item) + return True + + def poll(self, timeout=None, unit=None): + with self._lock: + if not self._heap: + return None + return _queue.heappop(self._heap) + + def size(self): + with self._lock: + return len(self._heap) + + def isEmpty(self): + with self._lock: + return len(self._heap) == 0 + + def clear(self): + with self._lock: + self._heap = [] + + +class SynchronousQueue(BlockingQueue): + def __init__(self, fair=False): + super().__init__() + self._exchange = _queue.Queue(maxsize=1) + + def put(self, item): + self._exchange.put(item) + + def take(self): + return self._exchange.get() + + def offer(self, item, timeout=None, unit=None): + try: + self._exchange.put_nowait(item) + return True + except _queue.Full: + return False + + def poll(self, timeout=None, unit=None): + try: + return self._exchange.get_nowait() + except _queue.Empty: + return None + + +class DelayQueue(BlockingQueue): + def __init__(self): + super().__init__() + self._queue = [] + + def put(self, item): + _queue.heappush(self._queue, item) + + def take(self): + while True: + if self._queue: + item = self._queue[0] + delay = item.getDelay(TimeUnit.MILLISECONDS) + if delay <= 0: + return _queue.heappop(self._queue) + _time.sleep(delay / 1000.0) + else: + _time.sleep(0.1) + + +class ConcurrentLinkedQueue(Stub): + def __init__(self): + super().__init__() + self._queue = _queue.Queue() + + def add(self, item): + self._queue.put(item) + return True + + def offer(self, item): + self._queue.put_nowait(item) + return True + + def poll(self): + try: + return self._queue.get_nowait() + except _queue.Empty: + return None + + def peek(self): + try: + item = self._queue.get_nowait() + self._queue.put_nowait(item) + return item + except _queue.Empty: + return None + + def size(self): + return self._queue.qsize() + + def isEmpty(self): + return self._queue.empty() + + def contains(self, item): + return item in list(self._queue.queue) + + +class ConcurrentLinkedDeque(ConcurrentLinkedQueue): + def addFirst(self, item): + self._queue.put(item) + + def addLast(self, item): + self._queue.put(item) + + def pollFirst(self): + return self.poll() + + def pollLast(self): + return self.poll() + + +class BlockingDeque(BlockingQueue): + pass + + +# ============================================================ +# Atomic classes +# ============================================================ + +class AtomicBoolean(Stub): + def __init__(self, initial=False): + super().__init__() + self._value = initial + self._lock = _threading.Lock() + + def get(self): + with self._lock: + return self._value + + def set(self, new_value): + with self._lock: + self._value = new_value + + def compareAndSet(self, expect, update): + with self._lock: + if self._value == expect: + self._value = update + return True + return False + + def getAndSet(self, new_value): + with self._lock: + old = self._value + self._value = new_value + return old + + def toString(self): + return str(self._value) + + +class AtomicInteger(Stub): + def __init__(self, initial=0): + super().__init__() + self._value = int(initial) + self._lock = _threading.Lock() + + def get(self): + with self._lock: + return self._value + + def set(self, new_value): + with self._lock: + self._value = int(new_value) + + def getAndSet(self, new_value): + with self._lock: + old = self._value + self._value = int(new_value) + return old + + def compareAndSet(self, expect, update): + with self._lock: + if self._value == expect: + self._value = int(update) + return True + return False + + def weakCompareAndSet(self, expect, update): + return self.compareAndSet(expect, update) + + def getAndIncrement(self): + with self._lock: + old = self._value + self._value += 1 + return old + + def getAndDecrement(self): + with self._lock: + old = self._value + self._value -= 1 + return old + + def getAndAdd(self, delta): + with self._lock: + old = self._value + self._value += delta + return old + + def incrementAndGet(self): + with self._lock: + self._value += 1 + return self._value + + def decrementAndGet(self): + with self._lock: + self._value -= 1 + return self._value + + def addAndGet(self, delta): + with self._lock: + self._value += delta + return self._value + + def lazySet(self, new_value): + with self._lock: + self._value = int(new_value) + + def updateAndGet(self, func): + with self._lock: + self._value = func(self._value) + return self._value + + def accumulateAndGet(self, x, func): + with self._lock: + self._value = func(self._value, x) + return self._value + + def intValue(self): + return self.get() + + def longValue(self): + return self.get() + + def floatValue(self): + return float(self.get()) + + def doubleValue(self): + return float(self.get()) + + def toString(self): + return str(self._value) + + +class AtomicLong(AtomicInteger): + pass + + +class AtomicReference(Stub): + def __init__(self, initial=None): + super().__init__() + self._value = initial + self._lock = _threading.Lock() + + def get(self): + with self._lock: + return self._value + + def set(self, new_value): + with self._lock: + self._value = new_value + + def compareAndSet(self, expect, update): + with self._lock: + if self._value == expect: + self._value = update + return True + return False + + def getAndSet(self, new_value): + with self._lock: + old = self._value + self._value = new_value + return old + + def getAndUpdate(self, func): + with self._lock: + old = self._value + self._value = func(old) + return old + + def updateAndGet(self, func): + with self._lock: + self._value = func(self._value) + return self._value + + def accumulateAndGet(self, x, func): + with self._lock: + self._value = func(self._value, x) + return self._value + + +class AtomicIntegerArray(Stub): + def __init__(self, length_or_array): + super().__init__() + if isinstance(length_or_array, int): + self._array = [0] * length_or_array + else: + self._array = list(length_or_array) + + def get(self, i): + return self._array[i] + + def set(self, i, new_value): + self._array[i] = new_value + + def compareAndSet(self, i, expect, update): + if self._array[i] == expect: + self._array[i] = update + return True + return False + + def getAndAdd(self, i, delta): + old = self._array[i] + self._array[i] += delta + return old + + def addAndGet(self, i, delta): + self._array[i] += delta + return self._array[i] + + def length(self): + return len(self._array) + + +class AtomicLongArray(AtomicIntegerArray): + pass + + +class AtomicReferenceArray(Stub): + def __init__(self, length_or_array): + super().__init__() + if isinstance(length_or_array, int): + self._array = [None] * length_or_array + else: + self._array = list(length_or_array) + + def get(self, i): + return self._array[i] + + def set(self, i, new_value): + self._array[i] = new_value + + def compareAndSet(self, i, expect, update): + if self._array[i] == expect: + self._array[i] = update + return True + return False + + def length(self): + return len(self._array) + + +class AtomicIntegerFieldUpdater(Stub): + @staticmethod + def newUpdater(tclass, field_name): + return AtomicIntegerFieldUpdater() + + def get(self, obj): + return 0 + + def set(self, obj, new_value): + pass + + def compareAndSet(self, obj, expect, update): + return True + + +class AtomicLongFieldUpdater(AtomicIntegerFieldUpdater): + pass + + +class AtomicReferenceFieldUpdater(Stub): + @staticmethod + def newUpdater(tclass, vclass, field_name): + return AtomicReferenceFieldUpdater() + + def get(self, obj): + return None + + def set(self, obj, new_value): + pass + + def compareAndSet(self, obj, expect, update): + return True + + +class AtomicMarkableReference(Stub): + def __init__(self, initial_ref, initial_mark): + super().__init__() + self._ref = initial_ref + self._mark = initial_mark + + def getReference(self): + return self._ref + + def isMarked(self): + return self._mark + + def compareAndSet(self, expected_ref, new_ref, expected_mark, new_mark): + if self._ref == expected_ref and self._mark == expected_mark: + self._ref = new_ref + self._mark = new_mark + return True + return False + + def attemptMark(self, expected_ref, new_mark): + if self._ref == expected_ref: + self._mark = new_mark + return True + return False + + +class AtomicStampedReference(Stub): + def __init__(self, initial_ref, initial_stamp): + super().__init__() + self._ref = initial_ref + self._stamp = initial_stamp + + def getReference(self): + return self._ref + + def getStamp(self): + return self._stamp + + def compareAndSet(self, expected_ref, new_ref, expected_stamp, new_stamp): + if self._ref == expected_ref and self._stamp == expected_stamp: + self._ref = new_ref + self._stamp = new_stamp + return True + return False + + def attemptStamp(self, expected_ref, new_stamp): + if self._ref == expected_ref: + self._stamp = new_stamp + return True + return False + + def set(self, new_ref, new_stamp): + self._ref = new_ref + self._stamp = new_stamp + + +class DoubleAccumulator(Stub): + def __init__(self, accumulator_function, identity): + super().__init__() + self._value = identity + self._func = accumulator_function + + def accumulate(self, x): + self._value = self._func(self._value, x) + + def get(self): + return self._value + + def reset(self): + self._value = 0.0 + + +class DoubleAdder(Stub): + def __init__(self): + super().__init__() + self._value = 0.0 + + def add(self, x): + self._value += x + + def sum(self): + return self._value + + def reset(self): + self._value = 0.0 + + def doubleValue(self): + return self._value + + def toString(self): + return str(self._value) + + +class LongAccumulator(Stub): + def __init__(self, accumulator_function, identity): + super().__init__() + self._value = identity + self._func = accumulator_function + + def accumulate(self, x): + self._value = self._func(self._value, x) + + def get(self): + return self._value + + def reset(self): + self._value = 0 + + +class LongAdder(Stub): + def __init__(self): + super().__init__() + self._value = 0 + + def add(self, x): + self._value += x + + def sum(self): + return self._value + + def reset(self): + self._value = 0 + + def intValue(self): + return self._value + + def longValue(self): + return self._value + + def toString(self): + return str(self._value) + + +# ============================================================ +# ReentrantLock +# ============================================================ + +class Lock(Stub): + def lock(self): + raise NotImplementedError + + def lockInterruptibly(self): + raise NotImplementedError + + def tryLock(self, timeout=None, unit=None): + raise NotImplementedError + + def unlock(self): + raise NotImplementedError + + def newCondition(self): + raise NotImplementedError + + +class ReentrantLock(Lock): + def __init__(self, fair=False): + super().__init__() + self._lock = _threading.RLock() if not fair else _threading.RLock() + + def lock(self): + self._lock.acquire() + + def lockInterruptibly(self): + self._lock.acquire() + + def tryLock(self, timeout=None, unit=None): + if timeout is not None and unit is not None: + seconds = unit.toMillis(timeout) / 1000.0 + return self._lock.acquire(timeout=seconds) + return self._lock.acquire(blocking=False) + + def unlock(self): + self._lock.release() + + def newCondition(self): + return Condition() + + def getHoldCount(self): + return 1 if self._lock._is_owned() else 0 + + def isHeldByCurrentThread(self): + return self._lock._is_owned() + + def isLocked(self): + return True + + def getQueueLength(self): + return 0 + + def hasQueuedThreads(self): + return False + + def getOwner(self): + return _threading.current_thread() if self.isHeldByCurrentThread() else None + + +# ============================================================ +# ReadWriteLock +# ============================================================ + +class ReadWriteLock(Stub): + def readLock(self): + raise NotImplementedError + + def writeLock(self): + raise NotImplementedError + + +class ReentrantReadWriteLock(ReadWriteLock): + class ReadLock(Lock): + def __init__(self, rw_lock): + super().__init__() + self._rw_lock = rw_lock + + def lock(self): + self._rw_lock._read_lock.acquire() + + def unlock(self): + self._rw_lock._read_lock.release() + + def tryLock(self, timeout=None, unit=None): + return self._rw_lock._read_lock.acquire(blocking=False) + + class WriteLock(Lock): + def __init__(self, rw_lock): + super().__init__() + self._rw_lock = rw_lock + + def lock(self): + self._rw_lock._write_lock.acquire() + + def unlock(self): + self._rw_lock._write_lock.release() + + def tryLock(self, timeout=None, unit=None): + return self._rw_lock._write_lock.acquire(blocking=False) + + def __init__(self, fair=False): + super().__init__() + self._read_lock_obj = _threading.RLock() + self._write_lock_obj = _threading.Lock() + self._read_lock = ReentrantReadWriteLock.ReadLock(self) + self._write_lock = ReentrantReadWriteLock.WriteLock(self) + + def readLock(self): + return self._read_lock + + def writeLock(self): + return self._write_lock + + +class Condition(Stub): + def __init__(self): + super().__init__() + self._cond = _threading.Condition() + + def await_(self, timeout=None, unit=None): + if timeout is not None and unit is not None: + seconds = unit.toMillis(timeout) / 1000.0 + with self._cond: + return self._cond.wait(timeout=seconds) + with self._cond: + self._cond.wait() + + def awaitUninterruptibly(self): + with self._cond: + self._cond.wait() + + def awaitNanos(self, nanos_timeout): + seconds = nanos_timeout / 1_000_000_000.0 + with self._cond: + self._cond.wait(timeout=seconds) + return 0 + + def awaitUntil(self, deadline): + remaining = (deadline - _time.time()) * 1000 + if remaining > 0: + self.await_(remaining, TimeUnit.MILLISECONDS) + + def signal(self): + with self._cond: + self._cond.notify() + + def signalAll(self): + with self._cond: + self._cond.notify_all() + + +class LockSupport(Stub): + @staticmethod + def park(): + _time.sleep(1) + + @staticmethod + def parkNanos(nanos): + _time.sleep(nanos / 1_000_000_000.0) + + @staticmethod + def parkUntil(deadline): + remaining = deadline - _time.time() + if remaining > 0: + _time.sleep(remaining) + + @staticmethod + def unpark(thread): + pass + + +# ============================================================ +# AbstractQueuedSynchronizer (stubs) +# ============================================================ + +class AbstractQueuedSynchronizer(Stub): + def __init__(self): + super().__init__() + self._state = 0 + + def getState(self): + return self._state + + def setState(self, state): + self._state = state + + def compareAndSetState(self, expect, update): + if self._state == expect: + self._state = update + return True + return False + + def acquire(self, arg): + pass + + def acquireInterruptibly(self, arg): + pass + + def tryAcquire(self, arg): + return True + + def tryRelease(self, arg): + return True + + def release(self, arg): + return True + + def acquireShared(self, arg): + pass + + def releaseShared(self, arg): + return True + + def tryAcquireShared(self, arg): + return 0 + + def tryReleaseShared(self, arg): + return True + + def hasQueuedThreads(self): + return False + + def getQueueLength(self): + return 0 + + +class AbstractQueuedLongSynchronizer(AbstractQueuedSynchronizer): + pass + + +# ============================================================ +# Flow / SubmissionPublisher (stubs) +# ============================================================ + +class Flow(Stub): + class Publisher(Stub): + def subscribe(self, subscriber): + pass + + class Subscriber(Stub): + def onSubscribe(self, subscription): + pass + + def onNext(self, item): + pass + + def onError(self, throwable): + pass + + def onComplete(self): + pass + + class Subscription(Stub): + def request(self, n): + pass + + def cancel(self): + pass + + class Processor(Stub): + pass + + +class SubmissionPublisher(Flow.Publisher): + def __init__(self): + super().__init__() + self._subscribers = [] + + def subscribe(self, subscriber): + self._subscribers.append(subscriber) + + def submit(self, item): + for sub in self._subscribers: + sub.onNext(item) + return 1 + + def close(self): + for sub in self._subscribers: + sub.onComplete() + + +# ============================================================ +# ForkJoinPool / ForkJoinTask (stubs) +# ============================================================ + +class ForkJoinPool(Stub): + def __init__(self, parallelism=None): + super().__init__() + self._parallelism = parallelism or _py_math.ceil(_threading._count / 2) + self._pool = _futures.ThreadPoolExecutor(max_workers=self._parallelism) + + def execute(self, task): + self._pool.submit(task.run) + + def submit(self, task): + future = FutureTask(task) + self._pool.submit(future.run) + return future + + def invoke(self, task): + return task.run() + + def shutdown(self): + self._pool.shutdown(wait=False) + + +class ForkJoinTask(Future): + def fork(self): + return self + + def join(self): + return None + + def invoke(self): + return None + + def adapt(self, callable_or_runnable): + return ForkJoinTask() + + +class RecursiveAction(ForkJoinTask): + def compute(self): + raise NotImplementedError + + def run(self): + self.compute() + + +class RecursiveTask(ForkJoinTask): + def compute(self): + raise NotImplementedError + + def run(self): + self.compute() + + +class ForkJoinWorkerThread(Stub): + def __init__(self, pool): + super().__init__() + self._pool = pool + + def getPool(self): + return self._pool \ No newline at end of file diff --git a/android_pyapi/java/util_regex.py b/android_pyapi/java/util_regex.py new file mode 100644 index 0000000..53e9b7b --- /dev/null +++ b/android_pyapi/java/util_regex.py @@ -0,0 +1,287 @@ +""" +Java regex standard library APIs implemented in Python. +java.util.regex package - Pattern, Matcher, MatchResult, PatternSyntaxException. +""" + +from .._core import Stub +from .io import IOException +from .lang import RuntimeException +import re as _re + +IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {}) +IllegalStateException = type("IllegalStateException", (RuntimeException,), {}) + +__all__ = [ + "Pattern", "Matcher", "MatchResult", "PatternSyntaxException", +] + + +# ============================================================ +# PatternSyntaxException +# ============================================================ + +class PatternSyntaxException(IllegalArgumentException): + def __init__(self, desc, regex, index): + super().__init__(f"{desc}: {regex} at index {index}") + self._desc = desc + self._regex = regex + self._index = index + + def getDescription(self): + return self._desc + + def getPattern(self): + return self._regex + + def getIndex(self): + return self._index + + def getMessage(self): + return f"{self._desc} near index {self._index}: {self._regex}" + + +# ============================================================ +# MatchResult +# ============================================================ + +class MatchResult(Stub): + def start(self, group=None): + raise NotImplementedError + + def end(self, group=None): + raise NotImplementedError + + def group(self, group=None): + raise NotImplementedError + + def groupCount(self): + raise NotImplementedError + + +# ============================================================ +# Matcher +# ============================================================ + +class Matcher(MatchResult): + def __init__(self, pattern, input_str): + super().__init__() + self._pattern = pattern + self._input = input_str + self._re_pattern = pattern._pattern + self._match = None + self._region_start = 0 + self._region_end = len(input_str) + self._transparent_bounds = False + self._anchoring_bounds = True + self._last_match = None + + def matches(self): + self._match = self._re_pattern.fullmatch(self._input, self._region_start, self._region_end) + self._last_match = self._match + return self._match is not None + + def find(self, start=None): + if start is not None: + self._match = self._re_pattern.search(self._input, start, self._region_end) + else: + if self._match is None: + self._match = self._re_pattern.search(self._input, self._region_start, self._region_end) + else: + pos = self._match.end() + self._match = self._re_pattern.search(self._input, pos, self._region_end) + if self._match: + self._last_match = self._match + return self._match is not None + + def lookingAt(self): + self._match = self._re_pattern.match(self._input, self._region_start, self._region_end) + self._last_match = self._match + return self._match is not None + + def start(self, group=0): + if self._match is None: + raise IllegalStateException("No match available") + if group == 0: + return self._match.start() + return self._match.start(group) + + def end(self, group=0): + if self._match is None: + raise IllegalStateException("No match available") + if group == 0: + return self._match.end() + return self._match.end(group) + + def group(self, group=0): + if self._match is None: + raise IllegalStateException("No match available") + if isinstance(group, str): + return self._match.group(group) + return self._match.group(group) + + def groupCount(self): + return self._re_pattern.groups + + def replaceAll(self, replacement): + return self._re_pattern.sub(replacement, self._input) + + def replaceFirst(self, replacement): + return self._re_pattern.sub(replacement, self._input, count=1) + + def appendReplacement(self, sb, replacement): + if self._match is None: + raise IllegalStateException("No match available") + self._append_pos = getattr(self, '_append_pos', 0) + sb.append(self._input[self._append_pos:self._match.start()]) + sb.append(self._re_pattern.sub(replacement, self._match.group(), count=1)) + self._append_pos = self._match.end() + return self + + def appendTail(self, sb): + self._append_pos = getattr(self, '_append_pos', 0) + sb.append(self._input[self._append_pos:]) + return sb + + def reset(self, input_str=None): + if input_str is not None: + self._input = input_str + self._region_end = len(input_str) + self._match = None + self._last_match = None + return self + + def region(self, start, end): + self._region_start = start + self._region_end = end + return self + + def regionStart(self): + return self._region_start + + def regionEnd(self): + return self._region_end + + def hasTransparentBounds(self): + return self._transparent_bounds + + def hasAnchoringBounds(self): + return self._anchoring_bounds + + def useAnchoringBounds(self, value): + self._anchoring_bounds = value + return self + + def useTransparentBounds(self, value): + self._transparent_bounds = value + return self + + def hitEnd(self): + return self._match is not None and self._match.end() >= self._region_end + + def requireEnd(self): + return self._match is not None and not self.hitEnd() + + def toMatchResult(self): + result = Matcher(self._pattern, self._input) + result._match = self._match + result._last_match = self._last_match + return result + + def pattern(self): + return self._pattern + + def toString(self): + return f"Matcher[pattern={self._pattern.pattern()}]" + + def namedGroups(self): + return self._re_pattern.groupindex + + +class IllegalStateException(RuntimeException): + pass + + +# ============================================================ +# Pattern +# ============================================================ + +class Pattern(Stub): + CANON_EQ = 128 + CASE_INSENSITIVE = 2 + COMMENTS = 4 + DOTALL = 32 + LITERAL = 16 + MULTILINE = 8 + UNICODE_CASE = 64 + UNICODE_CHARACTER_CLASS = 256 + UNIX_LINES = 1 + + _re_flags_map = { + 0: 0, # default + 2: _re.IGNORECASE, + 8: _re.MULTILINE, + 32: _re.DOTALL, + 64: _re.UNICODE, + 4: _re.VERBOSE, + } + + def __init__(self, regex, flags=0): + super().__init__() + self._regex = str(regex) + self._flags = flags + re_flags = 0 + for java_flag, py_flag in self._re_flags_map.items(): + if flags & java_flag: + re_flags |= py_flag + try: + self._pattern = _re.compile(self._regex, re_flags) + except _re.error as e: + raise PatternSyntaxException(str(e), self._regex, e.pos if hasattr(e, 'pos') else 0) + + @staticmethod + def compile(regex, flags=0): + return Pattern(regex, flags) + + def matcher(self, input_str): + return Matcher(self, input_str) + + def split(self, input_str, limit=0): + if limit <= 0: + parts = self._pattern.split(str(input_str)) + if limit == 0: + while parts and parts[-1] == "": + parts.pop() + else: + parts = self._pattern.split(str(input_str), maxsplit=limit - 1) + while len(parts) < limit: + parts.append("") + return parts + + def splitAsStream(self, input_str): + return iter(self.split(input_str)) + + @staticmethod + def matches(regex, input_str): + return bool(_re.fullmatch(regex, str(input_str))) + + @staticmethod + def quote(s): + return _re.escape(str(s)) + + def flags(self): + return self._flags + + def pattern(self): + return self._regex + + def toString(self): + return self._regex + + def predicate(self): + return lambda s: bool(self._pattern.fullmatch(str(s))) + + def asPredicate(self): + return self.predicate() + + def asMatchPredicate(self): + return lambda s: bool(self._pattern.fullmatch(str(s))) \ No newline at end of file diff --git a/android_pyapi/java/util_zip.py b/android_pyapi/java/util_zip.py new file mode 100644 index 0000000..034616c --- /dev/null +++ b/android_pyapi/java/util_zip.py @@ -0,0 +1,697 @@ +""" +Java zip standard library APIs implemented in Python. +java.util.zip package - ZipFile, ZipEntry, GZIP, CRC, Deflater, Inflater. +""" + +from .._core import Stub +from .io import IOException, Closeable, Flushable +import zipfile as _zipfile +import gzip as _gzip +import zlib as _zlib +import io as _pyio +import os as _os +from datetime import datetime as _datetime + +__all__ = [ + "ZipFile", "ZipEntry", "ZipOutputStream", "ZipInputStream", + "GZIPInputStream", "GZIPOutputStream", + "CRC32", "CheckedInputStream", "CheckedOutputStream", + "Adler32", + "DataFormatException", "ZipException", + "Deflater", "Inflater", + "DeflaterOutputStream", "InflaterInputStream", +] + + +# ============================================================ +# Exception classes +# ============================================================ + +class ZipException(IOException): + pass + +class DataFormatException(Exception): + pass + + +# ============================================================ +# CRC32 +# ============================================================ + +class CRC32(Stub): + def __init__(self): + super().__init__() + self._value = 0 + + def getValue(self): + return self._value + + def reset(self): + self._value = 0 + + def update(self, b, off=None, length=None): + if isinstance(b, int): + self._value = _zlib.crc32(bytes([b & 0xFF]), self._value) & 0xFFFFFFFF + elif isinstance(b, (bytes, bytearray)): + if off is not None and length is not None: + self._value = _zlib.crc32(bytes(b[off:off+length]), self._value) & 0xFFFFFFFF + else: + self._value = _zlib.crc32(bytes(b), self._value) & 0xFFFFFFFF + + +# ============================================================ +# Adler32 +# ============================================================ + +class Adler32(Stub): + def __init__(self): + super().__init__() + self._value = 1 + + def getValue(self): + return self._value + + def reset(self): + self._value = 1 + + def update(self, b, off=None, length=None): + if isinstance(b, int): + self._value = _zlib.adler32(bytes([b & 0xFF]), self._value) & 0xFFFFFFFF + elif isinstance(b, (bytes, bytearray)): + if off is not None and length is not None: + self._value = _zlib.adler32(bytes(b[off:off+length]), self._value) & 0xFFFFFFFF + else: + self._value = _zlib.adler32(bytes(b), self._value) & 0xFFFFFFFF + + +# ============================================================ +# CheckedInputStream / CheckedOutputStream +# ============================================================ + +class CheckedInputStream(Closeable): + def __init__(self, inp, cksum): + super().__init__() + self._in = inp + self._cksum = cksum + + def read(self, b=None, off=None, length=None): + if b is None: + result = self._in.read() + if result >= 0: + self._cksum.update(result) + return result + if off is None: + off = 0 + if length is None: + length = len(b) + result = self._in.read(b, off, length) + if result > 0: + self._cksum.update(b[off:off+result]) + return result + + def getChecksum(self): + return self._cksum + + def close(self): + self._in.close() + + def skip(self, n): + return self._in.skip(n) + + +class CheckedOutputStream(Closeable, Flushable): + def __init__(self, out, cksum): + super().__init__() + self._out = out + self._cksum = cksum + + def write(self, b, off=None, length=None): + if isinstance(b, int): + self._out.write(b) + self._cksum.update(b) + else: + if off is not None and length is not None: + data = bytes(b[off:off+length]) + else: + data = bytes(b) + self._out.write(data) + self._cksum.update(data) + + def getChecksum(self): + return self._cksum + + def flush(self): + self._out.flush() + + def close(self): + self._out.close() + + +# ============================================================ +# ZipEntry +# ============================================================ + +class ZipEntry(Stub): + STORED = 0 + DEFLATED = 8 + + def __init__(self, name): + super().__init__() + self._name = name + self._method = ZipEntry.DEFLATED + self._size = 0 + self._compressed_size = 0 + self._crc = 0 + self._comment = "" + self._time = 0 + self._extra = b"" + self._is_directory = name.endswith("/") + + def getName(self): + return self._name + + def getMethod(self): + return self._method + + def setMethod(self, method): + self._method = method + + def getSize(self): + return self._size + + def setSize(self, size): + self._size = size + + def getCompressedSize(self): + return self._compressed_size + + def setCompressedSize(self, size): + self._compressed_size = size + + def getCrc(self): + return self._crc + + def setCrc(self, crc): + self._crc = crc + + def getComment(self): + return self._comment + + def setComment(self, comment): + self._comment = comment + + def getTime(self): + return self._time + + def setTime(self, time): + self._time = time + + def getExtra(self): + return self._extra + + def setExtra(self, extra): + self._extra = extra + + def isDirectory(self): + return self._is_directory + + def toString(self): + return self._name + + def __str__(self): + return self._name + + +# ============================================================ +# ZipFile +# ============================================================ + +class ZipFile(Closeable): + OPEN_READ = 1 + OPEN_DELETE = 2 + + def __init__(self, file, mode="r"): + super().__init__() + if isinstance(file, str): + path = file + else: + path = file.getPath() + self._path = path + self._mode = mode + try: + self._zfile = _zipfile.ZipFile(path, mode) + except Exception as e: + raise ZipException(str(e)) + + def entries(self): + return iter(self._zfile.infolist()) + + def getEntry(self, name): + try: + info = self._zfile.getinfo(name) + entry = ZipEntry(info.filename) + entry.setMethod(info.compress_type) + entry.setSize(info.file_size) + entry.setCompressedSize(info.compress_size) + entry.setCrc(info.CRC) + entry.setTime(int(_datetime(*info.date_time).timestamp() * 1000) if info.date_time else 0) + return entry + except KeyError: + return None + + def getInputStream(self, entry): + try: + data = self._zfile.read(entry.getName() if isinstance(entry, ZipEntry) else entry) + from ..io import ByteArrayInputStream + return ByteArrayInputStream(data) + except Exception as e: + raise ZipException(str(e)) + + def close(self): + self._zfile.close() + + def size(self): + return len(self._zfile.namelist()) + + def getName(self): + return self._path + + def getComment(self): + return "" + + +# ============================================================ +# ZipOutputStream +# ============================================================ + +class ZipOutputStream(Closeable, Flushable): + STORED = 0 + DEFLATED = 8 + + def __init__(self, out): + super().__init__() + self._out = out + self._zfile = _zipfile.ZipFile(out, "w", _zipfile.ZIP_DEFLATED) + self._method = ZipOutputStream.DEFLATED + self._level = -1 + self._comment = "" + + def putNextEntry(self, entry): + zi = _zipfile.ZipInfo(entry.getName() if isinstance(entry, ZipEntry) else str(entry)) + if hasattr(entry, 'getTime') and entry.getTime(): + dt = _datetime.fromtimestamp(entry.getTime() / 1000.0) + zi.date_time = dt.timetuple()[:6] + self._zfile.writestr(zi, b"") + + def closeEntry(self): + pass + + def write(self, b, off=None, length=None): + if off is not None and length is not None: + self._zfile._zout.write(bytes(b[off:off+length])) + else: + self._zfile._zout.write(bytes(b) if not isinstance(b, (bytes, bytearray)) else b) + + def finish(self): + self._zfile.close() + + def close(self): + self._zfile.close() + + def setMethod(self, method): + self._method = method + + def setLevel(self, level): + self._level = level + + def setComment(self, comment): + self._comment = comment + + def flush(self): + self._zfile._zout.flush() if hasattr(self._zfile._zout, 'flush') else None + + +# ============================================================ +# ZipInputStream +# ============================================================ + +class ZipInputStream(Closeable): + def __init__(self, inp): + super().__init__() + self._inp = inp + self._zfile = _zipfile.ZipFile(inp) + self._entries = iter(self._zfile.infolist()) + self._current_entry = None + self._current_data = b"" + self._current_pos = 0 + + def getNextEntry(self): + try: + info = next(self._entries) + entry = ZipEntry(info.filename) + entry.setMethod(info.compress_type) + entry.setSize(info.file_size) + entry.setCompressedSize(info.compress_size) + entry.setCrc(info.CRC) + self._current_entry = info + self._current_data = self._zfile.read(info) + self._current_pos = 0 + return entry + except StopIteration: + self._current_entry = None + self._current_data = b"" + return None + + def read(self, b=None, off=None, length=None): + if self._current_entry is None: + return -1 + avail = len(self._current_data) - self._current_pos + if avail <= 0: + return -1 + if b is None: + val = self._current_data[self._current_pos] + self._current_pos += 1 + return val + if off is None: + off = 0 + if length is None: + length = len(b) + n = min(length, avail) + if isinstance(b, bytearray): + b[off:off+n] = self._current_data[self._current_pos:self._current_pos+n] + self._current_pos += n + return n + + def closeEntry(self): + self._current_entry = None + self._current_data = b"" + + def available(self): + if self._current_entry is None: + return 0 + return len(self._current_data) - self._current_pos + + def close(self): + self._zfile.close() + + +# ============================================================ +# GZIPInputStream +# ============================================================ + +class GZIPInputStream(Closeable): + def __init__(self, inp, size=512): + super().__init__() + self._inp = inp + self._gzip_file = _gzip.GzipFile(fileobj=_pyio.BufferedReader(inp) if hasattr(inp, 'read') else inp) + + def read(self, b=None, off=None, length=None): + if b is None: + data = self._gzip_file.read(1) + return data[0] if data else -1 + if off is None: + off = 0 + if length is None: + length = len(b) + data = self._gzip_file.read(length) + if not data: + return -1 + n = len(data) + if isinstance(b, bytearray): + b[off:off+n] = data + else: + ba = bytearray(b) + ba[off:off+n] = data + b[:] = bytes(ba) + return n + + def close(self): + self._gzip_file.close() + + +# ============================================================ +# GZIPOutputStream +# ============================================================ + +class GZIPOutputStream(Closeable, Flushable): + def __init__(self, out, size=512): + super().__init__() + self._out = out + self._gzip_file = _gzip.GzipFile(fileobj=out, mode="wb") + + def write(self, b, off=None, length=None): + if isinstance(b, int): + self._gzip_file.write(bytes([b & 0xFF])) + elif isinstance(b, (bytes, bytearray)): + if off is not None and length is not None: + self._gzip_file.write(bytes(b[off:off+length])) + else: + self._gzip_file.write(bytes(b)) + + def finish(self): + self._gzip_file.close() + + def close(self): + self._gzip_file.close() + + def flush(self): + self._gzip_file.flush() + + +# ============================================================ +# Deflater +# ============================================================ + +class Deflater(Stub): + BEST_COMPRESSION = 9 + BEST_SPEED = 1 + DEFAULT_COMPRESSION = -1 + DEFAULT_STRATEGY = 0 + FILTERED = 1 + HUFFMAN_ONLY = 2 + NO_COMPRESSION = 0 + NO_FLUSH = 0 + SYNC_FLUSH = 2 + FULL_FLUSH = 3 + + def __init__(self, level=-1, nowrap=False): + super().__init__() + self._level = level + self._nowrap = nowrap + self._compress = _zlib.compressobj(level=level if level >= 0 else 6, + method=_zlib.DEFLATED, + wbits=-_zlib.MAX_WBITS if nowrap else _zlib.MAX_WBITS) + self._input_buffer = b"" + self._finished = False + + def setInput(self, data, off=0, length=-1): + if length < 0: + length = len(data) - off + self._input_buffer = bytes(data[off:off+length]) + + def needsInput(self): + return len(self._input_buffer) == 0 + + def setDictionary(self, dictionary): + pass + + def getTotalIn(self): + return len(self._input_buffer) + + def getTotalOut(self): + return 0 + + def deflate(self, output, off=0, length=-1): + if length < 0: + length = len(output) - off + data = self._compress.compress(self._input_buffer) + self._input_buffer = b"" + n = min(len(data), length) + if isinstance(output, bytearray): + output[off:off+n] = data[:n] + else: + ba = bytearray(output) + ba[off:off+n] = data[:n] + output[:] = bytes(ba) + return n + + def finish(self): + self._finished = True + + def finished(self): + return self._finished + + def end(self): + pass + + def getAdler(self): + return 1 + + def reset(self): + self._compress = _zlib.compressobj(level=self._level if self._level >= 0 else 6, + method=_zlib.DEFLATED, + wbits=-_zlib.MAX_WBITS if self._nowrap else _zlib.MAX_WBITS) + self._input_buffer = b"" + self._finished = False + + +# ============================================================ +# Inflater +# ============================================================ + +class Inflater(Stub): + def __init__(self, nowrap=False): + super().__init__() + self._nowrap = nowrap + wbits = -_zlib.MAX_WBITS if nowrap else _zlib.MAX_WBITS + self._decompress = _zlib.decompressobj(wbits) + self._input_buffer = b"" + self._finished = False + + def setInput(self, data, off=0, length=-1): + if length < 0: + length = len(data) - off + self._input_buffer = bytes(data[off:off+length]) + + def needsInput(self): + return len(self._input_buffer) == 0 + + def setDictionary(self, dictionary): + pass + + def getTotalIn(self): + return len(self._input_buffer) + + def getTotalOut(self): + return 0 + + def inflate(self, output, off=0, length=-1): + if length < 0: + length = len(output) - off + try: + data = self._decompress.decompress(self._input_buffer) + self._input_buffer = b"" + n = min(len(data), length) + if isinstance(output, bytearray): + output[off:off+n] = data[:n] + else: + ba = bytearray(output) + ba[off:off+n] = data[:n] + output[:] = bytes(ba) + return n + except _zlib.error as e: + raise DataFormatException(str(e)) + + def finished(self): + return self._finished + + def end(self): + pass + + def getAdler(self): + return 1 + + def needsDictionary(self): + return False + + def reset(self): + wbits = -_zlib.MAX_WBITS if self._nowrap else _zlib.MAX_WBITS + self._decompress = _zlib.decompressobj(wbits) + self._input_buffer = b"" + self._finished = False + + +# ============================================================ +# DeflaterOutputStream +# ============================================================ + +class DeflaterOutputStream(Closeable, Flushable): + def __init__(self, out, deflater=None, size=512): + super().__init__() + self._out = out + self._deflater = deflater or Deflater() + self._buffer = bytearray(size) + + def write(self, b, off=None, length=None): + if isinstance(b, int): + self._out.write(bytes([b & 0xFF])) + else: + if off is not None and length is not None: + self._out.write(bytes(b[off:off+length])) + else: + self._deflater.setInput(bytes(b) if isinstance(b, (bytes, bytearray)) else bytes(b)) + while not self._deflater.needsInput(): + n = self._deflater.deflate(self._buffer) + if n > 0: + self._out.write(bytes(self._buffer[:n])) + + def finish(self): + self._deflater.finish() + while not self._deflater.finished(): + n = self._deflater.deflate(self._buffer) + if n > 0: + self._out.write(bytes(self._buffer[:n])) + + def close(self): + self.finish() + self._deflater.end() + self._out.close() + + def flush(self): + self._out.flush() + + +# ============================================================ +# InflaterInputStream +# ============================================================ + +class InflaterInputStream(Closeable): + def __init__(self, inp, inflater=None, size=512): + super().__init__() + self._in = inp + self._inflater = inflater or Inflater() + self._buffer = bytearray(size) + + def read(self, b=None, off=None, length=None): + if b is None: + buf = bytearray(1) + n = self._read_from_inflater(buf, 0, 1) + return buf[0] if n > 0 else -1 + if off is None: + off = 0 + if length is None: + length = len(b) + return self._read_from_inflater(b, off, length) + + def _read_from_inflater(self, b, off, length): + while not self._inflater.finished(): + if self._inflater.needsInput(): + data = self._in.read(self._buffer) + if data is None or data == -1 or data == 0: + break + if isinstance(data, int): + break + self._inflater.setInput(data) + try: + n = self._inflater.inflate(b, off, length) + if n > 0: + return n + except DataFormatException: + break + return -1 + + def available(self): + return 0 + + def close(self): + self._inflater.end() + self._in.close() + + def mark(self, readlimit): + pass + + def reset(self): + raise IOException("mark/reset not supported") + + def markSupported(self): + return False \ No newline at end of file diff --git a/android_pyapi/org/__init__.py b/android_pyapi/org/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/android_pyapi/org/__pycache__/__init__.cpython-310.pyc b/android_pyapi/org/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..0e9bda8 Binary files /dev/null and b/android_pyapi/org/__pycache__/__init__.cpython-310.pyc differ diff --git a/android_pyapi/org/__pycache__/json.cpython-310.pyc b/android_pyapi/org/__pycache__/json.cpython-310.pyc new file mode 100644 index 0000000..608f88b Binary files /dev/null and b/android_pyapi/org/__pycache__/json.cpython-310.pyc differ diff --git a/android_pyapi/org/json.py b/android_pyapi/org/json.py new file mode 100644 index 0000000..140a80c --- /dev/null +++ b/android_pyapi/org/json.py @@ -0,0 +1,858 @@ +""" +Java JSON standard library APIs implemented in Python. +org.json package - JSONObject, JSONArray, JSONTokener, JSONStringer, JSONWriter, etc. +""" + +from .._core import Stub +import json as _json + +__all__ = [ + "JSONObject", "JSONArray", "JSONTokener", + "JSONStringer", "JSONWriter", "JSONException", "JSONPointer", + "JSONPointerBuilder", +] + + +# ============================================================ +# JSONException +# ============================================================ + +class JSONException(Exception): + def __init__(self, *args): + if len(args) == 1 and isinstance(args[0], Exception): + super().__init__(str(args[0])) + self._cause = args[0] + else: + super().__init__(*args) + self._cause = None + + def getCause(self): + return self._cause + + +# ============================================================ +# JSONObject.NULL +# ============================================================ + +class _JSONNull(Stub): + def __init__(self): + super().__init__() + + def equals(self, other): + return other is None or isinstance(other, _JSONNull) + + def hashCode(self): + return 0 + + def toString(self): + return "null" + + +# ============================================================ +# JSONObject +# ============================================================ + +class JSONObject(Stub): + NULL = _JSONNull() + + def __init__(self, *args): + super().__init__() + self._map = {} + if len(args) == 1: + arg = args[0] + if isinstance(arg, str): + tokener = JSONTokener(arg) + obj = tokener.nextValue() + if isinstance(obj, JSONObject): + self._map = obj._map + else: + raise JSONException("String is not a JSONObject") + elif isinstance(arg, dict): + for k, v in arg.items(): + self._map[str(k)] = JSONObject._wrap(v) + elif isinstance(arg, JSONObject): + names = None + self._map = dict(arg._map) + + def _wrap(val): + if val is None: + return JSONObject.NULL + if isinstance(val, (bool, int, float)): + return val + if isinstance(val, str): + return val + if isinstance(val, (list, tuple)): + arr = JSONArray() + for item in val: + arr.put(JSONObject._wrap(item)) + return arr + if isinstance(val, dict): + obj = JSONObject() + for k, v in val.items(): + obj.put(str(k), JSONObject._wrap(v)) + return obj + if isinstance(val, JSONObject) or isinstance(val, JSONArray): + return val + return val + + def put(self, key, value): + self._map[str(key)] = JSONObject._wrap(value) + return self + + def accumulate(self, key, value): + k = str(key) + current = self._map.get(k) + if current is None: + arr = JSONArray() + arr.put(value) + self._map[k] = arr + elif isinstance(current, JSONArray): + current.put(value) + else: + arr = JSONArray() + arr.put(current) + arr.put(value) + self._map[k] = arr + return self + + def get(self, key): + k = str(key) + if k not in self._map: + raise JSONException(f"JSONObject[\"{k}\"] not found") + val = self._map[k] + if val is JSONObject.NULL: + raise JSONException(f"JSONObject[\"{k}\"] is null") + return val + + def opt(self, key, default=None): + k = str(key) + val = self._map.get(k) + if val is None or val is JSONObject.NULL: + return default + return val + + def getString(self, key): + val = self.get(key) + if isinstance(val, str): + return val + raise JSONException(f"JSONObject[\"{key}\"] is not a string") + + def getInt(self, key): + val = self.get(key) + if isinstance(val, int): + return val + if isinstance(val, float): + return int(val) + raise JSONException(f"JSONObject[\"{key}\"] is not an int") + + def getLong(self, key): + val = self.get(key) + if isinstance(val, int): + return val + if isinstance(val, float): + return int(val) + raise JSONException(f"JSONObject[\"{key}\"] is not a long") + + def getDouble(self, key): + val = self.get(key) + if isinstance(val, (int, float)): + return float(val) + raise JSONException(f"JSONObject[\"{key}\"] is not a double") + + def getBoolean(self, key): + val = self.get(key) + if isinstance(val, bool): + return val + if val == "true": + return True + if val == "false": + return False + raise JSONException(f"JSONObject[\"{key}\"] is not a boolean") + + def getJSONArray(self, key): + val = self.get(key) + if isinstance(val, JSONArray): + return val + raise JSONException(f"JSONObject[\"{key}\"] is not a JSONArray") + + def getJSONObject(self, key): + val = self.get(key) + if isinstance(val, JSONObject): + return val + raise JSONException(f"JSONObject[\"{key}\"] is not a JSONObject") + + def optString(self, key, default=""): + val = self.opt(key) + if val is None or val is JSONObject.NULL: + return default + return str(val) + + def optInt(self, key, default=0): + val = self.opt(key) + if val is None or val is JSONObject.NULL: + return default + if isinstance(val, int): + return val + if isinstance(val, float): + return int(val) + try: + return int(val) + except (ValueError, TypeError): + return default + + def optLong(self, key, default=0): + return self.optInt(key, default) + + def optDouble(self, key, default=0.0): + val = self.opt(key) + if val is None or val is JSONObject.NULL: + return default + if isinstance(val, (int, float)): + return float(val) + try: + return float(val) + except (ValueError, TypeError): + return default + + def optBoolean(self, key, default=False): + val = self.opt(key) + if val is None or val is JSONObject.NULL: + return default + if isinstance(val, bool): + return val + if isinstance(val, str): + return val.lower() == "true" + return default + + def optJSONArray(self, key): + val = self.opt(key) + if isinstance(val, JSONArray): + return val + return None + + def optJSONObject(self, key): + val = self.opt(key) + if isinstance(val, JSONObject): + return val + return None + + def has(self, key): + return str(key) in self._map + + def remove(self, key): + k = str(key) + if k in self._map: + del self._map[k] + + def keys(self): + return iter(self._map.keys()) + + def names(self): + if not self._map: + return None + arr = JSONArray() + for k in self._map: + arr.put(k) + return arr + + def length(self): + return len(self._map) + + def toString(self, indent_factor=None): + if indent_factor is None: + return self._to_json_string() + return self._to_json_pretty(indent_factor) + + def _to_json_string(self): + parts = ["{"] + for i, (k, v) in enumerate(self._map.items()): + parts.append('"' + k + '": ' + JSONObject._value_to_string(v)) + if i < len(self._map) - 1: + parts.append(", ") + parts.append("}") + return "".join(parts) + + def _to_json_pretty(self, indent): + parts = ["{"] + for i, (k, v) in enumerate(self._map.items()): + parts.append("\n" + " " * indent + '"' + k + '": ' + JSONObject._value_to_string(v)) + if i < len(self._map) - 1: + parts.append(",") + parts.append("\n}") + return "".join(parts) + + def _value_to_string(val): + if val is None or val is JSONObject.NULL: + return "null" + if isinstance(val, bool): + return "true" if val else "false" + if isinstance(val, (int, float)): + return str(val) + if isinstance(val, str): + return _json.dumps(val) + if isinstance(val, JSONObject): + return val._to_json_string() + if isinstance(val, JSONArray): + return val._to_json_string() + return str(val) + + def toMap(self): + result = {} + for k, v in self._map.items(): + if isinstance(v, JSONObject): + result[k] = v.toMap() + elif isinstance(v, JSONArray): + result[k] = v.toList() + elif v is JSONObject.NULL: + result[k] = None + else: + result[k] = v + return result + + def write(self, writer): + writer._write(self._to_json_string()) + + def toJSONArray(self, names): + if names is None or names.length() == 0: + return None + arr = JSONArray() + for i in range(names.length()): + arr.put(self.opt(names.getString(i))) + return arr + + def hashCode(self): + return hash(frozenset(self._map.items())) + + def equals(self, other): + if isinstance(other, JSONObject): + return self._map == other._map + return False + + +# ============================================================ +# JSONArray +# ============================================================ + +class JSONArray(Stub): + def __init__(self, *args): + super().__init__() + self._list = [] + if len(args) == 1: + arg = args[0] + if isinstance(arg, str): + tokener = JSONTokener(arg) + val = tokener.nextValue() + if isinstance(val, JSONArray): + self._list = val._list + else: + raise JSONException("String is not a JSONArray") + elif isinstance(arg, (list, tuple)): + for item in arg: + self.put(JSONObject._wrap(item)) + + def put(self, *args): + if len(args) == 1: + self._list.append(JSONObject._wrap(args[0])) + elif len(args) == 2: + index, value = args + while len(self._list) <= index: + self._list.append(None) + self._list[index] = JSONObject._wrap(value) + return self + + def get(self, index): + if index < 0 or index >= len(self._list): + raise JSONException(f"JSONArray[{index}] not found") + val = self._list[index] + if val is JSONObject.NULL: + raise JSONException(f"JSONArray[{index}] is null") + return val + + def getString(self, index): + val = self.get(index) + if isinstance(val, str): + return val + raise JSONException(f"JSONArray[{index}] is not a string") + + def getInt(self, index): + val = self.get(index) + if isinstance(val, int): + return val + if isinstance(val, float): + return int(val) + raise JSONException(f"JSONArray[{index}] is not an int") + + def getLong(self, index): + val = self.get(index) + if isinstance(val, int): + return val + if isinstance(val, float): + return int(val) + raise JSONException(f"JSONArray[{index}] is not a long") + + def getDouble(self, index): + val = self.get(index) + if isinstance(val, (int, float)): + return float(val) + raise JSONException(f"JSONArray[{index}] is not a double") + + def getBoolean(self, index): + val = self.get(index) + if isinstance(val, bool): + return val + if val == "true": + return True + if val == "false": + return False + raise JSONException(f"JSONArray[{index}] is not a boolean") + + def getJSONArray(self, index): + val = self.get(index) + if isinstance(val, JSONArray): + return val + raise JSONException(f"JSONArray[{index}] is not a JSONArray") + + def getJSONObject(self, index): + val = self.get(index) + if isinstance(val, JSONObject): + return val + raise JSONException(f"JSONArray[{index}] is not a JSONObject") + + def opt(self, index, default=None): + if index < 0 or index >= len(self._list): + return default + val = self._list[index] + if val is JSONObject.NULL: + return default + return val + + def optString(self, index, default=""): + val = self.opt(index) + if val is None: + return default + return str(val) + + def optInt(self, index, default=0): + val = self.opt(index) + if val is None: + return default + if isinstance(val, int): + return val + if isinstance(val, float): + return int(val) + try: + return int(val) + except (ValueError, TypeError): + return default + + def optLong(self, index, default=0): + return self.optInt(index, default) + + def optDouble(self, index, default=0.0): + val = self.opt(index) + if val is None: + return default + if isinstance(val, (int, float)): + return float(val) + try: + return float(val) + except (ValueError, TypeError): + return default + + def optBoolean(self, index, default=False): + val = self.opt(index) + if val is None: + return default + if isinstance(val, bool): + return val + if isinstance(val, str): + return val.lower() == "true" + return default + + def optJSONArray(self, index): + val = self.opt(index) + if isinstance(val, JSONArray): + return val + return None + + def optJSONObject(self, index): + val = self.opt(index) + if isinstance(val, JSONObject): + return val + return None + + def remove(self, index): + if 0 <= index < len(self._list): + return self._list.pop(index) + return None + + def length(self): + return len(self._list) + + def isNull(self, index): + if 0 <= index < len(self._list): + return self._list[index] is JSONObject.NULL or self._list[index] is None + return True + + def toString(self, indent_factor=None): + if indent_factor is None: + return self._to_json_string() + return self._to_json_pretty(indent_factor) + + def _to_json_string(self): + parts = ["["] + for i, v in enumerate(self._list): + parts.append(JSONObject._value_to_string(v)) + if i < len(self._list) - 1: + parts.append(", ") + parts.append("]") + return "".join(parts) + + def _to_json_pretty(self, indent): + parts = ["["] + for i, v in enumerate(self._list): + parts.append("\n" + " " * indent + JSONObject._value_to_string(v)) + if i < len(self._list) - 1: + parts.append(",") + parts.append("\n]") + return "".join(parts) + + def toList(self): + result = [] + for v in self._list: + if isinstance(v, JSONObject): + result.append(v.toMap()) + elif isinstance(v, JSONArray): + result.append(v.toList()) + elif v is JSONObject.NULL: + result.append(None) + else: + result.append(v) + return result + + def join(self, separator): + parts = [] + for v in self._list: + parts.append(JSONObject._value_to_string(v)) + return separator.join(parts) + + +# ============================================================ +# JSONTokener +# ============================================================ + +class JSONTokener(Stub): + def __init__(self, source): + super().__init__() + if hasattr(source, "read"): + self._source = source.read() + else: + self._source = str(source) + self._index = 0 + + def nextValue(self): + self._skipWhitespace() + if self._index >= len(self._source): + raise JSONException("Unexpected end of input") + ch = self._source[self._index] + if ch == "{": + return self._readObject() + if ch == "[": + return self._readArray() + if ch == '"': + return self._readString() + if ch == "'": + return self._readString("'") + if ch == "t" or ch == "f": + return self._readLiteral() + if ch == "n": + return self._readNull() + return self._readNumber() + + def _skipWhitespace(self): + while self._index < len(self._source) and self._source[self._index] in " \t\n\r": + self._index += 1 + + def _readObject(self): + self._index += 1 # skip { + obj = JSONObject() + self._skipWhitespace() + if self._index < len(self._source) and self._source[self._index] == "}": + self._index += 1 + return obj + while True: + self._skipWhitespace() + key = self.nextValue() + self._skipWhitespace() + if self._index < len(self._source) and self._source[self._index] == ":": + self._index += 1 + self._skipWhitespace() + val = self.nextValue() + obj.put(str(key), val) + self._skipWhitespace() + if self._index < len(self._source) and self._source[self._index] == ",": + self._index += 1 + elif self._index < len(self._source) and self._source[self._index] == "}": + self._index += 1 + return obj + else: + raise JSONException("Expected ',' or '}' in object") + + def _readArray(self): + self._index += 1 # skip [ + arr = JSONArray() + self._skipWhitespace() + if self._index < len(self._source) and self._source[self._index] == "]": + self._index += 1 + return arr + while True: + self._skipWhitespace() + val = self.nextValue() + arr.put(val) + self._skipWhitespace() + if self._index < len(self._source) and self._source[self._index] == ",": + self._index += 1 + elif self._index < len(self._source) and self._source[self._index] == "]": + self._index += 1 + return arr + else: + raise JSONException("Expected ',' or ']' in array") + + def _readString(self, quote='"'): + self._index += 1 # skip quote + result = [] + while self._index < len(self._source): + ch = self._source[self._index] + if ch == "\\": + self._index += 1 + if self._index >= len(self._source): + raise JSONException("Unterminated string escape") + esc = self._source[self._index] + if esc == '"': + result.append('"') + elif esc == "\\": + result.append("\\") + elif esc == "/": + result.append("/") + elif esc == "b": + result.append("\b") + elif esc == "f": + result.append("\f") + elif esc == "n": + result.append("\n") + elif esc == "r": + result.append("\r") + elif esc == "t": + result.append("\t") + elif esc == "u": + hex_str = self._source[self._index+1:self._index+5] + result.append(chr(int(hex_str, 16))) + self._index += 4 + else: + result.append(esc) + self._index += 1 + elif ch == quote: + self._index += 1 + return "".join(result) + else: + result.append(ch) + self._index += 1 + raise JSONException("Unterminated string") + + def _readLiteral(self): + for lit in ("true", "false"): + if self._source[self._index:self._index+len(lit)] == lit: + self._index += len(lit) + return lit == "true" + raise JSONException(f"Unexpected literal at {self._index}") + + def _readNull(self): + if self._source[self._index:self._index+4] == "null": + self._index += 4 + return JSONObject.NULL + raise JSONException(f"Unexpected token at {self._index}") + + def _readNumber(self): + start = self._index + while self._index < len(self._source) and self._source[self._index] in "-+0123456789.eE": + self._index += 1 + num_str = self._source[start:self._index] + if "." in num_str or "e" in num_str or "E" in num_str: + return float(num_str) + return int(num_str) + + def nextString(self, quote='"'): + return self._readString(quote) + + def nextChar(self, c): + self._skipWhitespace() + if self._index < len(self._source) and self._source[self._index] == c: + self._index += 1 + return c + raise JSONException(f"Expected '{c}'") + + def nextTo(self, delimiter): + start = self._index + while self._index < len(self._source) and self._source[self._index] not in delimiter: + self._index += 1 + return self._source[start:self._index] + + def skipPast(self, text): + idx = self._source.find(text, self._index) + if idx >= 0: + self._index = idx + len(text) + + def skipTo(self, c): + idx = self._source.find(c, self._index) + if idx >= 0: + self._index = idx + return idx + return -1 + + def back(self): + self._index -= 1 + + def more(self): + self._skipWhitespace() + return self._index < len(self._source) + + def readString(self): + return self._readString() + + +# ============================================================ +# JSONWriter +# ============================================================ + +class JSONWriter(Stub): + def __init__(self, writer): + super().__init__() + self._writer = writer + self._stack = [] + self._in_object = False + self._need_comma = False + + def _write(self, s): + self._writer.write(s) + + def array(self): + self._beforeValue() + self._stack.append(("array", False)) + self._write("[") + return self + + def endArray(self): + self._write("]") + self._stack.pop() + return self + + def object(self): + self._beforeValue() + self._stack.append(("object", False)) + self._write("{") + return self + + def endObject(self): + self._write("}") + self._stack.pop() + return self + + def key(self, s): + if not self._stack or self._stack[-1][0] != "object": + raise JSONException("Can't set key outside object") + self._beforeKey() + self._write('"' + s + '":') + return self + + def value(self, v): + self._beforeValue() + self._write(JSONObject._value_to_string(v)) + return self + + def _beforeValue(self): + if self._stack and self._stack[-1][0] == "object": + raise JSONException("Expected key in object") + + def _beforeKey(self): + if self._stack and self._stack[-1][1]: + self._write(",") + self._stack[-1] = (self._stack[-1][0], True) + + +# ============================================================ +# JSONStringer +# ============================================================ + +class JSONStringer(JSONWriter): + def __init__(self): + from io import StringIO + super().__init__(StringIO()) + + def toString(self): + return self._writer.getvalue() + + +# ============================================================ +# JSONPointer +# ============================================================ + +class JSONPointer(Stub): + def __init__(self, pointer): + super().__init__() + if not pointer or pointer == "": + self._tokens = [] + elif pointer.startswith("#"): + pointer = pointer[1:] + pointer = pointer.replace("~1", "/").replace("~0", "~") + import urllib.parse + pointer = urllib.parse.unquote(pointer) + self._tokens = pointer.split("/")[1:] if len(pointer) > 1 else [] + else: + self._tokens = pointer.split("/")[1:] if len(pointer) > 0 else [] + + def query(self, root): + current = root + for token in self._tokens: + token = token.replace("~1", "/").replace("~0", "~") + if isinstance(current, JSONObject): + current = current.get(token) + elif isinstance(current, JSONArray): + try: + idx = int(token) + current = current.get(idx) + except (ValueError, IndexError): + raise JSONException(f"Invalid index: {token}") + else: + raise JSONException(f"Cannot index into {type(current)}") + return current + + def queryFrom(self, root): + return self.query(root) + + def toString(self): + return "#/" + "/".join(t.replace("~", "~0").replace("/", "~1") for t in self._tokens) + + def hashCode(self): + return hash(tuple(self._tokens)) + + def equals(self, other): + if isinstance(other, JSONPointer): + return self._tokens == other._tokens + return False + + +# ============================================================ +# JSONPointerBuilder +# ============================================================ + +class JSONPointerBuilder(Stub): + def __init__(self): + super().__init__() + self._tokens = [] + + def append(self, token): + self._tokens.append(str(token)) + return self + + def build(self): + return JSONPointer("#/" + "/".join(t.replace("~", "~0").replace("/", "~1") for t in self._tokens)) \ No newline at end of file