Files
Android-pyApi/android_pyapi/android/content.py
T
shuiyu1123 690022cea3 feat: Linux 真实实现层 — 从存根库变为可用生产力工具
新增 _linux_impl.py (800+行):
- LinuxNetwork: 真实IP/接口/DNS/网关/MAC (ip命令+/proc/net)
- LinuxBluetooth: 真实蓝牙状态/扫描/配对/连接 (bluetoothctl)
- LinuxSensors: 真实温度/CPU频率/风扇 (/sys/class/hwmon)
- LinuxAudio: 真实音量控制/静音 (amixer)
- LinuxClipboard: 真实剪贴板读写 (xclip+tkinter回退)
- LinuxPower: 真实CPU/内存/电池 (psutil+/sys/class/power_supply)
- LinuxLocation: 真实IP地理定位 (ip-api.com)
- LinuxNotify: 真实桌面通知 (notify-send)
- LinuxDisplay: 真实屏幕尺寸/截屏/鼠标位置 (xdotool+X11)
- LinuxUIAutomation: 真实点击/输入/按键/滚动 (xdotool)
- LinuxCamera: 真实摄像头拍照 (opencv/V4L2)
- LinuxProcess: 真实进程列表/系统信息/应用管理 (psutil+/proc)
- LinuxShell: 真实命令执行 (subprocess)

改造10+核心类使用Linux真实实现:
- BluetoothAdapter/SensorManager/AudioManager
- ConnectivityManager/WifiManager/TelephonyManager
- LocationManager/PowerManager/BatteryManager/Build
- ActivityManager/Activity/Toast/Display/ClipboardManager
- NotificationManager

15项真实行为测试全部通过:
- 真实IP: 10.60.218.244
- 真实位置: 北京 39.9042, 116.407
- 真实主机名: work-41-vci-g427c
- 真实内核: 5.15.120.byteatom-ck.14
- 真实内存: 1.8GB/3.8GB
- 真实进程列表
2026-06-24 15:40:49 +00:00

1784 lines
57 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):
pass
def unregisterOnSharedPreferenceChangeListener(self, listener):
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()
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",
]