""" 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")