- android/ 167个模块 (app/content/os/view/widget/graphics/media/hardware/net/telephony/provider等) - java/ 35个模块 (lang/io/util/net/nio/security/crypto/sql/beans/rmi/awt等) - javax/ 4个模块 (net/crypto/security/sql) - org/ 5个模块 (json/w3c_dom/xml_sax/xml_pull/apache_http) - dalvik/ 1个模块 (system) - ~3053个类/接口,方法签名完全对齐Android SDK - 桌面跨平台运行 (Windows/macOS/Linux) - 修复所有导入错误和Python关键字冲突
564 lines
18 KiB
Python
564 lines
18 KiB
Python
"""
|
|
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",
|
|
] |