SQLite真实数据库: - SQLiteDatabase: execSQL/insert/insertOrThrow/replace/update/delete/query 用sqlite3 - 修复getVersion/setVersion用PRAGMA user_version - 修复getMaximumSize/getPageSize/setMaximumSize/setPageSize用PRAGMA - SQLiteStatement: execute/simpleQueryForLong/simpleQueryForString/execQuery - SQLiteOpenHelper: onCreate/onUpgrade/onDowngrade回调正确触发 - 修复SQLiteCursor.moveToPosition越界处理 - 修复ContentValues值对提取bug JSON真实实现: - JSONObject: isNull/append/putOnce/putOpt/getNames - JSONArray: 完整put/get/opt/length/toString 网络真实实现: - NetworkInterface: getByName/getNetworkInterfaces/getHardwareAddress 用/sys/class/net+fcntl.ioctl - InterfaceAddress: 新增类 - ConnectivityManager: getActiveNetwork/getAllNetworks 用LinuxNetwork - WifiManager: getScanResults用nmcli, setWifiEnabled用nmcli/rfkill - BluetoothAdapter: getAddress/getName 用bluetoothctl 媒体真实实现: - MediaPlayer: prepare检查文件, getCurrentPosition实时计算, getDuration/getVideoWidth用ffprobe - MediaRecorder: setVideoSize/setVideoFrameRate/setVideoEncodingBitRate配置存储 - MediaMetadataRetriever: extractMetadata用ffprobe, getFrameAtTime用ffmpeg, getEmbeddedPicture用ffmpeg 文本真实实现: - TextUtils: split用re.split, concat/indexOf/lastIndexOf/urlEncode/urlDecode - SpannableString: setSpan/getSpans/getSpanFlags/nextSpanTransition 真实实现 - SpannableStringBuilder: 修复append重载bug Provider增强: - Settings: putBoolean/getBoolean - MediaStore: getContentUri/VOLUME常量 - ContactsContract: 补全常量和CommonDataKinds Util增强: - Log: w/e/wtf支持Throwable参数, 修复wtf重复定义bug - TypedValue: 修复complexToDimension/complexToFloat/getFloat实现 修复: - __init__.py: 补全NetworkInterface/InetAddress/MediaMetadataRetriever/SpannableStringBuilder导出 统计: 221模块, 3666类, 31005方法, 183529行
1111 lines
34 KiB
Python
1111 lines
34 KiB
Python
"""
|
|
Android Util 组件 — Log, SparseArray, LruCache, DisplayMetrics 等
|
|
方法签名完全对齐 Android SDK
|
|
"""
|
|
|
|
import math
|
|
import re as _re
|
|
import struct
|
|
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 = None, tr: Exception = None) -> int:
|
|
if Log._min_log_level <= Log.WARN:
|
|
# 支持 w(tag, Throwable) 调用形式
|
|
if msg is None and tr is not None:
|
|
msg = Log.getStackTraceString(tr)
|
|
elif tr is not None:
|
|
msg = f"{msg}\n{Log.getStackTraceString(tr)}"
|
|
sys.stderr.write(f"W/{tag}: {msg}\n")
|
|
return 0
|
|
|
|
@staticmethod
|
|
def e(tag: str, msg: str = None, tr: Exception = None) -> int:
|
|
if Log._min_log_level <= Log.ERROR:
|
|
# 支持 e(tag, Throwable) 调用形式
|
|
if msg is None and tr is not None:
|
|
msg = Log.getStackTraceString(tr)
|
|
elif tr is not None:
|
|
msg = f"{msg}\n{Log.getStackTraceString(tr)}"
|
|
sys.stderr.write(f"E/{tag}: {msg}\n")
|
|
return 0
|
|
|
|
@staticmethod
|
|
def wtf(tag: str, msg=None, tr: Exception = None) -> int:
|
|
"""What a Terrible Failure。支持以下调用形式:
|
|
- wtf(tag, msg)
|
|
- wtf(tag, Throwable)
|
|
- wtf(tag, msg, Throwable)
|
|
"""
|
|
# 如果 msg 是异常对象,则当作 tr 处理
|
|
if isinstance(msg, BaseException):
|
|
tr = msg
|
|
msg = None
|
|
if msg is None and tr is not None:
|
|
msg = Log.getStackTraceString(tr)
|
|
elif tr is not None:
|
|
msg = f"{msg}\n{Log.getStackTraceString(tr)}"
|
|
if msg is None:
|
|
msg = ""
|
|
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
|
|
if tr is None:
|
|
return ""
|
|
if isinstance(tr, str):
|
|
return tr
|
|
return "".join(traceback.format_exception(type(tr), tr, tr.__traceback__))
|
|
|
|
@staticmethod
|
|
def setMinLogLevel(level: int):
|
|
Log._min_log_level = level
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# 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
|
|
|
|
COMPLEX_UNIT_SHIFT = 0
|
|
COMPLEX_UNIT_MASK = 0xF
|
|
|
|
COMPLEX_RADIX_SHIFT = 4
|
|
COMPLEX_RADIX_MASK = 0x3
|
|
|
|
COMPLEX_MANTISSA_SHIFT = 8
|
|
COMPLEX_MANTISSA_MASK = 0x00ffffff
|
|
|
|
# 复数值的基数乘数(与 AOSP TypedValue.RADIX_MULTS 一致)
|
|
_RADIX_MULTS = (
|
|
0.00390625, # 1/256, radix 0
|
|
3.0517578e-5, # 1/32768, radix 1
|
|
1.1920929e-7, # 1/8388608, radix 2
|
|
4.6566129e-10, # 1/2147483648, radix 3
|
|
)
|
|
|
|
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
|
|
|
|
DATA_NULL_UNDEFINED = 0
|
|
DATA_NULL_EMPTY = 1
|
|
|
|
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:
|
|
"""将复数维度值转换为像素值。
|
|
复数值格式:bits 0-3=unit, bits 4-5=radix, bits 8-31=mantissa(有符号24位)"""
|
|
unit = (data >> TypedValue.COMPLEX_UNIT_SHIFT) & TypedValue.COMPLEX_UNIT_MASK
|
|
radix = (data >> TypedValue.COMPLEX_RADIX_SHIFT) & TypedValue.COMPLEX_RADIX_MASK
|
|
mantissa = (data >> TypedValue.COMPLEX_MANTISSA_SHIFT) & TypedValue.COMPLEX_MANTISSA_MASK
|
|
# 24 位有符号数符号扩展
|
|
if mantissa & 0x800000:
|
|
mantissa = mantissa - 0x1000000
|
|
value = mantissa * TypedValue._RADIX_MULTS[radix]
|
|
return TypedValue.applyDimension(unit, value, metrics)
|
|
|
|
@staticmethod
|
|
def complexToFloat(data: int) -> float:
|
|
"""将复数值转换为浮点数(与 AOSP complexToFloat 一致)。"""
|
|
radix = (data >> TypedValue.COMPLEX_RADIX_SHIFT) & TypedValue.COMPLEX_RADIX_MASK
|
|
# mantissa 保持在原始位位置(左移 8 位),与 AOSP 一致
|
|
mantissa = data & (TypedValue.COMPLEX_MANTISSA_MASK << TypedValue.COMPLEX_MANTISSA_SHIFT)
|
|
# 处理符号(Java int 是有符号的,bit 31 为符号位)
|
|
if mantissa & 0x80000000:
|
|
mantissa = mantissa - 0x100000000
|
|
return float(mantissa) * TypedValue._RADIX_MULTS[radix]
|
|
|
|
@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:
|
|
"""将 data 字段按 IEEE 754 单精度浮点数解读(与 Float.intBitsToFloat 一致)。"""
|
|
bits = self.data & 0xFFFFFFFF
|
|
return struct.unpack('f', struct.pack('I', bits))[0]
|
|
|
|
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",
|
|
] |