""" Android content.res 组件 — Resources, AssetManager, Configuration 等 方法签名完全对齐 Android SDK """ from __future__ import annotations import io import os import struct import sys from pathlib import Path from typing import Any, Dict, List, Optional, Set, Tuple, Union from xml.etree import ElementTree from .._core import Stub, AndroidOnlyError, android_context, resolve_android_path from ..java.lang import RuntimeException IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {}) # ═══════════════════════════════════════════════════════════════ # Resources # ═══════════════════════════════════════════════════════════════ class Resources(Stub): """Resources — 资源访问""" def __init__(self, assets: Optional["AssetManager"] = None): super().__init__() self._assets = assets or AssetManager() self._strings: Dict[str, str] = {} self._colors: Dict[str, int] = {} self._dimens: Dict[str, float] = {} self._drawables: Dict[str, Any] = {} self._booleans: Dict[str, bool] = {} self._integers: Dict[str, int] = {} self._identifiers: Dict[str, int] = {} self._next_id = 0x7f010000 self._configuration = Configuration() self._display_metrics = DisplayMetrics() # ── String ── def getString(self, id: Union[int, str], *format_args: Any) -> Optional[str]: if isinstance(id, str): return self._strings.get(id) for key, val in self._strings.items(): if self._identifiers.get(key) == id: return val.format(*format_args) if format_args else val return None def getText(self, id: Union[int, str]) -> Optional[str]: return self.getString(id) def getQuantityString(self, id: Union[int, str], quantity: int, *format_args: Any) -> str: base = self.getString(id) or "" return base.format(quantity, *format_args) # ── Color ── def getColor(self, id: Union[int, str], theme: Optional["Resources.Theme"] = None) -> int: if isinstance(id, str): return self._colors.get(id, 0xFF000000) for key, val in self._colors.items(): if self._identifiers.get(key) == id: return val return 0xFF000000 def getColorStateList(self, id: Union[int, str], theme: Optional["Resources.Theme"] = None) -> Any: return self.getColor(id, theme) # ── Dimension ── def getDimension(self, id: Union[int, str]) -> float: if isinstance(id, str): return self._dimens.get(id, 0.0) for key, val in self._dimens.items(): if self._identifiers.get(key) == id: return val return 0.0 def getDimensionPixelSize(self, id: Union[int, str]) -> int: return int(self.getDimension(id)) def getDimensionPixelOffset(self, id: Union[int, str]) -> int: return int(self.getDimension(id)) # ── Drawable ── def getDrawable(self, id: Union[int, str], theme: Optional["Resources.Theme"] = None) -> Any: if isinstance(id, str): return self._drawables.get(id) for key, val in self._drawables.items(): if self._identifiers.get(key) == id: return val return None # ── Identifier ── def getIdentifier(self, name: str, defType: Optional[str] = None, defPackage: Optional[str] = None) -> int: key = f"{defPackage}:{defType}/{name}" if defType else name if key not in self._identifiers: self._next_id += 1 self._identifiers[key] = self._next_id return self._identifiers[key] # ── Boolean / Integer ── def getBoolean(self, id: Union[int, str]) -> bool: if isinstance(id, str): return self._booleans.get(id, False) for key, val in self._booleans.items(): if self._identifiers.get(key) == id: return val return False def getInteger(self, id: Union[int, str]) -> int: if isinstance(id, str): return self._integers.get(id, 0) for key, val in self._integers.items(): if self._identifiers.get(key) == id: return val return 0 # ── Layout / Animation / Xml ── def getLayout(self, id: Union[int, str]) -> Any: return self.getDrawable(id) def getAnimation(self, id: Union[int, str]) -> Any: return self.getDrawable(id) def getXml(self, id: Union[int, str]) -> "XmlResourceParser": return XmlResourceParser() # ── Assets ── def getAssets(self) -> "AssetManager": return self._assets # ── Config / Metrics ── def getConfiguration(self) -> "Configuration": return self._configuration def getDisplayMetrics(self) -> "DisplayMetrics": return self._display_metrics def getSystem(self) -> "Resources": return self # ── Resource names ── def getResourceEntryName(self, resid: int) -> str: for key, val in self._identifiers.items(): if val == resid: return key.split("/")[-1] if "/" in key else key return "unknown" def getResourceName(self, resid: int) -> str: for key, val in self._identifiers.items(): if val == resid: return key return "unknown" def getResourcePackageName(self, resid: int) -> str: for key, val in self._identifiers.items(): if val == resid: parts = key.split(":") return parts[0] if len(parts) > 1 else "" return "" def getResourceTypeName(self, resid: int) -> str: for key, val in self._identifiers.items(): if val == resid: parts = key.split("/") if len(parts) > 1: type_part = parts[0] if ":" in type_part: return type_part.split(":")[1] return type_part return "" return "" # ── Raw resources ── def openRawResource(self, id: int) -> io.BytesIO: return io.BytesIO(b"") def openRawResourceFd(self, id: int) -> Any: return None # ── Mutation helpers ── def _put_string(self, name: str, value: str): self._strings[name] = value def _put_color(self, name: str, value: int): self._colors[name] = value def _put_dimension(self, name: str, value: float): self._dimens[name] = value def _put_drawable(self, name: str, value: Any): self._drawables[name] = value def _put_boolean(self, name: str, value: bool): self._booleans[name] = value def _put_integer(self, name: str, value: int): self._integers[name] = value # ── Theme ── class Theme(Stub): """Resources.Theme — 主题""" def __init__(self, resources: "Resources"): super().__init__() self._resources = resources self._styles: Dict[int, Any] = {} def applyStyle(self, resid: int, force: bool = False): pass def setTo(self, other: "Resources.Theme"): self._styles = dict(other._styles) def getDrawable(self, id: int) -> Any: return self._resources.getDrawable(id) def getColor(self, id: int) -> int: return self._resources.getColor(id) def getResources(self) -> "Resources": return self._resources def toString(self) -> str: return f"Resources.Theme@{hex(id(self))}" def hashCode(self) -> int: return hash(id(self)) & 0x7FFFFFFF def equals(self, other) -> bool: return self is other # ═══════════════════════════════════════════════════════════════ # AssetManager # ═══════════════════════════════════════════════════════════════ class AssetManager(Stub): """AssetManager — 资源资产管理""" ACCESS_UNKNOWN = 0 ACCESS_STREAMING = 1 ACCESS_BUFFER = 2 ACCESS_RANDOM = 3 def __init__(self): super().__init__() self._assets_dir = os.path.join( os.path.expanduser("~"), "Android-compat", "assets" ) os.makedirs(self._assets_dir, exist_ok=True) self._open: bool = True def open(self, fileName: str, access: int = ACCESS_STREAMING) -> io.BytesIO: path = os.path.join(self._assets_dir, fileName) if os.path.isfile(path): with open(path, "rb") as f: return io.BytesIO(f.read()) return io.BytesIO(b"") def openFd(self, fileName: str) -> Any: path = os.path.join(self._assets_dir, fileName) if os.path.isfile(path): return open(path, "rb") return None def openNonAssetFd(self, fileName: str) -> Any: return self.openFd(fileName) def list(self, path: str = "") -> List[str]: dir_path = os.path.join(self._assets_dir, path) if os.path.isdir(dir_path): return os.listdir(dir_path) return [] def close(self): self._open = False def toString(self) -> str: return f"AssetManager@{hex(id(self))}" # ═══════════════════════════════════════════════════════════════ # Configuration # ═══════════════════════════════════════════════════════════════ class Configuration(Stub): """Configuration — 设备配置""" # Color mode constants COLOR_MODE_DEFAULT = 0 COLOR_MODE_WIDE_COLOR_GAMUT = 1 COLOR_MODE_HDR = 2 # Density constants DENSITY_DPI_UNDEFINED = 0 DENSITY_DPI_LOW = 120 DENSITY_DPI_MEDIUM = 160 DENSITY_DPI_HIGH = 240 DENSITY_DPI_XHIGH = 320 DENSITY_DPI_XXHIGH = 480 DENSITY_DPI_XXXHIGH = 640 DENSITY_DPI_TV = 213 # Screen layout size SCREENLAYOUT_SIZE_SMALL = 1 SCREENLAYOUT_SIZE_NORMAL = 2 SCREENLAYOUT_SIZE_LARGE = 3 SCREENLAYOUT_SIZE_XLARGE = 4 # Orientation ORIENTATION_LANDSCAPE = 2 ORIENTATION_PORTRAIT = 1 ORIENTATION_SQUARE = 3 ORIENTATION_UNDEFINED = 0 # UI mode UI_MODE_TYPE_NORMAL = 1 UI_MODE_TYPE_DESK = 2 UI_MODE_TYPE_CAR = 3 UI_MODE_TYPE_TELEVISION = 4 UI_MODE_TYPE_APPLIANCE = 5 UI_MODE_TYPE_WATCH = 6 UI_MODE_TYPE_VR_HEADSET = 7 UI_MODE_NIGHT_UNDEFINED = 0 UI_MODE_NIGHT_NO = 1 UI_MODE_NIGHT_YES = 2 # Touchscreen TOUCHSCREEN_UNDEFINED = 0 TOUCHSCREEN_NOTOUCH = 1 TOUCHSCREEN_FINGER = 3 # Keyboard KEYBOARD_UNDEFINED = 0 KEYBOARD_NOKEYS = 1 KEYBOARD_QWERTY = 2 KEYBOARD_12KEY = 3 # Keyboard hidden KEYBOARDHIDDEN_UNDEFINED = 0 KEYBOARDHIDDEN_NO = 1 KEYBOARDHIDDEN_YES = 2 # Hard keyboard hidden HARDKEYBOARDHIDDEN_UNDEFINED = 0 HARDKEYBOARDHIDDEN_NO = 1 HARDKEYBOARDHIDDEN_YES = 2 # Navigation NAVIGATION_UNDEFINED = 0 NAVIGATION_NONAV = 1 NAVIGATION_DPAD = 2 NAVIGATION_TRACKBALL = 3 NAVIGATION_WHEEL = 4 # Navigation hidden NAVIGATIONHIDDEN_UNDEFINED = 0 NAVIGATIONHIDDEN_NO = 1 NAVIGATIONHIDDEN_YES = 2 def __init__(self): super().__init__() self.fontScale: float = 1.0 self.mcc: int = 310 self.mnc: int = 260 self.locale: Optional[str] = "en-US" self.screenLayout: int = self.SCREENLAYOUT_SIZE_NORMAL self.touchscreen: int = self.TOUCHSCREEN_FINGER self.keyboard: int = self.KEYBOARD_QWERTY self.keyboardHidden: int = self.KEYBOARDHIDDEN_NO self.hardKeyboardHidden: int = self.HARDKEYBOARDHIDDEN_NO self.navigation: int = self.NAVIGATION_NONAV self.navigationHidden: int = self.NAVIGATIONHIDDEN_NO self.orientation: int = self.ORIENTATION_PORTRAIT self.screenWidthDp: int = 360 self.screenHeightDp: int = 640 self.smallestScreenWidthDp: int = 360 self.densityDpi: int = self.DENSITY_DPI_MEDIUM self.uiMode: int = self.UI_MODE_TYPE_NORMAL | self.UI_MODE_NIGHT_NO def toString(self) -> str: return (f"Configuration{{fontScale={self.fontScale}, mcc={self.mcc}, " f"mnc={self.mnc}, locale={self.locale}, " f"screenLayout={self.screenLayout}, orientation={self.orientation}, " f"screenWidthDp={self.screenWidthDp}, screenHeightDp={self.screenHeightDp}}}") # ═══════════════════════════════════════════════════════════════ # DisplayMetrics # ═══════════════════════════════════════════════════════════════ class DisplayMetrics(Stub): """DisplayMetrics — 显示指标""" DENSITY_DEFAULT = 160 DENSITY_LOW = 120 DENSITY_MEDIUM = 160 DENSITY_HIGH = 240 DENSITY_XHIGH = 320 DENSITY_XXHIGH = 480 DENSITY_XXXHIGH = 640 DENSITY_TV = 213 def __init__(self): super().__init__() self.widthPixels: int = 1080 self.heightPixels: int = 1920 self.densityDpi: int = self.DENSITY_DEFAULT self.density: float = 1.0 self.scaledDensity: float = 1.0 self.xdpi: float = 160.0 self.ydpi: float = 160.0 self.noncompatWidthPixels: int = 1080 self.noncompatHeightPixels: int = 1920 self.noncompatDensityDpi: int = self.DENSITY_DEFAULT self.noncompatDensity: float = 1.0 self.noncompatScaledDensity: float = 1.0 self.noncompatXdpi: float = 160.0 self.noncompatYdpi: float = 160.0 self.densityDpi: int = self.DENSITY_DEFAULT def toString(self) -> str: return (f"DisplayMetrics{{width={self.widthPixels}, height={self.heightPixels}, " f"densityDpi={self.densityDpi}, density={self.density:.2f}}}") # ═══════════════════════════════════════════════════════════════ # TypedArray # ═══════════════════════════════════════════════════════════════ class TypedArray(Stub): """TypedArray — 类型化数组""" def __init__(self): super().__init__() self._data: Dict[int, Any] = {} def getString(self, index: int) -> Optional[str]: val = self._data.get(index) return str(val) if val is not None else None def getInt(self, index: int, defValue: int = 0) -> int: val = self._data.get(index) return int(val) if val is not None else defValue def getFloat(self, index: int, defValue: float = 0.0) -> float: val = self._data.get(index) return float(val) if val is not None else defValue def getBoolean(self, index: int, defValue: bool = False) -> bool: val = self._data.get(index) return bool(val) if val is not None else defValue def getColor(self, index: int, defValue: int = 0) -> int: val = self._data.get(index) return int(val) if val is not None else defValue def getDimension(self, index: int, defValue: float = 0.0) -> float: val = self._data.get(index) return float(val) if val is not None else defValue def getDrawable(self, index: int) -> Any: return self._data.get(index) def getResourceId(self, index: int, defValue: int = 0) -> int: val = self._data.get(index) return int(val) if val is not None else defValue def getText(self, index: int) -> Optional[str]: return self.getString(index) def hasValue(self, index: int) -> bool: return index in self._data def length(self) -> int: return len(self._data) def recycle(self): self._data.clear() def toString(self) -> str: return f"TypedArray@{hex(id(self))}" # ═══════════════════════════════════════════════════════════════ # XmlResourceParser # ═══════════════════════════════════════════════════════════════ class XmlResourceParser(Stub): """XmlResourceParser — XML 资源解析器""" START_DOCUMENT = 0 END_DOCUMENT = 1 START_TAG = 2 END_TAG = 3 TEXT = 4 def __init__(self): super().__init__() self._root: Optional[ElementTree.Element] = None self._event_type = self.START_DOCUMENT def next(self) -> int: return self.END_DOCUMENT def getEventType(self) -> int: return self._event_type def getName(self) -> Optional[str]: return None def getText(self) -> Optional[str]: return None def getAttributeCount(self) -> int: return 0 def getAttributeName(self, index: int) -> Optional[str]: return None def getAttributeValue(self, index: int) -> Optional[str]: return None def getAttributeValue(self, namespace: Optional[str], name: str) -> Optional[str]: return None def close(self): pass def toString(self) -> str: return f"XmlResourceParser@{hex(id(self))}" # ═══════════════════════════════════════════════════════════════ # __all__ # ═══════════════════════════════════════════════════════════════ __all__ = [ "Resources", "AssetManager", "Configuration", "DisplayMetrics", "TypedArray", "XmlResourceParser", ]