187 lines
6.0 KiB
Python
187 lines
6.0 KiB
Python
"""
|
|
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")
|