feat: Android-pyApi v1.0 - 全面翻译 Android SDK API 为 Python
52 个模块覆盖 Android + Java + org.json 标准库
This commit is contained in:
commit
bfe27fdc08
104 files changed
+58750
No files matched your search
@@ -0,0 +1,186 @@
|
||||
"""
|
||||
Android-pyApi 核心基础设施
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import platform
|
||||
import tempfile
|
||||
import json
|
||||
import hashlib
|
||||
import random
|
||||
import time
|
||||
from typing import Any, Dict, List, Optional, Callable
|
||||
|
||||
|
||||
class AndroidOnlyError(Exception):
|
||||
"""当调用仅 Android 可用的 API 时抛出"""
|
||||
pass
|
||||
|
||||
|
||||
class Stub:
|
||||
"""
|
||||
通用 Android API 存根基类。
|
||||
子类可以覆盖具体方法实现真实逻辑;
|
||||
未覆盖的方法调用会返回合理默认值。
|
||||
"""
|
||||
|
||||
_android_only = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def __getattr__(self, name: str):
|
||||
if name.startswith("_"):
|
||||
raise AttributeError(name)
|
||||
|
||||
def _stub_method(*args, **kwargs):
|
||||
if self._android_only:
|
||||
raise AndroidOnlyError(
|
||||
f"{self.__class__.__name__}.{name}() 仅在 Android 设备上可用"
|
||||
)
|
||||
return None
|
||||
|
||||
return _stub_method
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"{self.__class__.__name__}@{hex(id(self))}"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(self) & 0x7FFFFFFF
|
||||
|
||||
def getClass(self) -> type:
|
||||
return self.__class__
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# 全局路径和环境
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
_package_name = "com.ai.assistance.operit"
|
||||
_sdcard_root = os.path.join(os.path.expanduser("~"), "Android-compat", "sdcard")
|
||||
_app_data_dir = os.path.join(os.path.expanduser("~"), "Android-compat", "app-data")
|
||||
|
||||
|
||||
def _ensure_dirs():
|
||||
dirs = [
|
||||
_sdcard_root,
|
||||
os.path.join(_sdcard_root, "Download"),
|
||||
os.path.join(_sdcard_root, "DCIM"),
|
||||
os.path.join(_sdcard_root, "DCIM", "Screenshots"),
|
||||
os.path.join(_sdcard_root, "Documents"),
|
||||
os.path.join(_sdcard_root, "Music"),
|
||||
os.path.join(_sdcard_root, "Movies"),
|
||||
os.path.join(_sdcard_root, "Pictures"),
|
||||
os.path.join(_sdcard_root, "Android", "data"),
|
||||
os.path.join(_sdcard_root, "Android", "data", _package_name, "files"),
|
||||
os.path.join(_sdcard_root, "Android", "data", _package_name, "cache"),
|
||||
_app_data_dir,
|
||||
os.path.join(_app_data_dir, "files"),
|
||||
os.path.join(_app_data_dir, "cache"),
|
||||
os.path.join(_app_data_dir, "shared_prefs"),
|
||||
]
|
||||
for d in dirs:
|
||||
os.makedirs(d, exist_ok=True)
|
||||
|
||||
|
||||
_ensure_dirs()
|
||||
|
||||
|
||||
def resolve_android_path(path: str) -> str:
|
||||
"""将 Android 路径解析为桌面路径"""
|
||||
if path.startswith("/sdcard"):
|
||||
return os.path.join(_sdcard_root, path[7:].lstrip("/"))
|
||||
if path.startswith("/data/data/"):
|
||||
return os.path.join(_app_data_dir, path[11:].lstrip("/"))
|
||||
if path.startswith("content://"):
|
||||
# 简单解析 content URI
|
||||
from urllib.parse import urlparse
|
||||
parsed = urlparse(path)
|
||||
return parsed.path or path
|
||||
if path.startswith("file://"):
|
||||
from urllib.parse import unquote
|
||||
return unquote(path[7:])
|
||||
return path
|
||||
|
||||
|
||||
def to_android_path(desktop_path: str) -> str:
|
||||
"""将桌面路径映射回 Android 路径"""
|
||||
if desktop_path.startswith(_sdcard_root):
|
||||
rel = os.path.relpath(desktop_path, _sdcard_root)
|
||||
return f"/sdcard/{rel}"
|
||||
if desktop_path.startswith(_app_data_dir):
|
||||
rel = os.path.relpath(desktop_path, _app_data_dir)
|
||||
return f"/data/data/{_package_name}/{rel}"
|
||||
return desktop_path
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# 全局 Context 单例
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class _GlobalContext:
|
||||
"""全局应用上下文占位符,在 android.content 模块中被替换为真实 Context"""
|
||||
|
||||
@property
|
||||
def packageName(self):
|
||||
return _package_name
|
||||
|
||||
def getPackageName(self):
|
||||
return _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 getExternalFilesDir(self, type_=None):
|
||||
base = os.path.join(_sdcard_root, "Android", "data", _package_name, "files")
|
||||
if type_:
|
||||
base = os.path.join(base, type_)
|
||||
os.makedirs(base, exist_ok=True)
|
||||
return base
|
||||
|
||||
def getExternalStorageDirectory(self):
|
||||
return _sdcard_root
|
||||
|
||||
def getSystemService(self, name):
|
||||
return None # 由具体模块填充
|
||||
|
||||
|
||||
android_context = _GlobalContext()
|
||||
|
||||
|
||||
def get_context():
|
||||
"""获取全局 Android Context"""
|
||||
return android_context
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# 可选依赖检测
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
def _check_optional(name: str) -> bool:
|
||||
"""检查可选依赖是否可用"""
|
||||
try:
|
||||
__import__(name)
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
|
||||
HAS_PIL = _check_optional("PIL")
|
||||
HAS_REQUESTS = _check_optional("requests")
|
||||
HAS_HTTPX = _check_optional("httpx")
|
||||
HAS_PYAUTOGUI = _check_optional("pyautogui")
|
||||
HAS_SCREENINFO = _check_optional("screeninfo")
|
||||
HAS_PSUTIL = _check_optional("psutil")
|
||||
HAS_PYMUPDF = _check_optional("fitz")
|
||||
HAS_PDF2IMAGE = _check_optional("pdf2image")
|
||||
HAS_CRYPTOGRAPHY = _check_optional("cryptography")
|
||||
HAS_PYPERCLIP = _check_optional("pyperclip")
|
||||
HAS_JPYPE = _check_optional("jpype")
|
||||
Reference in new issue
Block a user