feat: 新增 42 个 Android API 模块
新增模块覆盖: 系统层 (14): system, security, print, opengl, renderscript, transition, test, service_notification, service_controls, service_quicksettings, service_wallpaper, service_autofill, service_dreams, service_vr 应用层 (13): app_admin, app_backup, app_job, app_usage, app_role, app_people, appwidget, companion, credentials, mtp, sip, telecom, speech 硬件媒体层 (15): hardware_display, hardware_fingerprint, hardware_usb, hardware_input, media_audiofx, media_browse, media_session, media_tv, media_midi, media_projection, uwb, se, view_autofill, view_textclassifier, view_translation 总计 94 个模块,75,342 行代码
This commit is contained in:
1 parent
8ad62be068
commit
506f9ac725
43 files changed
+16704
-23
No files matched your search
@@ -0,0 +1,477 @@
|
||||
"""
|
||||
Android Backup API — BackupManager, BackupAgent, BackupDataInput/Output, RestoreObserver 等
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import json
|
||||
import os as _os
|
||||
import sys
|
||||
import tempfile
|
||||
import shutil
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple, Callable, BinaryIO
|
||||
|
||||
from .._core import Stub, AndroidOnlyError, resolve_android_path, _app_data_dir
|
||||
from ..java.lang import RuntimeException
|
||||
|
||||
|
||||
IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {})
|
||||
|
||||
__all__ = [
|
||||
"BackupManager",
|
||||
"BackupAgent",
|
||||
"BackupDataInput",
|
||||
"BackupDataOutput",
|
||||
"FullBackupDataOutput",
|
||||
"BackupHelper",
|
||||
"FileBackupHelper",
|
||||
"SharedPreferencesBackupHelper",
|
||||
"RestoreObserver",
|
||||
"BackupDataInputStream",
|
||||
]
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# 备份存储路径
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
_BACKUP_DIR = _os.path.join(_app_data_dir, "backups")
|
||||
|
||||
|
||||
def _ensure_backup_dir():
|
||||
_os.makedirs(_BACKUP_DIR, exist_ok=True)
|
||||
|
||||
|
||||
_ensure_backup_dir()
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BackupManager
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BackupManager(Stub):
|
||||
"""BackupManager — 备份管理器 (桌面文件备份模拟)"""
|
||||
|
||||
def __init__(self, context: Any = None):
|
||||
super().__init__()
|
||||
self._context = context
|
||||
self._backup_enabled = True
|
||||
|
||||
def dataChanged(self, packageName: str = None):
|
||||
"""通知系统数据已更改,需要备份"""
|
||||
if packageName:
|
||||
flag_path = _os.path.join(_BACKUP_DIR, f"{packageName}.pending")
|
||||
elif self._context:
|
||||
flag_path = _os.path.join(_BACKUP_DIR, f"{self._context.getPackageName()}.pending")
|
||||
else:
|
||||
flag_path = _os.path.join(_BACKUP_DIR, "default.pending")
|
||||
with open(flag_path, "w") as f:
|
||||
f.write(time.time())
|
||||
|
||||
def requestRestore(self, observer: "RestoreObserver") -> int:
|
||||
"""请求恢复备份数据"""
|
||||
raise AndroidOnlyError("BackupManager.requestRestore() 仅在 Android 设备上可用")
|
||||
|
||||
def getCurrentTransport(self) -> Optional[str]:
|
||||
"""获取当前备份传输名称"""
|
||||
return "com.android.localtransport.LocalTransport"
|
||||
|
||||
def listAllTransports(self) -> List[str]:
|
||||
"""列出所有可用备份传输"""
|
||||
return ["com.android.localtransport.LocalTransport"]
|
||||
|
||||
def isBackupEnabled(self) -> bool:
|
||||
"""检查备份是否启用"""
|
||||
return self._backup_enabled
|
||||
|
||||
def setBackupEnabled(self, isEnabled: bool):
|
||||
"""设置备份启用状态"""
|
||||
self._backup_enabled = isEnabled
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"BackupManager(backup_enabled={self._backup_enabled})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BackupAgent
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BackupAgent(Stub):
|
||||
"""BackupAgent — 备份代理基类"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def onCreate(self):
|
||||
"""创建备份代理时调用"""
|
||||
pass
|
||||
|
||||
def onBackup(
|
||||
self,
|
||||
oldState: "BackupDataInput",
|
||||
data: "BackupDataOutput",
|
||||
newState: "BackupDataOutput",
|
||||
):
|
||||
"""执行备份操作"""
|
||||
pass
|
||||
|
||||
def onRestore(
|
||||
self,
|
||||
data: "BackupDataInput",
|
||||
appVersionCode: int,
|
||||
newState: "BackupDataOutput",
|
||||
):
|
||||
"""执行恢复操作"""
|
||||
pass
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"BackupAgent@{hex(id(self))}"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BackupDataInput
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BackupDataInput(Stub):
|
||||
"""BackupDataInput — 备份数据输入流"""
|
||||
|
||||
def __init__(self, filepath: str = ""):
|
||||
super().__init__()
|
||||
self._filepath = filepath
|
||||
self._data: Dict[str, Any] = {}
|
||||
self._keys: List[str] = []
|
||||
self._index = 0
|
||||
if filepath and _os.path.exists(filepath):
|
||||
with open(filepath, "r") as f:
|
||||
self._data = json.load(f)
|
||||
self._keys = list(self._data.keys())
|
||||
|
||||
def readNextHeader(self) -> bool:
|
||||
"""读取下一个数据头"""
|
||||
if self._index < len(self._keys):
|
||||
return True
|
||||
return False
|
||||
|
||||
def getKey(self) -> str:
|
||||
"""获取当前键名"""
|
||||
if self._index < len(self._keys):
|
||||
return self._keys[self._index]
|
||||
return ""
|
||||
|
||||
def getDataSize(self) -> int:
|
||||
"""获取当前数据大小"""
|
||||
if self._index < len(self._keys):
|
||||
val = self._data[self._keys[self._index]]
|
||||
return len(str(val))
|
||||
return 0
|
||||
|
||||
def readEntityData(self, data: bytearray, offset: int, size: int) -> int:
|
||||
"""读取实体数据"""
|
||||
if self._index < len(self._keys):
|
||||
val = str(self._data[self._keys[self._index]])
|
||||
raw = val.encode("utf-8")
|
||||
copy_len = min(size, len(raw) - offset)
|
||||
for i in range(copy_len):
|
||||
data[offset + i] = raw[i]
|
||||
self._index += 1
|
||||
return copy_len
|
||||
return -1
|
||||
|
||||
def skipEntityData(self):
|
||||
"""跳过当前实体数据"""
|
||||
self._index += 1
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"BackupDataInput(file={self._filepath})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BackupDataOutput
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BackupDataOutput(Stub):
|
||||
"""BackupDataOutput — 备份数据输出流"""
|
||||
|
||||
def __init__(self, filepath: str = ""):
|
||||
super().__init__()
|
||||
self._filepath = filepath
|
||||
self._data: Dict[str, str] = {}
|
||||
|
||||
def writeEntityHeader(self, key: str, dataSize: int):
|
||||
"""写入实体数据头"""
|
||||
pass
|
||||
|
||||
def writeEntityData(self, data: bytes, size: int):
|
||||
"""写入实体数据"""
|
||||
key = f"entity_{len(self._data)}"
|
||||
self._data[key] = data[:size].decode("utf-8", errors="replace")
|
||||
if self._filepath:
|
||||
_os.makedirs(_os.path.dirname(self._filepath), exist_ok=True)
|
||||
with open(self._filepath, "w") as f:
|
||||
json.dump(self._data, f)
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"BackupDataOutput(file={self._filepath})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# FullBackupDataOutput
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class FullBackupDataOutput(Stub):
|
||||
"""FullBackupDataOutput — 完整备份数据输出"""
|
||||
|
||||
def __init__(self, quota: int = 0, transportFlags: int = 0):
|
||||
super().__init__()
|
||||
self._quota = quota
|
||||
self._transportFlags = transportFlags
|
||||
|
||||
def getQuota(self) -> int:
|
||||
return self._quota
|
||||
|
||||
def getTransportFlags(self) -> int:
|
||||
return self._transportFlags
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"FullBackupDataOutput(quota={self._quota})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BackupHelper
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BackupHelper(Stub):
|
||||
"""BackupHelper — 备份辅助接口"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def performBackup(
|
||||
self,
|
||||
oldState: "BackupDataInput",
|
||||
data: "BackupDataOutput",
|
||||
newState: "BackupDataOutput",
|
||||
):
|
||||
"""执行备份"""
|
||||
raise AndroidOnlyError("BackupHelper.performBackup() 仅在 Android 设备上可用")
|
||||
|
||||
def restoreEntity(self, data: "BackupDataInput", appVersionCode: int):
|
||||
"""恢复实体"""
|
||||
raise AndroidOnlyError("BackupHelper.restoreEntity() 仅在 Android 设备上可用")
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"BackupHelper@{hex(id(self))}"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# FileBackupHelper
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class FileBackupHelper(BackupHelper):
|
||||
"""FileBackupHelper — 文件备份辅助"""
|
||||
|
||||
def __init__(self, context: Any, files: List[str]):
|
||||
super().__init__()
|
||||
self._context = context
|
||||
self._files = list(files)
|
||||
|
||||
def performBackup(
|
||||
self,
|
||||
oldState: "BackupDataInput",
|
||||
data: "BackupDataOutput",
|
||||
newState: "BackupDataOutput",
|
||||
):
|
||||
"""执行文件备份"""
|
||||
for fname in self._files:
|
||||
if self._context:
|
||||
filepath = _os.path.join(self._context.getFilesDir(), fname)
|
||||
if _os.path.exists(filepath):
|
||||
with open(filepath, "rb") as f:
|
||||
content = f.read()
|
||||
data.writeEntityHeader(fname, len(content))
|
||||
data.writeEntityData(content, len(content))
|
||||
|
||||
def restoreEntity(self, data: "BackupDataInput", appVersionCode: int):
|
||||
"""恢复文件实体"""
|
||||
key = data.getKey()
|
||||
if self._context and key in self._files:
|
||||
filepath = _os.path.join(self._context.getFilesDir(), key)
|
||||
size = data.getDataSize()
|
||||
buf = bytearray(size)
|
||||
data.readEntityData(buf, 0, size)
|
||||
_os.makedirs(_os.path.dirname(filepath), exist_ok=True)
|
||||
with open(filepath, "wb") as f:
|
||||
f.write(buf)
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"FileBackupHelper(files={self._files})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, FileBackupHelper):
|
||||
return False
|
||||
return self._files == other._files
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(tuple(self._files)) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# SharedPreferencesBackupHelper
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class SharedPreferencesBackupHelper(BackupHelper):
|
||||
"""SharedPreferencesBackupHelper — SharedPreferences 备份辅助"""
|
||||
|
||||
def __init__(self, context: Any, prefNames: List[str]):
|
||||
super().__init__()
|
||||
self._context = context
|
||||
self._prefNames = list(prefNames)
|
||||
|
||||
def performBackup(
|
||||
self,
|
||||
oldState: "BackupDataInput",
|
||||
data: "BackupDataOutput",
|
||||
newState: "BackupDataOutput",
|
||||
):
|
||||
"""执行 SharedPreferences 备份"""
|
||||
if not self._context:
|
||||
return
|
||||
prefs_dir = _os.path.join(_app_data_dir, "shared_prefs")
|
||||
for name in self._prefNames:
|
||||
filepath = _os.path.join(prefs_dir, f"{name}.xml")
|
||||
if _os.path.exists(filepath):
|
||||
with open(filepath, "rb") as f:
|
||||
content = f.read()
|
||||
data.writeEntityHeader(name, len(content))
|
||||
data.writeEntityData(content, len(content))
|
||||
|
||||
def restoreEntity(self, data: "BackupDataInput", appVersionCode: int):
|
||||
"""恢复 SharedPreferences"""
|
||||
key = data.getKey()
|
||||
if self._context and key in self._prefNames:
|
||||
prefs_dir = _os.path.join(_app_data_dir, "shared_prefs")
|
||||
filepath = _os.path.join(prefs_dir, f"{key}.xml")
|
||||
size = data.getDataSize()
|
||||
buf = bytearray(size)
|
||||
data.readEntityData(buf, 0, size)
|
||||
_os.makedirs(prefs_dir, exist_ok=True)
|
||||
with open(filepath, "wb") as f:
|
||||
f.write(buf)
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"SharedPreferencesBackupHelper(prefs={self._prefNames})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
if not isinstance(other, SharedPreferencesBackupHelper):
|
||||
return False
|
||||
return self._prefNames == other._prefNames
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return hash(tuple(self._prefNames)) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# RestoreObserver
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class RestoreObserver(Stub):
|
||||
"""RestoreObserver — 恢复过程观察者"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def onUpdate(self, nowBeingRestored: int, currentPackage: str):
|
||||
"""恢复进度更新回调"""
|
||||
pass
|
||||
|
||||
def onRestoreStarting(self, numPackages: int):
|
||||
"""恢复开始回调"""
|
||||
pass
|
||||
|
||||
def onRestoreFinished(self, error: int):
|
||||
"""恢复完成回调"""
|
||||
pass
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"RestoreObserver@{hex(id(self))}"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# BackupDataInputStream
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class BackupDataInputStream(Stub):
|
||||
"""BackupDataInputStream — 备份数据输入流包装器"""
|
||||
|
||||
def __init__(self, dataInput: "BackupDataInput" = None):
|
||||
super().__init__()
|
||||
self._input = dataInput
|
||||
self._pos = 0
|
||||
self._size = 0
|
||||
|
||||
def read(self) -> int:
|
||||
"""读取一个字节"""
|
||||
if self._input and self._pos < self._size:
|
||||
self._pos += 1
|
||||
return 0
|
||||
return -1
|
||||
|
||||
def readFully(self, data: bytearray, offset: int, length: int):
|
||||
"""读取完整数据块"""
|
||||
if self._input:
|
||||
self._input.readEntityData(data, offset, length)
|
||||
|
||||
def size(self) -> int:
|
||||
return self._size
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"BackupDataInputStream(size={self._size})"
|
||||
|
||||
def equals(self, other) -> bool:
|
||||
return self is other
|
||||
|
||||
def hashCode(self) -> int:
|
||||
return id(self) & 0x7FFFFFFF
|
||||
Reference in new issue
Block a user