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