核心类方法补全: - Activity: +20方法 (requestWindowFeature, setTitle/getTitle, onKeyDown/onKeyUp, enterPictureInPictureMode, getReferrer, isTaskRoot, navigateUpTo等) - Fragment: +15方法 (getChildFragmentManager, getParentFragment, requireContext/requireActivity, setMenuVisibility等) - Intent: +20方法 (getParcelableExtra, setComponent/getComponent, getDataString, getScheme, putExtras等) - View: +20方法 (setLabelFor, setRotationX/Y, setForeground, setBackgroundTintList等) - KeyEvent: +7方法 (getCharacters, isSystem, isPrintingKey, isModifierKey, startTracking等) - Paint: +12方法 (setStrokeCap/Join, getTextBounds, setBlendMode, setLetterSpacing等) - Notification.Builder: +10方法 (setProgress, setShowWhen, setOnlyAlertOnce, setSound等) 存根转真实实现: - TrafficStats: 读取 /proc/net/dev 返回真实网络流量 - RecyclerView.Adapter: 观察者模式触发通知 - BaseAdapter: 观察者模式触发通知 - ExifInterface: 使用PIL实现图片翻转/旋转 - Process: 使用os.kill实现进程信号 - FileUtils: 真实文件I/O - SharedPreferences: 监听器模式 - TelephonyManager.listen: 维护监听器列表 - ScrollView.smoothScrollTo: 更新滚动位置 Exception体系扩展: - 新增15个Exception/Error类 (ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchElementException, AssertionError, OutOfMemoryError, StackOverflowError, NoClassDefFoundError, UnsatisfiedLinkError, LinkageError, VerifyError, AbstractMethodError, IncompatibleClassChangeError, ExceptionInInitializerError) Math/System/Thread/StringBuilder/Integer增强: - Math: +20方法 (subtractExact, multiplyExact, toIntExact, floorDiv/floorMod, fma等) - System: getProperties/setProperties/mapLibraryName/getLogger - Thread: +10方法 (getUncaughtExceptionHandler, getAllStackTraces, getStackTrace, holdLock等) - StringBuilder: +7方法 (ensureCapacity, trimToSize, setLength, subSequence等) - Integer: +8静态方法 (compare, compareUnsigned, divideUnsigned, sum, max, min等) 修复: - app_fragment.py: 使用java.lang.IllegalStateException替代本地定义 - content_intent.py: Intent.setData/getScheme正确从Uri提取scheme - view_ext.py: KeyEvent继承view.KeyEvent获取所有KEYCODE常量 - __init__.py: 补全Fragment/KeyEvent/MotionEvent/TrafficStats/RecyclerView等导出 统计: 221模块, 3660类, 30826方法, 180217行
1796 lines
58 KiB
Python
1796 lines
58 KiB
Python
"""
|
|
Android Content 组件 — Context, ContentResolver, Uri, SharedPreferences 等
|
|
方法签名完全对齐 Android SDK
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
import threading
|
|
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
|
from urllib.parse import urlparse, urlunparse, urlencode, parse_qs, quote, unquote
|
|
|
|
from .._core import (
|
|
Stub, AndroidOnlyError, android_context,
|
|
resolve_android_path, to_android_path,
|
|
_sdcard_root, _app_data_dir, _package_name,
|
|
)
|
|
|
|
try:
|
|
from .._linux_impl import IS_LINUX, LinuxClipboard, LinuxNotify, LinuxLocation, LinuxProcess
|
|
_USE_LINUX = IS_LINUX
|
|
except ImportError:
|
|
_USE_LINUX = False
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# Uri
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class Uri(Stub):
|
|
"""Android Uri — 统一资源标识符"""
|
|
|
|
def __init__(self, scheme: str = "", authority: str = "", path: str = "",
|
|
query: str = "", fragment: str = "", host: str = "", port: int = -1,
|
|
ssp: str = "", user_info: str = ""):
|
|
super().__init__()
|
|
self._scheme = scheme
|
|
self._authority = authority
|
|
self._path = path
|
|
self._query = query
|
|
self._fragment = fragment
|
|
self._host = host
|
|
self._port = port
|
|
self._ssp = ssp
|
|
self._user_info = user_info
|
|
self._opaque = bool(ssp)
|
|
self._cached_string: Optional[str] = None
|
|
|
|
@staticmethod
|
|
def parse(uri_string: str) -> "Uri":
|
|
parsed = urlparse(uri_string)
|
|
host = parsed.hostname or ""
|
|
port = parsed.port or -1
|
|
return Uri(
|
|
scheme=parsed.scheme,
|
|
authority=parsed.netloc,
|
|
path=parsed.path,
|
|
query=parsed.query,
|
|
fragment=parsed.fragment,
|
|
host=host,
|
|
port=port,
|
|
user_info=parsed.username or "",
|
|
)
|
|
|
|
@staticmethod
|
|
def fromParts(scheme: str, ssp: str, fragment: str) -> "Uri":
|
|
uri = Uri(scheme=scheme, ssp=ssp, fragment=fragment)
|
|
uri._opaque = True
|
|
return uri
|
|
|
|
@staticmethod
|
|
def withAppendedPath(base_uri: "Uri", path_segment: str) -> "Uri":
|
|
new_path = base_uri._path.rstrip("/") + "/" + path_segment
|
|
return Uri(
|
|
scheme=base_uri._scheme,
|
|
authority=base_uri._authority,
|
|
path=new_path,
|
|
query=base_uri._query,
|
|
fragment=base_uri._fragment,
|
|
host=base_uri._host,
|
|
port=base_uri._port,
|
|
)
|
|
|
|
@staticmethod
|
|
def encode(s: str) -> str:
|
|
return quote(s, safe=":/?#[]@!$&'()*+,;=-._~")
|
|
|
|
@staticmethod
|
|
def decode(s: str) -> str:
|
|
return unquote(s)
|
|
|
|
def buildUpon(self) -> "Uri.Builder":
|
|
b = Uri.Builder()
|
|
b._scheme = self._scheme
|
|
b._authority = self._authority
|
|
b._path = self._path
|
|
b._query = self._query
|
|
b._fragment = self._fragment
|
|
return b
|
|
|
|
def getScheme(self) -> Optional[str]:
|
|
return self._scheme or None
|
|
|
|
def getAuthority(self) -> Optional[str]:
|
|
return self._authority or None
|
|
|
|
def getPath(self) -> Optional[str]:
|
|
return self._path or None
|
|
|
|
def getQuery(self) -> Optional[str]:
|
|
return self._query or None
|
|
|
|
def getFragment(self) -> Optional[str]:
|
|
return self._fragment or None
|
|
|
|
def getHost(self) -> Optional[str]:
|
|
return self._host or None
|
|
|
|
def getPort(self) -> int:
|
|
return self._port
|
|
|
|
def getPathSegments(self) -> List[str]:
|
|
return [s for s in self._path.split("/") if s] if self._path else []
|
|
|
|
def getLastPathSegment(self) -> Optional[str]:
|
|
segments = self.getPathSegments()
|
|
return segments[-1] if segments else None
|
|
|
|
def getUserInfo(self) -> Optional[str]:
|
|
return self._user_info or None
|
|
|
|
def isAbsolute(self) -> bool:
|
|
return bool(self._scheme)
|
|
|
|
def isOpaque(self) -> bool:
|
|
return self._opaque
|
|
|
|
def isHierarchical(self) -> bool:
|
|
return not self._opaque
|
|
|
|
def getSchemeSpecificPart(self) -> Optional[str]:
|
|
return self._ssp or None
|
|
|
|
def normalizeScheme(self) -> "Uri":
|
|
if self._scheme:
|
|
self._scheme = self._scheme.lower()
|
|
return self
|
|
|
|
def toString(self) -> str:
|
|
if self._cached_string is not None:
|
|
return self._cached_string
|
|
if self._opaque and self._ssp:
|
|
result = f"{self._scheme}:{self._ssp}"
|
|
if self._fragment:
|
|
result += f"#{self._fragment}"
|
|
else:
|
|
parts = []
|
|
if self._scheme:
|
|
parts.append(f"{self._scheme}:")
|
|
if self._authority:
|
|
parts.append(f"//{self._authority}")
|
|
if self._path:
|
|
parts.append(self._path)
|
|
if self._query:
|
|
parts.append(f"?{self._query}")
|
|
if self._fragment:
|
|
parts.append(f"#{self._fragment}")
|
|
result = "".join(parts)
|
|
self._cached_string = result
|
|
return result
|
|
|
|
def __repr__(self):
|
|
return self.toString()
|
|
|
|
def __str__(self):
|
|
return self.toString()
|
|
|
|
def __eq__(self, other):
|
|
if isinstance(other, Uri):
|
|
return self.toString() == other.toString()
|
|
return NotImplemented
|
|
|
|
def __hash__(self):
|
|
return hash(self.toString())
|
|
|
|
class Builder(Stub):
|
|
"""Uri builder pattern"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._scheme = ""
|
|
self._authority = ""
|
|
self._path = ""
|
|
self._query = ""
|
|
self._fragment = ""
|
|
self._host = ""
|
|
self._port = -1
|
|
self._segments: List[str] = []
|
|
|
|
def scheme(self, scheme: str) -> "Uri.Builder":
|
|
self._scheme = scheme
|
|
return self
|
|
|
|
def authority(self, authority: str) -> "Uri.Builder":
|
|
self._authority = authority
|
|
return self
|
|
|
|
def path(self, path: str) -> "Uri.Builder":
|
|
self._path = path
|
|
return self
|
|
|
|
def appendPath(self, new_segment: str) -> "Uri.Builder":
|
|
self._segments.append(new_segment)
|
|
return self
|
|
|
|
def query(self, query: str) -> "Uri.Builder":
|
|
self._query = query
|
|
return self
|
|
|
|
def appendQueryParameter(self, key: str, value: str) -> "Uri.Builder":
|
|
params = dict(parse_qs(self._query, keep_blank_values=True)) if self._query else {}
|
|
if key in params:
|
|
params[key].append(value)
|
|
else:
|
|
params[key] = [value]
|
|
self._query = urlencode(params, doseq=True)
|
|
return self
|
|
|
|
def fragment(self, fragment: str) -> "Uri.Builder":
|
|
self._fragment = fragment
|
|
return self
|
|
|
|
def opaquePart(self, opaque_part: str) -> "Uri.Builder":
|
|
return self
|
|
|
|
def build(self) -> "Uri":
|
|
path = self._path
|
|
if self._segments:
|
|
path = (path.rstrip("/") if path else "") + "/" + "/".join(self._segments)
|
|
return Uri(self._scheme, self._authority, path, self._query, self._fragment)
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# ContentValues
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class ContentValues(Stub):
|
|
"""ContentValues — 类似 dict 的内容值容器"""
|
|
|
|
def __init__(self, from_dict: Optional[Dict[str, Any]] = None):
|
|
super().__init__()
|
|
self._values: Dict[str, Any] = dict(from_dict) if from_dict else {}
|
|
|
|
def put(self, key: str, value: Any):
|
|
self._values[key] = value
|
|
|
|
def putNull(self, key: str):
|
|
self._values[key] = None
|
|
|
|
def get(self, key: str) -> Optional[Any]:
|
|
return self._values.get(key)
|
|
|
|
def getAsString(self, key: str) -> Optional[str]:
|
|
v = self._values.get(key)
|
|
return str(v) if v is not None else None
|
|
|
|
def getAsInteger(self, key: str) -> Optional[int]:
|
|
v = self._values.get(key)
|
|
if v is None:
|
|
return None
|
|
try:
|
|
return int(v)
|
|
except (ValueError, TypeError):
|
|
return None
|
|
|
|
def getAsLong(self, key: str) -> Optional[int]:
|
|
return self.getAsInteger(key)
|
|
|
|
def getAsFloat(self, key: str) -> Optional[float]:
|
|
v = self._values.get(key)
|
|
if v is None:
|
|
return None
|
|
try:
|
|
return float(v)
|
|
except (ValueError, TypeError):
|
|
return None
|
|
|
|
def getAsDouble(self, key: str) -> Optional[float]:
|
|
return self.getAsFloat(key)
|
|
|
|
def getAsBoolean(self, key: str) -> Optional[bool]:
|
|
v = self._values.get(key)
|
|
if v is None:
|
|
return None
|
|
if isinstance(v, bool):
|
|
return v
|
|
if isinstance(v, str):
|
|
return v.lower() in ("true", "1", "yes", "y")
|
|
return bool(v)
|
|
|
|
def getAsByteArray(self, key: str) -> Optional[bytes]:
|
|
v = self._values.get(key)
|
|
if isinstance(v, bytes):
|
|
return v
|
|
if isinstance(v, str):
|
|
return v.encode("utf-8")
|
|
return None
|
|
|
|
def containsKey(self, key: str) -> bool:
|
|
return key in self._values
|
|
|
|
def valueSet(self) -> Set[str]:
|
|
return set(self._values.keys())
|
|
|
|
def keySet(self) -> Set[str]:
|
|
return set(self._values.keys())
|
|
|
|
def size(self) -> int:
|
|
return len(self._values)
|
|
|
|
def isEmpty(self) -> bool:
|
|
return len(self._values) == 0
|
|
|
|
def clear(self):
|
|
self._values.clear()
|
|
|
|
def remove(self, key: str):
|
|
self._values.pop(key, None)
|
|
|
|
def putAll(self, other: "ContentValues"):
|
|
if other:
|
|
self._values.update(other._values)
|
|
|
|
def equals(self, other) -> bool:
|
|
if isinstance(other, ContentValues):
|
|
return self._values == other._values
|
|
return False
|
|
|
|
def hashCode(self) -> int:
|
|
return hash(frozenset(self._values.items())) & 0x7FFFFFFF
|
|
|
|
def toString(self) -> str:
|
|
return f"ContentValues({self._values})"
|
|
|
|
def __getitem__(self, key: str):
|
|
return self._values[key]
|
|
|
|
def __setitem__(self, key: str, value: Any):
|
|
self._values[key] = value
|
|
|
|
def __contains__(self, key: str) -> bool:
|
|
return key in self._values
|
|
|
|
def __len__(self) -> int:
|
|
return len(self._values)
|
|
|
|
def __repr__(self):
|
|
return self.toString()
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# ContentResolver
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class ContentResolver(Stub):
|
|
"""ContentResolver — 内容解析器"""
|
|
|
|
NOTIFY_SYNC_TO_NETWORK = 1
|
|
NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS = 2
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._observers: Dict[str, List[Any]] = {}
|
|
|
|
def query(self, uri: Uri, projection: Optional[List[str]], selection: Optional[str],
|
|
selection_args: Optional[List[str]], sort_order: Optional[str],
|
|
cancellation_signal: Any = None) -> Any:
|
|
"""Query content provider"""
|
|
sys.stderr.write(f"[ContentResolver] query: uri={uri}\n")
|
|
return None
|
|
|
|
def insert(self, uri: Uri, values: ContentValues) -> Optional[Uri]:
|
|
sys.stderr.write(f"[ContentResolver] insert: uri={uri}\n")
|
|
return uri
|
|
|
|
def update(self, uri: Uri, values: ContentValues, selection: Optional[str],
|
|
selection_args: Optional[List[str]]) -> int:
|
|
sys.stderr.write(f"[ContentResolver] update: uri={uri}\n")
|
|
return 0
|
|
|
|
def delete(self, uri: Uri, selection: Optional[str],
|
|
selection_args: Optional[List[str]]) -> int:
|
|
sys.stderr.write(f"[ContentResolver] delete: uri={uri}\n")
|
|
return 0
|
|
|
|
def applyBatch(self, authority: str, operations: List[Any]) -> List[Any]:
|
|
sys.stderr.write(f"[ContentResolver] applyBatch: authority={authority}\n")
|
|
return []
|
|
|
|
def bulkInsert(self, uri: Uri, values: List[ContentValues]) -> int:
|
|
sys.stderr.write(f"[ContentResolver] bulkInsert: uri={uri}\n")
|
|
return len(values) if values else 0
|
|
|
|
def call(self, uri: Uri, method: str, arg: Optional[str], extras: Optional[ContentValues]) -> Optional[ContentValues]:
|
|
return None
|
|
|
|
def notifyChange(self, uri: Uri, observer: Any, sync_to_network: bool = False):
|
|
uri_str = str(uri)
|
|
if uri_str in self._observers:
|
|
for obs in list(self._observers[uri_str]):
|
|
try:
|
|
obs.onChange(True)
|
|
except Exception:
|
|
pass
|
|
|
|
def registerContentObserver(self, uri: Uri, notify_for_descendants: bool, observer: Any):
|
|
uri_str = str(uri)
|
|
if uri_str not in self._observers:
|
|
self._observers[uri_str] = []
|
|
self._observers[uri_str].append(observer)
|
|
|
|
def unregisterContentObserver(self, observer: Any):
|
|
for uri_str in list(self._observers.keys()):
|
|
if observer in self._observers[uri_str]:
|
|
self._observers[uri_str].remove(observer)
|
|
if not self._observers[uri_str]:
|
|
del self._observers[uri_str]
|
|
|
|
def getType(self, uri: Uri) -> Optional[str]:
|
|
return None
|
|
|
|
def acquireContentProviderClient(self, uri: Uri) -> Optional["ContentProviderClient"]:
|
|
return ContentProviderClient(uri)
|
|
|
|
def acquireUnstableContentProviderClient(self, uri: Uri) -> Optional["ContentProviderClient"]:
|
|
return ContentProviderClient(uri)
|
|
|
|
def getStreamTypes(self, uri: Uri, mime_type_filter: str) -> Optional[List[str]]:
|
|
return None
|
|
|
|
def openInputStream(self, uri: Uri) -> Any:
|
|
return None
|
|
|
|
def openOutputStream(self, uri: Uri) -> Any:
|
|
return None
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# Context
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class Context(Stub):
|
|
"""Android Context — 应用上下文"""
|
|
|
|
# Service 常量
|
|
POWER_SERVICE = "power"
|
|
WINDOW_SERVICE = "window"
|
|
LAYOUT_INFLATER_SERVICE = "layout_inflater"
|
|
ACTIVITY_SERVICE = "activity"
|
|
ALARM_SERVICE = "alarm"
|
|
NOTIFICATION_SERVICE = "notification"
|
|
ACCESSIBILITY_SERVICE = "accessibility"
|
|
KEYGUARD_SERVICE = "keyguard"
|
|
LOCATION_SERVICE = "location"
|
|
SENSOR_SERVICE = "sensor"
|
|
STORAGE_SERVICE = "storage"
|
|
VIBRATOR_SERVICE = "vibrator"
|
|
CONNECTIVITY_SERVICE = "connectivity"
|
|
WIFI_SERVICE = "wifi"
|
|
AUDIO_SERVICE = "audio"
|
|
TELEPHONY_SERVICE = "telephony"
|
|
INPUT_METHOD_SERVICE = "input_method"
|
|
CAMERA_SERVICE = "camera"
|
|
BATTERY_SERVICE = "battery"
|
|
CLIPBOARD_SERVICE = "clipboard"
|
|
|
|
# Mode 常量
|
|
MODE_PRIVATE = 0x0000
|
|
MODE_WORLD_READABLE = 0x0001
|
|
MODE_WORLD_WRITEABLE = 0x0002
|
|
MODE_APPEND = 0x8000
|
|
MODE_MULTI_PROCESS = 0x0004
|
|
|
|
# Flag 常量
|
|
BIND_AUTO_CREATE = 0x0001
|
|
BIND_DEBUG_UNBIND = 0x0002
|
|
BIND_NOT_FOREGROUND = 0x0004
|
|
BIND_ABOVE_CLIENT = 0x0008
|
|
BIND_ALLOW_OOM_MANAGEMENT = 0x0010
|
|
BIND_WAIVE_PRIORITY = 0x0020
|
|
BIND_IMPORTANT = 0x0040
|
|
BIND_ADJUST_WITH_ACTIVITY = 0x0080
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._package_name = _package_name
|
|
self._services: Dict[str, Any] = {}
|
|
self._shared_prefs: Dict[str, "SharedPreferences"] = {}
|
|
self._registered_receivers: List[Dict] = []
|
|
self._content_resolver = ContentResolver()
|
|
|
|
def getPackageName(self) -> str:
|
|
return self._package_name
|
|
|
|
def getFilesDir(self):
|
|
return os.path.join(_app_data_dir, "files")
|
|
|
|
def getCacheDir(self):
|
|
return os.path.join(_app_data_dir, "cache")
|
|
|
|
def getCodeCacheDir(self):
|
|
return os.path.join(_app_data_dir, "code_cache")
|
|
|
|
def getNoBackupFilesDir(self):
|
|
return os.path.join(_app_data_dir, "no_backup")
|
|
|
|
def getExternalFilesDir(self, type_: Optional[str] = None) -> str:
|
|
base = os.path.join(_sdcard_root, "Android", "data", self._package_name, "files")
|
|
if type_:
|
|
base = os.path.join(base, type_)
|
|
os.makedirs(base, exist_ok=True)
|
|
return base
|
|
|
|
def getExternalCacheDir(self):
|
|
base = os.path.join(_sdcard_root, "Android", "data", self._package_name, "cache")
|
|
os.makedirs(base, exist_ok=True)
|
|
return base
|
|
|
|
def getObbDir(self):
|
|
base = os.path.join(_sdcard_root, "Android", "obb", self._package_name)
|
|
os.makedirs(base, exist_ok=True)
|
|
return base
|
|
|
|
def getDataDir(self):
|
|
return _app_data_dir
|
|
|
|
def getPackageCodePath(self) -> str:
|
|
return os.path.join(_app_data_dir, "base.apk")
|
|
|
|
def getPackageResourcePath(self) -> str:
|
|
return os.path.join(_app_data_dir, "base.apk")
|
|
|
|
def startActivity(self, intent: Any):
|
|
sys.stderr.write(f"[Context] startActivity: {intent}\n")
|
|
|
|
def startActivities(self, intents: List[Any], options: Optional[Any] = None):
|
|
for intent in intents:
|
|
self.startActivity(intent)
|
|
|
|
def sendBroadcast(self, intent: Any):
|
|
sys.stderr.write(f"[Context] sendBroadcast: {intent}\n")
|
|
for receiver in self._registered_receivers:
|
|
if self._matches_intent_filter(intent, receiver.get("filter")):
|
|
try:
|
|
receiver["receiver"].onReceive(self, intent)
|
|
except Exception as e:
|
|
sys.stderr.write(f"[Context] broadcast error: {e}\n")
|
|
|
|
def sendOrderedBroadcast(self, intent: Any, receiver_permission: Optional[str] = None):
|
|
self.sendBroadcast(intent)
|
|
|
|
def sendStickyBroadcast(self, intent: Any):
|
|
self.sendBroadcast(intent)
|
|
|
|
def registerReceiver(self, receiver: Optional[Any], filter: Any) -> Optional[Any]:
|
|
if receiver:
|
|
self._registered_receivers.append({"receiver": receiver, "filter": filter})
|
|
return receiver
|
|
|
|
def unregisterReceiver(self, receiver: Any):
|
|
self._registered_receivers = [
|
|
r for r in self._registered_receivers
|
|
if r["receiver"] is not receiver
|
|
]
|
|
|
|
def _matches_intent_filter(self, intent: Any, intent_filter: Any) -> bool:
|
|
if intent_filter is None:
|
|
return True
|
|
action = getattr(intent, "getAction", lambda: None)()
|
|
if intent_filter._action and action != intent_filter._action:
|
|
return False
|
|
return True
|
|
|
|
def getContentResolver(self) -> ContentResolver:
|
|
return self._content_resolver
|
|
|
|
def getSharedPreferences(self, name: str, mode: int = MODE_PRIVATE) -> "SharedPreferences":
|
|
if name not in self._shared_prefs:
|
|
self._shared_prefs[name] = SharedPreferences(name)
|
|
return self._shared_prefs[name]
|
|
|
|
def getPackageManager(self):
|
|
from .content_pm import PackageManager
|
|
return PackageManager()
|
|
|
|
def getResources(self):
|
|
return None
|
|
|
|
def getAssets(self):
|
|
return None
|
|
|
|
def getMainLooper(self):
|
|
from .os import Looper
|
|
return Looper.getMainLooper()
|
|
|
|
def getApplicationContext(self):
|
|
return self
|
|
|
|
def getSystemService(self, name: str) -> Any:
|
|
if name == Context.ACTIVITY_SERVICE:
|
|
from .app import ActivityManager
|
|
return ActivityManager()
|
|
if name == Context.ALARM_SERVICE:
|
|
from .app import AlarmManager
|
|
return AlarmManager()
|
|
if name == Context.NOTIFICATION_SERVICE:
|
|
from .app import NotificationManager
|
|
return NotificationManager()
|
|
if name == Context.VIBRATOR_SERVICE:
|
|
from .os import Vibrator
|
|
return Vibrator()
|
|
if name == Context.POWER_SERVICE:
|
|
from .os import PowerManager
|
|
return PowerManager()
|
|
if name == Context.LOCATION_SERVICE:
|
|
from .location import LocationManager
|
|
return LocationManager()
|
|
if name == Context.SENSOR_SERVICE:
|
|
from .hardware import SensorManager
|
|
return SensorManager()
|
|
if name == Context.CONNECTIVITY_SERVICE:
|
|
from .net import ConnectivityManager
|
|
return ConnectivityManager()
|
|
if name == Context.WIFI_SERVICE:
|
|
from .net_wifi import WifiManager
|
|
return WifiManager()
|
|
if name == Context.AUDIO_SERVICE:
|
|
from .media import AudioManager
|
|
return AudioManager()
|
|
if name == Context.TELEPHONY_SERVICE:
|
|
from .telephony import TelephonyManager
|
|
return TelephonyManager()
|
|
if name == Context.INPUT_METHOD_SERVICE:
|
|
from .view_inputmethod import InputMethodManager
|
|
return InputMethodManager()
|
|
if name == Context.CAMERA_SERVICE:
|
|
from .hardware_camera2 import CameraManager
|
|
return CameraManager()
|
|
if name == Context.BATTERY_SERVICE:
|
|
from .os import BatteryManager
|
|
return BatteryManager()
|
|
if name == Context.CLIPBOARD_SERVICE:
|
|
return ClipboardManager()
|
|
return self._services.get(name)
|
|
|
|
def startService(self, intent: Any) -> Optional[Any]:
|
|
sys.stderr.write(f"[Context] startService: {intent}\n")
|
|
return None
|
|
|
|
def stopService(self, intent: Any) -> bool:
|
|
return True
|
|
|
|
def bindService(self, intent: Any, conn: Any, flags: int) -> bool:
|
|
sys.stderr.write(f"[Context] bindService: {intent}\n")
|
|
return True
|
|
|
|
def unbindService(self, conn: Any):
|
|
pass
|
|
|
|
def getString(self, res_id: int, *args) -> str:
|
|
return str(res_id)
|
|
|
|
def getColor(self, color_id: int) -> int:
|
|
return 0
|
|
|
|
def getDrawable(self, drawable_id: int):
|
|
return None
|
|
|
|
def getWallpaper(self):
|
|
return None
|
|
|
|
def getWallpaperDesiredMinimumWidth(self) -> int:
|
|
return 1080
|
|
|
|
def getWallpaperDesiredMinimumHeight(self) -> int:
|
|
return 1920
|
|
|
|
def checkCallingPermission(self, permission: str) -> int:
|
|
from .content_pm import PackageManager
|
|
return PackageManager.PERMISSION_GRANTED
|
|
|
|
def checkSelfPermission(self, permission: str) -> int:
|
|
from .content_pm import PackageManager
|
|
return PackageManager.PERMISSION_GRANTED
|
|
|
|
def shouldShowRequestPermissionRationale(self, permission: str) -> bool:
|
|
return False
|
|
|
|
def enforcePermission(self, permission: str, pid: int, uid: int, message: Optional[str]):
|
|
pass
|
|
|
|
def createPackageContext(self, package_name: str, flags: int) -> "Context":
|
|
ctx = Context()
|
|
ctx._package_name = package_name
|
|
return ctx
|
|
|
|
def isDeviceProtectedStorage(self) -> bool:
|
|
return False
|
|
|
|
def isCredentialProtectedStorage(self) -> bool:
|
|
return False
|
|
|
|
def deleteFile(self, name: str) -> bool:
|
|
path = os.path.join(self.getFilesDir(), name)
|
|
try:
|
|
os.remove(path)
|
|
return True
|
|
except OSError:
|
|
return False
|
|
|
|
def fileList(self) -> List[str]:
|
|
try:
|
|
return os.listdir(self.getFilesDir())
|
|
except OSError:
|
|
return []
|
|
|
|
def getDatabasePath(self, name: str):
|
|
path = os.path.join(_app_data_dir, "databases", name)
|
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
return path
|
|
|
|
def databaseList(self) -> List[str]:
|
|
db_dir = os.path.join(_app_data_dir, "databases")
|
|
try:
|
|
return [f for f in os.listdir(db_dir) if f.endswith(".db")]
|
|
except OSError:
|
|
return []
|
|
|
|
def deleteDatabase(self, name: str) -> bool:
|
|
db_path = os.path.join(_app_data_dir, "databases", name)
|
|
try:
|
|
os.remove(db_path)
|
|
return True
|
|
except OSError:
|
|
return False
|
|
|
|
def getDir(self, name: str, mode: int) -> str:
|
|
path = os.path.join(_app_data_dir, name)
|
|
os.makedirs(path, exist_ok=True)
|
|
return path
|
|
|
|
def toString(self) -> str:
|
|
return f"Context{{{self._package_name}}}"
|
|
|
|
# ─── 文件流 / 权限 (扩展) ─────────────────────────────────
|
|
|
|
def openFileInput(self, name):
|
|
path = os.path.join(self.getFilesDir(), name)
|
|
return open(path, 'rb')
|
|
|
|
def openFileOutput(self, name, mode):
|
|
path = os.path.join(self.getFilesDir(), name)
|
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
return open(path, 'wb')
|
|
|
|
def getFileStreamPath(self, name):
|
|
from ..java.io import File
|
|
return File(self.getFilesDir(), name)
|
|
|
|
def grantUriPermission(self, toPackage, uri, modeFlags):
|
|
pass
|
|
|
|
def revokeUriPermission(self, uri, modeFlags):
|
|
pass
|
|
|
|
def checkUriPermission(self, uri, pid, uid, modeFlags):
|
|
return 0 # PERMISSION_GRANTED
|
|
|
|
def checkPermission(self, permission, pid, uid):
|
|
return 0 # PERMISSION_GRANTED
|
|
|
|
def enforceUriPermission(self, uri, pid, uid, modeFlags, message):
|
|
pass
|
|
|
|
# ─── 资源 / 主题 (扩展) ───────────────────────────────────
|
|
|
|
def getApplicationInfo(self):
|
|
from .content_pm import ApplicationInfo
|
|
return ApplicationInfo()
|
|
|
|
def getTheme(self):
|
|
return getattr(self, '_theme', 0)
|
|
|
|
def setTheme(self, resid):
|
|
self._theme = resid
|
|
|
|
def getText(self, resId):
|
|
return str(resId)
|
|
|
|
def getQuantityString(self, resId, quantity, *formatArgs):
|
|
return str(resId)
|
|
|
|
def getStringArray(self, resId):
|
|
return []
|
|
|
|
def getTextArray(self, resId):
|
|
return []
|
|
|
|
def getIntArray(self, resId):
|
|
return []
|
|
|
|
# ─── Context 派生 / 回调 (扩展) ───────────────────────────
|
|
|
|
def createConfigurationContext(self, overrideConfiguration):
|
|
return Context()
|
|
|
|
def createDisplayContext(self, display):
|
|
return Context()
|
|
|
|
def registerComponentCallbacks(self, callback):
|
|
pass
|
|
|
|
def unregisterComponentCallbacks(self, callback):
|
|
pass
|
|
|
|
def getExternalStoragePublicDirectory(self, type_):
|
|
from ..java.io import File
|
|
from .os import Environment
|
|
return Environment.getExternalStoragePublicDirectory(type_)
|
|
|
|
def getDisplayMetrics(self):
|
|
from .util import DisplayMetrics
|
|
return DisplayMetrics()
|
|
|
|
|
|
class ContextWrapper(Context):
|
|
"""ContextWrapper — Context 的包装器"""
|
|
|
|
def __init__(self, base: Context):
|
|
super().__init__()
|
|
self._base = base
|
|
|
|
def getBaseContext(self) -> Context:
|
|
return self._base
|
|
|
|
def getPackageName(self) -> str:
|
|
return self._base.getPackageName()
|
|
|
|
def getFilesDir(self):
|
|
return self._base.getFilesDir()
|
|
|
|
def getCacheDir(self):
|
|
return self._base.getCacheDir()
|
|
|
|
def startActivity(self, intent: Any):
|
|
self._base.startActivity(intent)
|
|
|
|
def sendBroadcast(self, intent: Any):
|
|
self._base.sendBroadcast(intent)
|
|
|
|
def getContentResolver(self) -> ContentResolver:
|
|
return self._base.getContentResolver()
|
|
|
|
def getSharedPreferences(self, name: str, mode: int) -> "SharedPreferences":
|
|
return self._base.getSharedPreferences(name, mode)
|
|
|
|
def getSystemService(self, name: str) -> Any:
|
|
return self._base.getSystemService(name)
|
|
|
|
def startService(self, intent: Any) -> Optional[Any]:
|
|
return self._base.startService(intent)
|
|
|
|
def bindService(self, intent: Any, conn: Any, flags: int) -> bool:
|
|
return self._base.bindService(intent, conn, flags)
|
|
|
|
def registerReceiver(self, receiver: Optional[Any], filter: Any) -> Optional[Any]:
|
|
return self._base.registerReceiver(receiver, filter)
|
|
|
|
def unregisterReceiver(self, receiver: Any):
|
|
self._base.unregisterReceiver(receiver)
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# IntentFilter
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class IntentFilter(Stub):
|
|
"""IntentFilter — 描述 Intent 匹配规则"""
|
|
|
|
MATCH_CATEGORY_EMPTY = 0x100000
|
|
MATCH_CATEGORY_HOST = 0x200000
|
|
MATCH_CATEGORY_PORT = 0x400000
|
|
MATCH_CATEGORY_PATH = 0x800000
|
|
MATCH_CATEGORY_SCHEME = 0x1000000
|
|
MATCH_CATEGORY_SCHEME_SPECIFIC_PART = 0x2000000
|
|
MATCH_CATEGORY_TYPE = 0x4000000
|
|
NO_MATCH_ACTION = -3
|
|
NO_MATCH_CATEGORY = -4
|
|
NO_MATCH_DATA = -5
|
|
NO_MATCH_TYPE = -6
|
|
|
|
def __init__(self, action: Optional[str] = None):
|
|
super().__init__()
|
|
self._action = action
|
|
self._actions: List[str] = []
|
|
self._categories: List[str] = []
|
|
self._data_schemes: List[str] = []
|
|
self._data_authorities: List[str] = []
|
|
self._data_paths: List[str] = []
|
|
self._data_types: List[str] = []
|
|
self._priority = 0
|
|
if action:
|
|
self.addAction(action)
|
|
|
|
def addAction(self, action: str):
|
|
if action not in self._actions:
|
|
self._actions.append(action)
|
|
|
|
def addCategory(self, category: str):
|
|
if category not in self._categories:
|
|
self._categories.append(category)
|
|
|
|
def addDataScheme(self, scheme: str):
|
|
if scheme not in self._data_schemes:
|
|
self._data_schemes.append(scheme)
|
|
|
|
def addDataAuthority(self, host: str, port: int = -1):
|
|
auth = f"{host}:{port}" if port >= 0 else host
|
|
if auth not in self._data_authorities:
|
|
self._data_authorities.append(auth)
|
|
|
|
def addDataPath(self, path: str, pattern_type: int = 0):
|
|
if path not in self._data_paths:
|
|
self._data_paths.append(path)
|
|
|
|
def addDataType(self, type_: str):
|
|
if type_ not in self._data_types:
|
|
self._data_types.append(type_)
|
|
|
|
def hasAction(self, action: str) -> bool:
|
|
return action in self._actions
|
|
|
|
def hasCategory(self, category: str) -> bool:
|
|
return category in self._categories
|
|
|
|
def hasDataScheme(self, scheme: str) -> bool:
|
|
return scheme in self._data_schemes
|
|
|
|
def countActions(self) -> int:
|
|
return len(self._actions)
|
|
|
|
def getAction(self, index: int) -> str:
|
|
return self._actions[index]
|
|
|
|
def setPriority(self, priority: int):
|
|
self._priority = priority
|
|
|
|
def getPriority(self) -> int:
|
|
return self._priority
|
|
|
|
def match(self, action: str, type_: Optional[str], scheme: Optional[str],
|
|
data: Optional[Any], categories: Optional[Set[str]], tag: str) -> int:
|
|
if action and action not in self._actions:
|
|
return self.NO_MATCH_ACTION
|
|
return self.MATCH_CATEGORY_EMPTY
|
|
|
|
def matchAction(self, action: str) -> bool:
|
|
return action in self._actions
|
|
|
|
def matchCategories(self, categories: Set[str]) -> Optional[str]:
|
|
return None
|
|
|
|
def matchData(self, type_: str, scheme: str, data: Any) -> int:
|
|
return self.MATCH_CATEGORY_EMPTY
|
|
|
|
def dump(self, du: Any, prefix: str):
|
|
pass
|
|
|
|
def describeContents(self) -> int:
|
|
return 0
|
|
|
|
def toString(self) -> str:
|
|
return f"IntentFilter(actions={self._actions})"
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# BroadcastReceiver
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class BroadcastReceiver(Stub):
|
|
"""BroadcastReceiver — 广播接收器"""
|
|
|
|
RESULT_OK = -1
|
|
RESULT_CANCELED = 0
|
|
RESULT_REDIRECT = 1
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._result_code = self.RESULT_OK
|
|
self._result_data = ""
|
|
self._result_extras: Optional[ContentValues] = None
|
|
self._ordered = False
|
|
self._initial_sticky = False
|
|
|
|
def onReceive(self, context: Context, intent: Any):
|
|
"""Override this method to handle broadcasts"""
|
|
pass
|
|
|
|
def setResultCode(self, code: int):
|
|
self._result_code = code
|
|
|
|
def getResultCode(self) -> int:
|
|
return self._result_code
|
|
|
|
def setResultData(self, data: str):
|
|
self._result_data = data
|
|
|
|
def getResultData(self) -> str:
|
|
return self._result_data
|
|
|
|
def setResult(self, code: int, data: str, extras: Optional[ContentValues]):
|
|
self._result_code = code
|
|
self._result_data = data
|
|
self._result_extras = extras
|
|
|
|
def setResultExtras(self, extras: ContentValues):
|
|
self._result_extras = extras
|
|
|
|
def getResultExtras(self, make_map: bool) -> Optional[ContentValues]:
|
|
return self._result_extras
|
|
|
|
def abortBroadcast(self):
|
|
self._result_code = self.RESULT_CANCELED
|
|
|
|
def isOrderedBroadcast(self) -> bool:
|
|
return self._ordered
|
|
|
|
def isInitialStickyBroadcast(self) -> bool:
|
|
return self._initial_sticky
|
|
|
|
def getDebugUnregister(self) -> bool:
|
|
return False
|
|
|
|
def toString(self) -> str:
|
|
return f"BroadcastReceiver@{hex(id(self))}"
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# SharedPreferences / SharedPreferencesEditor
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
_SHARED_PREFS_DIR = os.path.join(_app_data_dir, "shared_prefs")
|
|
os.makedirs(_SHARED_PREFS_DIR, exist_ok=True)
|
|
|
|
class SharedPreferences(Stub):
|
|
"""SharedPreferences — JSON 文件实现的持久化键值存储"""
|
|
|
|
def __init__(self, name: str):
|
|
super().__init__()
|
|
self._name = name
|
|
self._lock = threading.Lock()
|
|
self._file_path = os.path.join(_SHARED_PREFS_DIR, f"{name}.json")
|
|
self._data: Dict[str, Any] = {}
|
|
self._load()
|
|
|
|
def _load(self):
|
|
try:
|
|
if os.path.exists(self._file_path):
|
|
with open(self._file_path, "r", encoding="utf-8") as f:
|
|
self._data = json.load(f)
|
|
else:
|
|
self._data = {}
|
|
except (json.JSONDecodeError, OSError):
|
|
self._data = {}
|
|
|
|
def _save(self):
|
|
with self._lock:
|
|
os.makedirs(os.path.dirname(self._file_path), exist_ok=True)
|
|
with open(self._file_path, "w", encoding="utf-8") as f:
|
|
json.dump(self._data, f, indent=2, ensure_ascii=False)
|
|
|
|
def getString(self, key: str, def_value: Optional[str] = None) -> Optional[str]:
|
|
v = self._data.get(key)
|
|
return str(v) if v is not None else def_value
|
|
|
|
def getInt(self, key: str, def_value: int = 0) -> int:
|
|
v = self._data.get(key)
|
|
if isinstance(v, int) or isinstance(v, float):
|
|
return int(v)
|
|
return def_value
|
|
|
|
def getLong(self, key: str, def_value: int = 0) -> int:
|
|
return self.getInt(key, def_value)
|
|
|
|
def getFloat(self, key: str, def_value: float = 0.0) -> float:
|
|
v = self._data.get(key)
|
|
if isinstance(v, (int, float)):
|
|
return float(v)
|
|
return def_value
|
|
|
|
def getBoolean(self, key: str, def_value: bool = False) -> bool:
|
|
v = self._data.get(key)
|
|
if isinstance(v, bool):
|
|
return v
|
|
if isinstance(v, str):
|
|
return v.lower() in ("true", "1", "yes")
|
|
return def_value
|
|
|
|
def getAll(self) -> Dict[str, Any]:
|
|
return dict(self._data)
|
|
|
|
def contains(self, key: str) -> bool:
|
|
return key in self._data
|
|
|
|
def edit(self) -> "SharedPreferencesEditor":
|
|
return SharedPreferencesEditor(self)
|
|
|
|
def registerOnSharedPreferenceChangeListener(self, listener):
|
|
if not hasattr(self, '_listeners'):
|
|
self._listeners = []
|
|
self._listeners.append(listener)
|
|
|
|
def unregisterOnSharedPreferenceChangeListener(self, listener):
|
|
if hasattr(self, '_listeners'):
|
|
try:
|
|
self._listeners.remove(listener)
|
|
except ValueError:
|
|
pass
|
|
|
|
def getSharedPrefsFile(self):
|
|
return self._file_path
|
|
|
|
def toString(self) -> str:
|
|
return f"SharedPreferences({self._name})"
|
|
|
|
|
|
class SharedPreferencesEditor(Stub):
|
|
"""SharedPreferences Editor"""
|
|
|
|
def __init__(self, prefs: SharedPreferences):
|
|
super().__init__()
|
|
self._prefs = prefs
|
|
self._modified: Dict[str, Any] = {}
|
|
self._removed: Set[str] = set()
|
|
self._clear_all = False
|
|
|
|
def putString(self, key: str, value: str) -> "SharedPreferencesEditor":
|
|
self._modified[key] = value
|
|
self._removed.discard(key)
|
|
return self
|
|
|
|
def putInt(self, key: str, value: int) -> "SharedPreferencesEditor":
|
|
self._modified[key] = value
|
|
self._removed.discard(key)
|
|
return self
|
|
|
|
def putLong(self, key: str, value: int) -> "SharedPreferencesEditor":
|
|
self._modified[key] = value
|
|
self._removed.discard(key)
|
|
return self
|
|
|
|
def putFloat(self, key: str, value: float) -> "SharedPreferencesEditor":
|
|
self._modified[key] = value
|
|
self._removed.discard(key)
|
|
return self
|
|
|
|
def putBoolean(self, key: str, value: bool) -> "SharedPreferencesEditor":
|
|
self._modified[key] = value
|
|
self._removed.discard(key)
|
|
return self
|
|
|
|
def remove(self, key: str) -> "SharedPreferencesEditor":
|
|
self._removed.add(key)
|
|
self._modified.pop(key, None)
|
|
return self
|
|
|
|
def clear(self) -> "SharedPreferencesEditor":
|
|
self._clear_all = True
|
|
self._modified.clear()
|
|
self._removed.clear()
|
|
return self
|
|
|
|
def apply(self):
|
|
"""异步保存"""
|
|
self.commit()
|
|
|
|
def commit(self) -> bool:
|
|
try:
|
|
if self._clear_all:
|
|
self._prefs._data.clear()
|
|
for key in self._removed:
|
|
self._prefs._data.pop(key, None)
|
|
self._prefs._data.update(self._modified)
|
|
self._prefs._save()
|
|
# 触发监听器
|
|
sp = getattr(self, '_prefs', None)
|
|
if sp:
|
|
for key in self._modified:
|
|
for listener in getattr(sp, '_listeners', []):
|
|
listener.onSharedPreferenceChanged(sp, key)
|
|
self._modified.clear()
|
|
self._removed.clear()
|
|
self._clear_all = False
|
|
return True
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# ComponentName
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class ComponentName(Stub):
|
|
"""ComponentName — 标识包中的特定组件"""
|
|
|
|
def __init__(self, pkg: str, cls: str):
|
|
super().__init__()
|
|
self._package = pkg
|
|
self._classname = cls
|
|
|
|
def getPackageName(self) -> str:
|
|
return self._package
|
|
|
|
def getClassName(self) -> str:
|
|
return self._classname
|
|
|
|
def getShortClassName(self) -> str:
|
|
return self._classname.split(".")[-1] if "." in self._classname else self._classname
|
|
|
|
def flattenToString(self) -> str:
|
|
return f"{self._package}/{self._classname}"
|
|
|
|
def flattenToShortString(self) -> str:
|
|
return f"{self._package}/{self.getShortClassName()}"
|
|
|
|
@staticmethod
|
|
def unflattenFromString(str_: str) -> Optional["ComponentName"]:
|
|
if "/" in str_:
|
|
parts = str_.split("/", 1)
|
|
return ComponentName(parts[0], parts[1])
|
|
return None
|
|
|
|
def toString(self) -> str:
|
|
return f"ComponentInfo{{{self._package}/{self._classname}}}"
|
|
|
|
def __repr__(self):
|
|
return self.toString()
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# ClipData / ClipDescription
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class ClipDescription(Stub):
|
|
"""ClipDescription — 描述剪贴板数据"""
|
|
|
|
MIMETYPE_TEXT_PLAIN = "text/plain"
|
|
MIMETYPE_TEXT_HTML = "text/html"
|
|
MIMETYPE_TEXT_URILIST = "text/uri-list"
|
|
MIMETYPE_TEXT_INTENT = "text/vnd.android.intent"
|
|
|
|
def __init__(self, label: str, mime_types: List[str]):
|
|
super().__init__()
|
|
self._label = label
|
|
self._mime_types = list(mime_types)
|
|
self._timestamp = 0
|
|
|
|
def getLabel(self) -> str:
|
|
return self._label
|
|
|
|
def getMimeType(self, index: int) -> str:
|
|
if 0 <= index < len(self._mime_types):
|
|
return self._mime_types[index]
|
|
return ""
|
|
|
|
def getMimeTypeCount(self) -> int:
|
|
return len(self._mime_types)
|
|
|
|
def filterMimeTypes(self, mime_type: str) -> bool:
|
|
return mime_type in self._mime_types
|
|
|
|
def compareMimeTypes(self, mime_type1: str, mime_type2: str) -> bool:
|
|
return mime_type1 == mime_type2
|
|
|
|
def getTimestamp(self) -> int:
|
|
return self._timestamp
|
|
|
|
def toString(self) -> str:
|
|
return f"ClipDescription({self._label}, {self._mime_types})"
|
|
|
|
|
|
class ClipData(Stub):
|
|
"""ClipData — 剪贴板数据"""
|
|
|
|
def __init__(self, description: ClipDescription, item: "ClipData.Item"):
|
|
super().__init__()
|
|
self._description = description
|
|
self._items: List["ClipData.Item"] = [item] if item else []
|
|
|
|
class Item(Stub):
|
|
"""单个剪贴板条目"""
|
|
def __init__(self, text: Optional[str] = None, intent: Optional[Any] = None, uri: Optional[Uri] = None):
|
|
super().__init__()
|
|
self._text = text
|
|
self._intent = intent
|
|
self._uri = uri
|
|
self._html_text = None
|
|
|
|
def getText(self) -> Optional[str]:
|
|
return self._text
|
|
|
|
def getIntent(self):
|
|
return self._intent
|
|
|
|
def getUri(self) -> Optional[Uri]:
|
|
return self._uri
|
|
|
|
def getHtmlText(self) -> Optional[str]:
|
|
return self._html_text
|
|
|
|
def setHtmlText(self, html_text: str):
|
|
self._html_text = html_text
|
|
|
|
def coerceToText(self, context: Context) -> str:
|
|
if self._text:
|
|
return self._text
|
|
if self._uri:
|
|
return str(self._uri)
|
|
if self._intent:
|
|
return str(self._intent)
|
|
return ""
|
|
|
|
def coerceToHtmlText(self, context: Context) -> Optional[str]:
|
|
return self._html_text
|
|
|
|
def toString(self) -> str:
|
|
return f"ClipData.Item({self._text})"
|
|
|
|
@staticmethod
|
|
def newPlainText(label: str, text: str) -> "ClipData":
|
|
desc = ClipDescription(label, [ClipDescription.MIMETYPE_TEXT_PLAIN])
|
|
return ClipData(desc, ClipData.Item(text=text))
|
|
|
|
@staticmethod
|
|
def newUri(cr: ContentResolver, label: str, uri: Uri) -> "ClipData":
|
|
desc = ClipDescription(label, [ClipDescription.MIMETYPE_TEXT_URILIST])
|
|
return ClipData(desc, ClipData.Item(uri=uri))
|
|
|
|
@staticmethod
|
|
def newIntent(label: str, intent: Any) -> "ClipData":
|
|
desc = ClipDescription(label, [ClipDescription.MIMETYPE_TEXT_INTENT])
|
|
return ClipData(desc, ClipData.Item(intent=intent))
|
|
|
|
def getDescription(self) -> ClipDescription:
|
|
return self._description
|
|
|
|
def addItem(self, item: "ClipData.Item"):
|
|
self._items.append(item)
|
|
|
|
def getItemAt(self, index: int) -> Optional["ClipData.Item"]:
|
|
if 0 <= index < len(self._items):
|
|
return self._items[index]
|
|
return None
|
|
|
|
def getItemCount(self) -> int:
|
|
return len(self._items)
|
|
|
|
def toString(self) -> str:
|
|
return f"ClipData({self._description})"
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# ContentProvider / ContentProviderClient
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class ContentProvider(Stub):
|
|
"""ContentProvider — 内容提供者基类"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._context: Optional[Context] = None
|
|
self._calling_package = ""
|
|
|
|
def onCreate(self) -> bool:
|
|
return True
|
|
|
|
def query(self, uri: Uri, projection: Optional[List[str]], selection: Optional[str],
|
|
selection_args: Optional[List[str]], sort_order: Optional[str]) -> Optional[Any]:
|
|
return None
|
|
|
|
def insert(self, uri: Uri, values: ContentValues) -> Optional[Uri]:
|
|
return None
|
|
|
|
def update(self, uri: Uri, values: ContentValues, selection: Optional[str],
|
|
selection_args: Optional[List[str]]) -> int:
|
|
return 0
|
|
|
|
def delete(self, uri: Uri, selection: Optional[str], selection_args: Optional[List[str]]) -> int:
|
|
return 0
|
|
|
|
def getType(self, uri: Uri) -> Optional[str]:
|
|
return None
|
|
|
|
def getStreamTypes(self, uri: Uri, mime_type_filter: str) -> Optional[List[str]]:
|
|
return None
|
|
|
|
def openFile(self, uri: Uri, mode: str) -> Any:
|
|
return None
|
|
|
|
def openAssetFile(self, uri: Uri, mode: str) -> Any:
|
|
return None
|
|
|
|
def attachInfo(self, context: Context, info: Any):
|
|
self._context = context
|
|
|
|
def getContext(self) -> Optional[Context]:
|
|
return self._context
|
|
|
|
def getCallingPackage(self) -> str:
|
|
return self._calling_package
|
|
|
|
def applyBatch(self, operations: List[Any]) -> List[Any]:
|
|
return []
|
|
|
|
def bulkInsert(self, uri: Uri, values: List[ContentValues]) -> int:
|
|
return len(values) if values else 0
|
|
|
|
def isTemporary(self) -> bool:
|
|
return False
|
|
|
|
def shutdown(self):
|
|
pass
|
|
|
|
def toString(self) -> str:
|
|
return f"ContentProvider@{hex(id(self))}"
|
|
|
|
|
|
class ContentProviderClient(Stub):
|
|
"""ContentProviderClient — 内容提供者客户端"""
|
|
|
|
def __init__(self, uri: Optional[Uri] = None):
|
|
super().__init__()
|
|
self._uri = uri
|
|
|
|
def query(self, uri: Uri, projection: Optional[List[str]], selection: Optional[str],
|
|
selection_args: Optional[List[str]], sort_order: Optional[str]) -> Optional[Any]:
|
|
return None
|
|
|
|
def insert(self, uri: Uri, values: ContentValues) -> Optional[Uri]:
|
|
return uri
|
|
|
|
def update(self, uri: Uri, values: ContentValues, selection: Optional[str],
|
|
selection_args: Optional[List[str]]) -> int:
|
|
return 0
|
|
|
|
def delete(self, uri: Uri, selection: Optional[str], selection_args: Optional[List[str]]) -> int:
|
|
return 0
|
|
|
|
def applyBatch(self, operations: List[Any]) -> List[Any]:
|
|
return []
|
|
|
|
def bulkInsert(self, uri: Uri, values: List[ContentValues]) -> int:
|
|
return len(values) if values else 0
|
|
|
|
def getType(self, uri: Uri) -> Optional[str]:
|
|
return None
|
|
|
|
def getStreamTypes(self, uri: Uri, mime_type_filter: str) -> Optional[List[str]]:
|
|
return None
|
|
|
|
def call(self, method: str, arg: Optional[str], extras: Optional[ContentValues]) -> Optional[ContentValues]:
|
|
return None
|
|
|
|
def release(self):
|
|
pass
|
|
|
|
def close(self):
|
|
pass
|
|
|
|
def openFile(self, uri: Uri, mode: str) -> Any:
|
|
return None
|
|
|
|
def openAssetFile(self, uri: Uri, mode: str) -> Any:
|
|
return None
|
|
|
|
def openTypedAssetFile(self, uri: Uri, mime_type: str, opts: Optional[ContentValues]) -> Any:
|
|
return None
|
|
|
|
def getLocalContentProvider(self) -> Optional[ContentProvider]:
|
|
return None
|
|
|
|
|
|
class ContentProviderOperation(Stub):
|
|
"""ContentProviderOperation — 批量操作"""
|
|
|
|
TYPE_INSERT = 1
|
|
TYPE_UPDATE = 2
|
|
TYPE_DELETE = 3
|
|
TYPE_ASSERT = 4
|
|
|
|
class Builder(Stub):
|
|
def __init__(self, type_: int, uri: Uri):
|
|
super().__init__()
|
|
self._type = type_
|
|
self._uri = uri
|
|
self._values: Optional[ContentValues] = None
|
|
self._selection: Optional[str] = None
|
|
self._selection_args: Optional[List[str]] = None
|
|
|
|
def withValue(self, key: str, value: Any) -> "ContentProviderOperation.Builder":
|
|
if self._values is None:
|
|
self._values = ContentValues()
|
|
self._values.put(key, value)
|
|
return self
|
|
|
|
def withValues(self, values: ContentValues) -> "ContentProviderOperation.Builder":
|
|
self._values = values
|
|
return self
|
|
|
|
def withSelection(self, selection: str, selection_args: List[str]) -> "ContentProviderOperation.Builder":
|
|
self._selection = selection
|
|
self._selection_args = selection_args
|
|
return self
|
|
|
|
def build(self) -> "ContentProviderOperation":
|
|
op = ContentProviderOperation()
|
|
op._type = self._type
|
|
op._uri = self._uri
|
|
op._values = self._values
|
|
op._selection = self._selection
|
|
op._selection_args = self._selection_args
|
|
return op
|
|
|
|
@staticmethod
|
|
def newInsert(uri: Uri) -> "ContentProviderOperation.Builder":
|
|
return ContentProviderOperation.Builder(ContentProviderOperation.TYPE_INSERT, uri)
|
|
|
|
@staticmethod
|
|
def newUpdate(uri: Uri) -> "ContentProviderOperation.Builder":
|
|
return ContentProviderOperation.Builder(ContentProviderOperation.TYPE_UPDATE, uri)
|
|
|
|
@staticmethod
|
|
def newDelete(uri: Uri) -> "ContentProviderOperation.Builder":
|
|
return ContentProviderOperation.Builder(ContentProviderOperation.TYPE_DELETE, uri)
|
|
|
|
@staticmethod
|
|
def newAssert(uri: Uri) -> "ContentProviderOperation.Builder":
|
|
return ContentProviderOperation.Builder(ContentProviderOperation.TYPE_ASSERT, uri)
|
|
|
|
|
|
class ContentProviderResult(Stub):
|
|
"""ContentProviderResult — 操作结果"""
|
|
|
|
def __init__(self, uri: Optional[Uri] = None, count: Optional[int] = None):
|
|
super().__init__()
|
|
self.uri = uri
|
|
self.count = count
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# Loader / CursorLoader / DialogInterface
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class Loader(Stub):
|
|
"""Loader — 异步数据加载器基类"""
|
|
|
|
def __init__(self, context: Context):
|
|
super().__init__()
|
|
self._context = context
|
|
self._started = False
|
|
self._abandoned = False
|
|
self._listeners: List = []
|
|
|
|
def deliverResult(self, data: Any):
|
|
for listener in self._listeners:
|
|
try:
|
|
listener.onLoadFinished(self, data)
|
|
except Exception:
|
|
pass
|
|
|
|
def startLoading(self):
|
|
self._started = True
|
|
|
|
def forceLoad(self):
|
|
self.deliverResult(None)
|
|
|
|
def stopLoading(self):
|
|
self._started = False
|
|
|
|
def abandon(self):
|
|
self._abandoned = True
|
|
|
|
def isStarted(self) -> bool:
|
|
return self._started
|
|
|
|
def isAbandoned(self) -> bool:
|
|
return self._abandoned
|
|
|
|
def reset(self):
|
|
self._started = False
|
|
self._abandoned = False
|
|
|
|
def registerListener(self, listener):
|
|
if listener not in self._listeners:
|
|
self._listeners.append(listener)
|
|
|
|
def unregisterListener(self, listener):
|
|
if listener in self._listeners:
|
|
self._listeners.remove(listener)
|
|
|
|
def dataToString(self, data: Any) -> str:
|
|
return str(data)
|
|
|
|
def toString(self) -> str:
|
|
return f"Loader@{hex(id(self))}"
|
|
|
|
|
|
class CursorLoader(Loader):
|
|
"""CursorLoader — 游标加载器"""
|
|
|
|
def __init__(self, context: Context, uri: Uri, projection: Optional[List[str]],
|
|
selection: Optional[str], selection_args: Optional[List[str]], sort_order: Optional[str]):
|
|
super().__init__(context)
|
|
self._uri = uri
|
|
self._projection = projection
|
|
self._selection = selection
|
|
self._selection_args = selection_args
|
|
self._sort_order = sort_order
|
|
|
|
def getUri(self) -> Uri:
|
|
return self._uri
|
|
|
|
def setUri(self, uri: Uri):
|
|
self._uri = uri
|
|
|
|
def getProjection(self) -> Optional[List[str]]:
|
|
return self._projection
|
|
|
|
def setProjection(self, projection: Optional[List[str]]):
|
|
self._projection = projection
|
|
|
|
def getSelection(self) -> Optional[str]:
|
|
return self._selection
|
|
|
|
def setSelection(self, selection: Optional[str]):
|
|
self._selection = selection
|
|
|
|
def getSelectionArgs(self) -> Optional[List[str]]:
|
|
return self._selection_args
|
|
|
|
def setSelectionArgs(self, args: Optional[List[str]]):
|
|
self._selection_args = args
|
|
|
|
def getSortOrder(self) -> Optional[str]:
|
|
return self._sort_order
|
|
|
|
def setSortOrder(self, sort_order: Optional[str]):
|
|
self._sort_order = sort_order
|
|
|
|
def loadInBackground(self):
|
|
return None
|
|
|
|
|
|
class DialogInterface(Stub):
|
|
"""DialogInterface — 对话框接口常量"""
|
|
|
|
BUTTON_POSITIVE = -1
|
|
BUTTON_NEGATIVE = -2
|
|
BUTTON_NEUTRAL = -3
|
|
|
|
def cancel(self):
|
|
pass
|
|
|
|
def dismiss(self):
|
|
pass
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# SyncAdapter / SyncResult
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class SyncAdapter(Stub):
|
|
"""SyncAdapter — 同步适配器"""
|
|
|
|
def __init__(self, context: Context, auto_initialize: bool, allow_parallel_syncs: bool):
|
|
super().__init__()
|
|
self._context = context
|
|
|
|
def onPerformSync(self, account: Any, extras: ContentValues, authority: str,
|
|
provider: ContentProviderClient, sync_result: "SyncResult"):
|
|
pass
|
|
|
|
def onSecurityException(self, account: Any, extras: ContentValues,
|
|
authority: str, sync_result: "SyncResult"):
|
|
pass
|
|
|
|
|
|
class SyncResult(Stub):
|
|
"""SyncResult — 同步结果"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.stats = self._Stats()
|
|
|
|
class _Stats:
|
|
def __init__(self):
|
|
self.numEntries = 0
|
|
self.numUpdates = 0
|
|
self.numDeletes = 0
|
|
self.numInserts = 0
|
|
self.numSkippedEntries = 0
|
|
|
|
def clear(self):
|
|
self.stats = self._Stats()
|
|
|
|
def hasError(self) -> bool:
|
|
return False
|
|
|
|
def hasSoftError(self) -> bool:
|
|
return False
|
|
|
|
def hasHardError(self) -> bool:
|
|
return False
|
|
|
|
def madeSomeProgress(self) -> bool:
|
|
return self.stats.numEntries > 0
|
|
|
|
def toString(self) -> str:
|
|
return f"SyncResult({self.stats.numEntries} entries)"
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# ClipboardManager
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class ClipboardManager(Stub):
|
|
"""ClipboardManager — 剪贴板管理器"""
|
|
|
|
def getText(self):
|
|
if _USE_LINUX:
|
|
return LinuxClipboard.read()
|
|
return ""
|
|
|
|
def setText(self, text):
|
|
if _USE_LINUX:
|
|
return LinuxClipboard.write(text)
|
|
return False
|
|
|
|
def hasText(self):
|
|
if _USE_LINUX:
|
|
return len(LinuxClipboard.read()) > 0
|
|
return False
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# 替换 _core.py 中的 _GlobalContext
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
_real_context = Context()
|
|
# 将 _core 中的 android_context 替换为真实 Context
|
|
import importlib
|
|
try:
|
|
_core_mod = importlib.import_module(".._core", __package__)
|
|
_core_mod.android_context.__class__ = Context
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# __all__
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
__all__ = [
|
|
"Uri",
|
|
"ContentValues",
|
|
"ContentResolver",
|
|
"Context",
|
|
"ContextWrapper",
|
|
"IntentFilter",
|
|
"BroadcastReceiver",
|
|
"SharedPreferences",
|
|
"SharedPreferencesEditor",
|
|
"ComponentName",
|
|
"ClipData",
|
|
"ClipDescription",
|
|
"ContentProvider",
|
|
"ContentProviderClient",
|
|
"ContentProviderOperation",
|
|
"ContentProviderResult",
|
|
"CursorLoader",
|
|
"Loader",
|
|
"DialogInterface",
|
|
"SyncAdapter",
|
|
"SyncResult",
|
|
"ClipboardManager",
|
|
] |