""" Android Database Extension 组件 — ContentObserver, DataSetObserver, CrossProcessCursor, CursorWindow 等 方法签名完全对齐 Android SDK (android.database 包扩展) """ from __future__ import annotations import time from typing import Any, Callable, Dict, List, Optional, Set, Tuple from .._core import Stub, AndroidOnlyError from ..java.lang import RuntimeException # ═══════════════════════════════════════════════════════════════ # ContentObserver # ═══════════════════════════════════════════════════════════════ class ContentObserver(Stub): """ContentObserver — 内容观察者""" NOTIFY_URIS = 0 NOTIFY_SYNC_TO_NETWORK = 1 NOTIFY_SKIP_SYNC = 2 def __init__(self, handler: Any = None): super().__init__() self._handler = handler self._is_alive: bool = True self._self_change_count: int = 0 self._change_count: int = 0 self._observed_uris: List[str] = [] def onChange(self, self_change: bool = False): """内容变化回调""" pass def onChange(self, self_change: bool, uri: Any = None): """内容变化回调 (带 URI)""" pass def onChange(self, self_change: bool, uri: Any, user_id: int = 0): pass def deliverSelfNotifications(self) -> bool: """是否传递自身通知""" return False def isAlive(self) -> bool: return self._is_alive def release(self): """释放观察者""" self._is_alive = False self._observed_uris.clear() def dispatchChange(self, self_change: bool, uri: Any = None, user_id: int = 0): """调度变化""" if self._is_alive: if uri is not None: self.onChange(self_change, uri, user_id) else: self.onChange(self_change) def getHandler(self) -> Any: return self._handler def getChangeCount(self) -> int: return self._change_count def incrementChangeCount(self): self._change_count += 1 def addObservedUri(self, uri: str): if uri not in self._observed_uris: self._observed_uris.append(uri) def removeObservedUri(self, uri: str): self._observed_uris = [u for u in self._observed_uris if u != uri] def getObservedUris(self) -> List[str]: return list(self._observed_uris) def isObservingUri(self, uri: str) -> bool: return uri in self._observed_uris def toString(self) -> str: return f"ContentObserver(alive={self._is_alive}, uris={len(self._observed_uris)})" # ═══════════════════════════════════════════════════════════════ # DataSetObserver # ═══════════════════════════════════════════════════════════════ class DataSetObserver(Stub): """DataSetObserver — 数据集观察者""" def __init__(self): super().__init__() self._is_alive: bool = True self._notify_count: int = 0 def onChanged(self): """数据集变化回调""" self._notify_count += 1 def onInvalidated(self): """数据集失效回调""" pass def isAlive(self) -> bool: return self._is_alive def release(self): self._is_alive = False def getNotifyCount(self) -> int: return self._notify_count def toString(self) -> str: return f"DataSetObserver(alive={self._is_alive}, notified={self._notify_count})" # ═══════════════════════════════════════════════════════════════ # DataSetValidatable # ═══════════════════════════════════════════════════════════════ class DataSetValidatable(Stub): """DataSetValidatable — 数据集可验证接口""" def isValid(self) -> bool: """检查数据集是否有效""" return True def validate(self) -> bool: """验证数据集""" return self.isValid() def getValidationError(self) -> Optional[str]: """获取验证错误信息""" return None def resetValidation(self): """重置验证状态""" pass # ═══════════════════════════════════════════════════════════════ # CrossProcessCursor # ═══════════════════════════════════════════════════════════════ class CrossProcessCursor(Stub): """CrossProcessCursor — 跨进程游标""" def __init__(self): super().__init__() self._window: Optional[CursorWindow] = None self._position: int = -1 self._row_count: int = 0 self._column_count: int = 0 def getWindow(self) -> Optional[CursorWindow]: return self._window def setWindow(self, window: CursorWindow): self._window = window def fillWindow(self, position: int, window: CursorWindow): """填充窗口""" self._window = window self._position = position def onMove(self, old_position: int, new_position: int) -> bool: """移动回调""" return 0 <= new_position < self._row_count def getPosition(self) -> int: return self._position def moveToPosition(self, position: int) -> bool: if 0 <= position < self._row_count: old = self._position self._position = position return self.onMove(old, position) return False def moveToFirst(self) -> bool: return self.moveToPosition(0) def moveToLast(self) -> bool: return self.moveToPosition(self._row_count - 1) def moveToNext(self) -> bool: return self.moveToPosition(self._position + 1) def moveToPrevious(self) -> bool: return self.moveToPosition(self._position - 1) def isFirst(self) -> bool: return self._position == 0 def isLast(self) -> bool: return self._position == self._row_count - 1 def isBeforeFirst(self) -> bool: return self._position < 0 def isAfterLast(self) -> bool: return self._position >= self._row_count def getRowCount(self) -> int: return self._row_count def setRowCount(self, count: int): self._row_count = count def getColumnCount(self) -> int: return self._column_count def setColumnCount(self, count: int): self._column_count = count def getColumnIndex(self, column_name: str) -> int: return -1 def getColumnName(self, index: int) -> str: return "" def getColumnNames(self) -> List[str]: return [] def getString(self, column_index: int) -> Optional[str]: return None def getInt(self, column_index: int) -> int: return 0 def getLong(self, column_index: int) -> int: return 0 def getFloat(self, column_index: int) -> float: return 0.0 def getDouble(self, column_index: int) -> float: return 0.0 def isNull(self, column_index: int) -> bool: return True def close(self): self._window = None self._row_count = 0 def requery(self) -> bool: return False def toString(self) -> str: return f"CrossProcessCursor(rows={self._row_count}, pos={self._position})" # ═══════════════════════════════════════════════════════════════ # CrossProcessCursorWrapper # ═══════════════════════════════════════════════════════════════ class CrossProcessCursorWrapper(CrossProcessCursor): """CrossProcessCursorWrapper — 跨进程游标包装器""" def __init__(self, cursor: Optional[CrossProcessCursor] = None): super().__init__() self._wrapped_cursor = cursor def getWrappedCursor(self) -> Optional[CrossProcessCursor]: return self._wrapped_cursor def setWrappedCursor(self, cursor: CrossProcessCursor): self._wrapped_cursor = cursor def getWindow(self) -> Optional[CursorWindow]: if self._wrapped_cursor: return self._wrapped_cursor.getWindow() return self._window def fillWindow(self, position: int, window: CursorWindow): if self._wrapped_cursor: self._wrapped_cursor.fillWindow(position, window) else: super().fillWindow(position, window) def onMove(self, old_position: int, new_position: int) -> bool: if self._wrapped_cursor: return self._wrapped_cursor.onMove(old_position, new_position) return super().onMove(old_position, new_position) def getRowCount(self) -> int: if self._wrapped_cursor: return self._wrapped_cursor.getRowCount() return self._row_count def getColumnCount(self) -> int: if self._wrapped_cursor: return self._wrapped_cursor.getColumnCount() return self._column_count def getColumnNames(self) -> List[str]: if self._wrapped_cursor: return self._wrapped_cursor.getColumnNames() return [] def getColumnIndex(self, column_name: str) -> int: if self._wrapped_cursor: return self._wrapped_cursor.getColumnIndex(column_name) return -1 def getString(self, column_index: int) -> Optional[str]: if self._wrapped_cursor: return self._wrapped_cursor.getString(column_index) return None def getInt(self, column_index: int) -> int: if self._wrapped_cursor: return self._wrapped_cursor.getInt(column_index) return 0 def getLong(self, column_index: int) -> int: if self._wrapped_cursor: return self._wrapped_cursor.getLong(column_index) return 0 def getDouble(self, column_index: int) -> float: if self._wrapped_cursor: return self._wrapped_cursor.getDouble(column_index) return 0.0 def moveToPosition(self, position: int) -> bool: if self._wrapped_cursor: return self._wrapped_cursor.moveToPosition(position) return super().moveToPosition(position) def close(self): if self._wrapped_cursor: self._wrapped_cursor.close() super().close() def toString(self) -> str: return f"CrossProcessCursorWrapper(wrapped={self._wrapped_cursor})" # ═══════════════════════════════════════════════════════════════ # CursorWindow # ═══════════════════════════════════════════════════════════════ class CursorWindow(Stub): """CursorWindow — 游标窗口""" WINDOW_SIZE_LIMIT = 2097152 # 2MB TYPE_NULL = 0 TYPE_INTEGER = 1 TYPE_FLOAT = 2 TYPE_BLOB = 4 TYPE_STRING = 5 def __init__(self, name: str = "CursorWindow"): super().__init__() self._name = name self._start_position: int = 0 self._num_rows: int = 0 self._is_open: bool = True self._data: Dict[Tuple[int, int], Any] = {} self._types: Dict[Tuple[int, int], int] = {} self._allocated_memory: int = 0 self._max_allocated_memory: int = CursorWindow.WINDOW_SIZE_LIMIT @staticmethod def newFromParcel(data: Any) -> CursorWindow: return CursorWindow("from_parcel") def clear(self): """清空窗口""" self._data.clear() self._types.clear() self._num_rows = 0 self._allocated_memory = 0 def getStartPosition(self) -> int: return self._start_position def setStartPosition(self, pos: int): self._start_position = pos def getNumRows(self) -> int: return self._num_rows def setNumColumns(self, num_columns: int): self._num_columns = num_columns def getNumColumns(self) -> int: return self._num_columns def allocRow(self) -> bool: """分配一行""" self._num_rows += 1 return True def freeLastRow(self): """释放最后一行""" if self._num_rows > 0: self._num_rows -= 1 keys_to_remove = [k for k in self._data if k[0] == self._num_rows] for k in keys_to_remove: del self._data[k] del self._types[k] def putString(self, value: str, row: int, col: int) -> bool: self._data[(row, col)] = value self._types[(row, col)] = CursorWindow.TYPE_STRING self._allocated_memory += len(value) return True def putLong(self, value: int, row: int, col: int) -> bool: self._data[(row, col)] = value self._types[(row, col)] = CursorWindow.TYPE_INTEGER self._allocated_memory += 8 return True def putDouble(self, value: float, row: int, col: int) -> bool: self._data[(row, col)] = value self._types[(row, col)] = CursorWindow.TYPE_FLOAT self._allocated_memory += 8 return True def putBlob(self, value: bytes, row: int, col: int) -> bool: self._data[(row, col)] = value self._types[(row, col)] = CursorWindow.TYPE_BLOB self._allocated_memory += len(value) return True def putNull(self, row: int, col: int) -> bool: self._data[(row, col)] = None self._types[(row, col)] = CursorWindow.TYPE_NULL return True def getString(self, row: int, col: int) -> Optional[str]: val = self._data.get((row, col)) if isinstance(val, str): return val if val is None: return None return str(val) def getLong(self, row: int, col: int) -> int: val = self._data.get((row, col), 0) if isinstance(val, int): return val if isinstance(val, float): return int(val) if isinstance(val, str): try: return int(val) except ValueError: return 0 return 0 def getDouble(self, row: int, col: int) -> float: val = self._data.get((row, col), 0.0) if isinstance(val, (int, float)): return float(val) if isinstance(val, str): try: return float(val) except ValueError: return 0.0 return 0.0 def getBlob(self, row: int, col: int) -> Optional[bytes]: val = self._data.get((row, col)) if isinstance(val, bytes): return val if isinstance(val, str): return val.encode("utf-8") return None def getType(self, row: int, col: int) -> int: return self._types.get((row, col), CursorWindow.TYPE_NULL) def isNull(self, row: int, col: int) -> bool: return self.getType(row, col) == CursorWindow.TYPE_NULL def isOpen(self) -> bool: return self._is_open def close(self): """关闭窗口""" self._is_open = False self.clear() def toString(self) -> str: return f"CursorWindow(name={self._name}, rows={self._num_rows}, open={self._is_open})" # ═══════════════════════════════════════════════════════════════ # DatabaseUtils # ═══════════════════════════════════════════════════════════════ class DatabaseUtils(Stub): """DatabaseUtils — 数据库工具""" STATEMENT_ABORT = 0 STATEMENT_ATTACH = 1 STATEMENT_BEGIN = 2 STATEMENT_COMMIT = 3 STATEMENT_DELETE = 4 STATEMENT_DETACH = 5 STATEMENT_DROP_INDEX = 6 STATEMENT_DROP_TABLE = 7 STATEMENT_DROP_TRIGGER = 8 STATEMENT_DROP_VIEW = 9 STATEMENT_INSERT = 10 STATEMENT_PRAGMA = 11 STATEMENT_REINDEX = 12 STATEMENT_RELEASE = 13 STATEMENT_REPLACE = 14 STATEMENT_ROLLBACK = 15 STATEMENT_SAVEPOINT = 16 STATEMENT_SELECT = 17 STATEMENT_UPDATE = 18 STATEMENT_OTHER = 99 @staticmethod def sqlEscapeString(value: str) -> str: """SQL 转义字符串""" return "'" + value.replace("'", "''") + "'" @staticmethod def appendValueToSql(sql: str, value: Any) -> str: """将值附加到 SQL""" if value is None: return sql + " NULL" if isinstance(value, bool): return sql + (" 1" if value else " 0") if isinstance(value, int): return sql + f" {value}" if isinstance(value, float): return sql + f" {value}" return sql + f" '{DatabaseUtils.sqlEscapeString(str(value))}'" @staticmethod def concatenateWhere(a: str, b: str) -> str: """连接 WHERE 子句""" if a is None or a == "": return b if b is None or b == "": return a return f"({a}) AND ({b})" @staticmethod def getCollationKey(name: str) -> str: """获取排序键""" return name.lower() @staticmethod def getHexCollationKey(name: str) -> str: """获取十六进制排序键""" if not name: return "" return name.encode("utf-8").hex() @staticmethod def dumpCursor(cursor: CrossProcessCursor): """转储游标内容""" rows = cursor.getRowCount() cols = cursor.getColumnCount() names = cursor.getColumnNames() for r in range(rows): cursor.moveToPosition(r) row_data = {} for c in range(cols): name = names[c] if c < len(names) else f"col{c}" row_data[name] = cursor.getString(c) print(str(row_data)) @staticmethod def cursorRowToContentValues(cursor: CrossProcessCursor) -> Dict[str, Any]: """将游标行转换为内容值""" values = {} for c in range(cursor.getColumnCount()): name = cursor.getColumnName(c) values[name] = cursor.getString(c) return values @staticmethod def LongToHexString(value: int) -> str: """将长整型转换为十六进制字符串""" return f"{value:#018x}" @staticmethod def bindObjectToProgram(statement: Any, index: int, value: Any): """绑定对象到 SQL 程序""" pass @staticmethod def createDbFromSqlStatements(context: Any, name: str, sql_statements: List[str]) -> Any: """从 SQL 语句创建数据库""" return None @staticmethod def queryNumEntries(db: Any, table: str, selection: str = "") -> int: """查询条目数""" return 0 @staticmethod def stringOrEmpty(s: Optional[str]) -> str: """返回字符串或空""" return s if s else "" @staticmethod def isTableExists(db: Any, table_name: str) -> bool: """检查表是否存在""" return True # ═══════════════════════════════════════════════════════════════ # DefaultDatabaseErrorHandler # ═══════════════════════════════════════════════════════════════ class DefaultDatabaseErrorHandler(Stub): """DefaultDatabaseErrorHandler — 默认数据库错误处理器""" ERROR_TYPE_CORRUPT = 1 ERROR_TYPE_FULL = 2 ERROR_TYPE_PERMISSION = 3 ERROR_TYPE_IO = 4 ERROR_TYPE_NOMEM = 5 ERROR_TYPE_TOOBIG = 6 ERROR_TYPE_CONSTRAINT = 7 ERROR_TYPE_MISMATCH = 8 ERROR_TYPE_MISUSE = 9 ERROR_TYPE_NOTADB = 10 ERROR_TYPE_INTERNAL = 11 def __init__(self): super().__init__() self._error_count: int = 0 self._last_error: int = 0 self._error_message: str = "" self._is_recovering: bool = False def onCorruption(self, db_obj: Any): """数据库损坏回调""" self._error_count += 1 self._last_error = DefaultDatabaseErrorHandler.ERROR_TYPE_CORRUPT self._error_message = "Database corruption detected" self._is_recovering = True self._tryRecover(db_obj) self._is_recovering = False def onError(self, db_obj: Any, error_type: int, message: str = ""): """错误回调""" self._error_count += 1 self._last_error = error_type self._error_message = message def _tryRecover(self, db_obj: Any): """尝试恢复数据库""" pass def getErrorCount(self) -> int: return self._error_count def getLastError(self) -> int: return self._last_error def getErrorMessage(self) -> str: return self._error_message def isRecovering(self) -> bool: return self._is_recovering def toString(self) -> str: return f"DefaultDatabaseErrorHandler(errors={self._error_count})" # ═══════════════════════════════════════════════════════════════ # Extras (Database 扩展) # ═══════════════════════════════════════════════════════════════ class Extras(Stub): """Extras — 数据库扩展附加类""" EXTRA_DATABASE_NAME = "database_name" EXTRA_TABLE_NAME = "table_name" EXTRA_SQL_COMMAND = "sql_command" EXTRA_ERROR_DESCRIPTION = "error_description" EXTRA_ROW_COUNT = "row_count" EXTRA_PAGE_SIZE = "page_size" EXTRA_PAGE_COUNT = "page_count" EXTRA_DATABASE_SIZE = "database_size" EXTRA_JOURNAL_MODE = "journal_mode" EXTRA_SYNC_MODE = "sync_mode" EXTRA_CACHE_SIZE = "cache_size" EXTRA_FOREIGN_KEYS = "foreign_keys" EXTRA_ENCODING = "encoding" EXTRA_SCHEMA_VERSION = "schema_version" EXTRA_USER_VERSION = "user_version" FLAG_READ_ONLY = 1 FLAG_WRITE_AHEAD_LOGGING = 2 FLAG_ENABLE_FOREIGN_KEYS = 4 FLAG_CREATE_IF_NECESSARY = 8 FLAG_NO_LOCALIZED_COLLATORS = 16 FLAG_OPEN_READONLY = 32 FLAG_ENABLE_WRITE_AHEAD_LOGGING = 64 FLAG_DISABLE_COMPATIBILITY_WAL = 128 FLAG_OPEN_READ_WRITE = 256 def __init__(self): super().__init__() self._extras: Dict[str, Any] = {} self._flags: int = 0 def getString(self, key: str, default: str = "") -> str: return str(self._extras.get(key, default)) def getInt(self, key: str, default: int = 0) -> int: val = self._extras.get(key, default) return int(val) if val is not None else default def getBoolean(self, key: str, default: bool = False) -> bool: val = self._extras.get(key, default) return bool(val) if val is not None else default def putString(self, key: str, value: str): self._extras[key] = value def putInt(self, key: str, value: int): self._extras[key] = value def putBoolean(self, key: str, value: bool): self._extras[key] = value def putAll(self, extras: Dict[str, Any]): self._extras.update(extras) def containsKey(self, key: str) -> bool: return key in self._extras def getKeys(self) -> List[str]: return list(self._extras.keys()) def size(self) -> int: return len(self._extras) def isEmpty(self) -> bool: return len(self._extras) == 0 def clear(self): self._extras.clear() def getFlags(self) -> int: return self._flags def setFlags(self, flags: int): self._flags = flags def hasFlag(self, flag: int) -> bool: return bool(self._flags & flag) def toString(self) -> str: return f"Extras(entries={len(self._extras)}, flags={self._flags})" # ═══════════════════════════════════════════════════════════════ # BulkCursorDescriptor (helper) # ═══════════════════════════════════════════════════════════════ class BulkCursorDescriptor(Stub): """BulkCursorDescriptor — 批量游标描述符""" def __init__(self): super().__init__() self._cursor: Optional[CrossProcessCursor] = None self._column_names: List[str] = [] self._wants_all_on_move_calls: bool = False self._count: int = 0 self._window: Optional[CursorWindow] = None def getCursor(self) -> Optional[CrossProcessCursor]: return self._cursor def setCursor(self, cursor: CrossProcessCursor): self._cursor = cursor def getColumnNames(self) -> List[str]: return list(self._column_names) def setColumnNames(self, names: List[str]): self._column_names = list(names) def wantsAllOnMoveCalls(self) -> bool: return self._wants_all_on_move_calls def setWantsAllOnMoveCalls(self, wants: bool): self._wants_all_on_move_calls = wants def getCount(self) -> int: return self._count def setCount(self, count: int): self._count = count def getWindow(self) -> Optional[CursorWindow]: return self._window def setWindow(self, window: CursorWindow): self._window = window def toString(self) -> str: return f"BulkCursorDescriptor(columns={len(self._column_names)}, count={self._count})" # ═══════════════════════════════════════════════════════════════ # CursorJoiner (helper) # ═══════════════════════════════════════════════════════════════ class CursorJoiner(Stub): """CursorJoiner — 游标连接器""" RESULT_BOTH = 0 RESULT_LEFT = 1 RESULT_RIGHT = 2 def __init__(self, cursor_left: CrossProcessCursor, column_names_left: List[str], cursor_right: CrossProcessCursor, column_names_right: List[str]): super().__init__() self._left = cursor_left self._right = cursor_right self._left_cols = column_names_left self._right_cols = column_names_right self._left_pos: int = -1 self._right_pos: int = -1 self._result: int = CursorJoiner.RESULT_BOTH def hasNext(self) -> bool: return (self._left_pos + 1 < self._left.getRowCount() or self._right_pos + 1 < self._right.getRowCount()) def next(self) -> int: self._left_pos += 1 self._right_pos += 1 return self._result def getLeftCursor(self) -> CrossProcessCursor: return self._left def getRightCursor(self) -> CrossProcessCursor: return self._right def reset(self): self._left_pos = -1 self._right_pos = -1 def toString(self) -> str: return f"CursorJoiner(left={self._left_pos}, right={self._right_pos})"