""" Android Provider 组件 — Settings, MediaStore, ContactsContract 等 方法签名完全对齐 Android SDK """ import os import sys import json from typing import Any, Dict, List, Optional, Set, Tuple, Union from .._core import ( Stub, AndroidOnlyError, android_context, resolve_android_path, to_android_path, _sdcard_root, _app_data_dir, _package_name, ) # ═══════════════════════════════════════════════════════════════ # OpenableColumns # ═══════════════════════════════════════════════════════════════ class OpenableColumns(Stub): DISPLAY_NAME = "_display_name" SIZE = "_size" # ═══════════════════════════════════════════════════════════════ # Settings # ═══════════════════════════════════════════════════════════════ class _SettingsBase(Stub): """Settings 基类 — 存储到 JSON 文件""" _DATA_DIR = os.path.join(_app_data_dir, "shared_prefs") _cache: Dict[str, Dict[str, Any]] = {} @classmethod def _get_path(cls, name: str) -> str: os.makedirs(cls._DATA_DIR, exist_ok=True) return os.path.join(cls._DATA_DIR, f"{name}.json") @classmethod def _load(cls, name: str) -> Dict[str, Any]: if name in cls._cache: return cls._cache[name] path = cls._get_path(name) if os.path.exists(path): try: with open(path, "r") as f: data = json.load(f) cls._cache[name] = data return data except (json.JSONDecodeError, OSError): pass cls._cache[name] = {} return cls._cache[name] @classmethod def _save(cls, name: str): path = cls._get_path(name) try: with open(path, "w") as f: json.dump(cls._cache.get(name, {}), f) except OSError: pass @staticmethod def putString(name: str, key: str, value: str) -> bool: data = _SettingsBase._load(name) data[key] = value _SettingsBase._save(name) return True @staticmethod def getString(name: str, key: str, default_value: Optional[str] = None) -> Optional[str]: data = _SettingsBase._load(name) return data.get(key, default_value) @staticmethod def putInt(name: str, key: str, value: int) -> bool: data = _SettingsBase._load(name) data[key] = value _SettingsBase._save(name) return True @staticmethod def getInt(name: str, key: str, default_value: int = 0) -> int: data = _SettingsBase._load(name) return data.get(key, default_value) @staticmethod def putLong(name: str, key: str, value: int) -> bool: return _SettingsBase.putInt(name, key, value) @staticmethod def getLong(name: str, key: str, default_value: int = 0) -> int: return _SettingsBase.getInt(name, key, default_value) @staticmethod def putFloat(name: str, key: str, value: float) -> bool: data = _SettingsBase._load(name) data[key] = value _SettingsBase._save(name) return True @staticmethod def getFloat(name: str, key: str, default_value: float = 0.0) -> float: data = _SettingsBase._load(name) return data.get(key, default_value) class SettingsSystem(_SettingsBase): _NAME = "settings_system" @staticmethod def putInt(key: str, value: int) -> bool: return _SettingsBase.putInt(SettingsSystem._NAME, key, value) @staticmethod def getInt(key: str, default_value: int = 0) -> int: return _SettingsBase.getInt(SettingsSystem._NAME, key, default_value) @staticmethod def putString(key: str, value: str) -> bool: return _SettingsBase.putString(SettingsSystem._NAME, key, value) @staticmethod def getString(key: str, default_value: Optional[str] = None) -> Optional[str]: return _SettingsBase.getString(SettingsSystem._NAME, key, default_value) @staticmethod def putFloat(key: str, value: float) -> bool: return _SettingsBase.putFloat(SettingsSystem._NAME, key, value) @staticmethod def getFloat(key: str, default_value: float = 0.0) -> float: return _SettingsBase.getFloat(SettingsSystem._NAME, key, default_value) @staticmethod def putLong(key: str, value: int) -> bool: return _SettingsBase.putLong(SettingsSystem._NAME, key, value) @staticmethod def getLong(key: str, default_value: int = 0) -> int: return _SettingsBase.getLong(SettingsSystem._NAME, key, default_value) class SettingsSecure(_SettingsBase): _NAME = "settings_secure" @staticmethod def putInt(key: str, value: int) -> bool: return _SettingsBase.putInt(SettingsSecure._NAME, key, value) @staticmethod def getInt(key: str, default_value: int = 0) -> int: return _SettingsBase.getInt(SettingsSecure._NAME, key, default_value) @staticmethod def putString(key: str, value: str) -> bool: return _SettingsBase.putString(SettingsSecure._NAME, key, value) @staticmethod def getString(key: str, default_value: Optional[str] = None) -> Optional[str]: return _SettingsBase.getString(SettingsSecure._NAME, key, default_value) @staticmethod def putFloat(key: str, value: float) -> bool: return _SettingsBase.putFloat(SettingsSecure._NAME, key, value) @staticmethod def getFloat(key: str, default_value: float = 0.0) -> float: return _SettingsBase.getFloat(SettingsSecure._NAME, key, default_value) @staticmethod def putLong(key: str, value: int) -> bool: return _SettingsBase.putLong(SettingsSecure._NAME, key, value) @staticmethod def getLong(key: str, default_value: int = 0) -> int: return _SettingsBase.getLong(SettingsSecure._NAME, key, default_value) class SettingsGlobal(_SettingsBase): _NAME = "settings_global" @staticmethod def putInt(key: str, value: int) -> bool: return _SettingsBase.putInt(SettingsGlobal._NAME, key, value) @staticmethod def getInt(key: str, default_value: int = 0) -> int: return _SettingsBase.getInt(SettingsGlobal._NAME, key, default_value) @staticmethod def putString(key: str, value: str) -> bool: return _SettingsBase.putString(SettingsGlobal._NAME, key, value) @staticmethod def getString(key: str, default_value: Optional[str] = None) -> Optional[str]: return _SettingsBase.getString(SettingsGlobal._NAME, key, default_value) @staticmethod def putFloat(key: str, value: float) -> bool: return _SettingsBase.putFloat(SettingsGlobal._NAME, key, value) @staticmethod def getFloat(key: str, default_value: float = 0.0) -> float: return _SettingsBase.getFloat(SettingsGlobal._NAME, key, default_value) @staticmethod def putLong(key: str, value: int) -> bool: return _SettingsBase.putLong(SettingsGlobal._NAME, key, value) @staticmethod def getLong(key: str, default_value: int = 0) -> int: return _SettingsBase.getLong(SettingsGlobal._NAME, key, default_value) class Settings(Stub): """Settings — 系统设置""" System = SettingsSystem Secure = SettingsSecure Global = SettingsGlobal ACTION_SETTINGS = "android.settings.SETTINGS" ACTION_APPLICATION_DETAILS_SETTINGS = "android.settings.APPLICATION_DETAILS_SETTINGS" EXTRA_APP_PACKAGE = "android.intent.extra.PACKAGE_NAME" # ═══════════════════════════════════════════════════════════════ # MediaStore # ═══════════════════════════════════════════════════════════════ class MediaStore(Stub): """MediaStore — 媒体存储""" class MediaColumns(Stub): _ID = 0 DATA = 1 SIZE = 2 DISPLAY_NAME = 3 MIME_TYPE = 4 TITLE = 5 DATE_ADDED = 6 DATE_MODIFIED = 7 ORIENTATION = 8 WIDTH = 9 HEIGHT = 10 class Files(Stub): @staticmethod def getContentUri(volume_name: str) -> str: return f"content://media/{volume_name}/file" class Images(Stub): class Media(Stub): EXTERNAL_CONTENT_URI = "content://media/external/images/media" INTERNAL_CONTENT_URI = "content://media/internal/images/media" class Thumbnails(Stub): EXTERNAL_CONTENT_URI = "content://media/external/images/thumbnails" class Video(Stub): class Media(Stub): EXTERNAL_CONTENT_URI = "content://media/external/video/media" class Audio(Stub): class Media(Stub): EXTERNAL_CONTENT_URI = "content://media/external/audio/media" @staticmethod def getVersion(context: Any, uri: str) -> str: return "0" @staticmethod def setVersion(context: Any, uri: str, version: str) -> bool: return True # ═══════════════════════════════════════════════════════════════ # DocumentsContract # ═══════════════════════════════════════════════════════════════ class DocumentsContract(Stub): """DocumentsContract — 文档合约""" EXTRA_INITIAL_URI = "android.provider.extra.INITIAL_URI" EXTRA_LOADING = "android.provider.extra.LOADING" EXTRA_INFO = "android.provider.extra.INFO" EXTRA_PROMPT = "android.provider.extra.PROMPT" @staticmethod def buildDocumentUri(authority: str, document_id: str) -> str: return f"content://{authority}/document/{document_id}" @staticmethod def buildDocumentUriUsingTree(tree_uri: str, document_id: str) -> str: return f"{tree_uri}/document/{document_id}" @staticmethod def buildChildDocumentsUri(authority: str, parent_document_id: str) -> str: return f"content://{authority}/document/{parent_document_id}/children" @staticmethod def buildChildDocumentsUriUsingTree(tree_uri: str, parent_document_id: str) -> str: return f"{tree_uri}/document/{parent_document_id}/children" @staticmethod def getDocumentId(document_uri: str) -> str: return document_uri.rsplit("/", 1)[-1] @staticmethod def getTreeDocumentId(document_uri: str) -> str: parts = document_uri.split("/") if "document" in parts: idx = parts.index("document") if idx + 1 < len(parts): return parts[idx + 1] return "" @staticmethod def getRootId(root_uri: str) -> str: parts = root_uri.split("/") if "root" in parts: idx = parts.index("root") if idx + 1 < len(parts): return parts[idx + 1] return "" # ═══════════════════════════════════════════════════════════════ # ContactsContract # ═══════════════════════════════════════════════════════════════ class ContactsContract(Stub): """ContactsContract — 联系人合约""" class Contacts(Stub): CONTENT_URI = "content://com.android.contacts/contacts" CONTENT_FILTER_URI = "content://com.android.contacts/contacts/filter" DISPLAY_NAME = "display_name" PHOTO_URI = "photo_uri" _ID = "_id" class CommonDataKinds(Stub): class Email(Stub): CONTENT_URI = "content://com.android.contacts/data/emails" class Phone(Stub): CONTENT_URI = "content://com.android.contacts/data/phones" class Data(Stub): CONTENT_URI = "content://com.android.contacts/data" class ContactsContractContacts(ContactsContract.Contacts): pass class ContactsContractData(ContactsContract.Data): pass # ═══════════════════════════════════════════════════════════════ # CallLog # ═══════════════════════════════════════════════════════════════ class CallLog(Stub): """CallLog — 通话记录""" class Calls(Stub): CONTENT_URI = "content://call_log/calls" _ID = "_id" NUMBER = "number" TYPE = "type" TYPE_INCOMING = 1 TYPE_OUTGOING = 2 TYPE_MISSED = 3 TYPE_VOICEMAIL = 4 TYPE_REJECTED = 5 TYPE_BLOCKED = 6 TYPE_ANSWERED_EXTERNALLY = 7 DATE = "date" DURATION = "duration" NEW = "new" VOICEMAIL_URI = "voicemail_uri" CACHED_NAME = "cached_name" CACHED_NUMBER_LABEL = "cached_number_label" GEOCODED_LOCATION = "geocoded_location" COUNTRY_ISO = "country_iso" @staticmethod def getLastOutgoingCall(context: Any) -> Optional[str]: return None # ═══════════════════════════════════════════════════════════════ # CalendarContract # ═══════════════════════════════════════════════════════════════ class CalendarContract(Stub): """CalendarContract — 日历合约""" class Events(Stub): CONTENT_URI = "content://com.android.calendar/events" _ID = "_id" TITLE = "title" DESCRIPTION = "description" EVENT_LOCATION = "event_location" DTSTART = "dtstart" DTEND = "dtend" EVENT_TIMEZONE = "event_timezone" DURATION = "duration" ALL_DAY = "allDay" CALENDAR_ID = "calendar_id" ORGANIZER = "organizer" STATUS = "eventStatus" AVAILABILITY = "availability" ACCESS_LEVEL = "accessLevel" class Attendees(Stub): CONTENT_URI = "content://com.android.calendar/attendees" class Reminders(Stub): CONTENT_URI = "content://com.android.calendar/reminders" # ═══════════════════════════════════════════════════════════════ # Browser / SearchRecentSuggestions / UserDictionary # ═══════════════════════════════════════════════════════════════ class Browser(Stub): """Browser — 浏览器书签""" class BookmarkColumns(Stub): TITLE = "title" URL = "url" _ID = "_id" BOOKMARK = "bookmark" DATE = "date" CREATED = "created" VISITS = "visits" class SearchColumns(Stub): URL = "url" SEARCH = "search" DATE = "date" HISTORY_PROJECTION = ["_id", "url", "title", "date", "visits", "bookmark", "created"] SEARCHES_PROJECTION = ["_id", "search", "date"] class SearchRecentSuggestions(Stub): """SearchRecentSuggestions — 搜索建议""" def __init__(self, context: Any, authority: str, mode: int): super().__init__() self._authority = authority self._mode = mode def saveRecentQuery(self, query_string: str, line2: str): pass def clearHistory(self): pass class UserDictionary(Stub): """UserDictionary — 用户词典""" class Words(Stub): CONTENT_URI = "content://user_dictionary/words" _ID = "_id" WORD = "word" FREQUENCY = "frequency" LOCALE = "locale" APP_ID = "app_id" SHORTCUT = "shortcut" # ═══════════════════════════════════════════════════════════════ # SyncStateContract / LiveFolders # ═══════════════════════════════════════════════════════════════ class SyncStateContract(Stub): """SyncStateContract — 同步状态合约""" class Columns(Stub): _ID = "_id" ACCOUNT_NAME = "account_name" ACCOUNT_TYPE = "account_type" DATA = "data" class Helpers(Stub): @staticmethod def get(cr: Any, provider_uri: str, account: Any) -> Optional[bytes]: return None @staticmethod def set(cr: Any, provider_uri: str, account: Any, data: bytes): pass @staticmethod def insert(cr: Any, provider_uri: str, account: Any, data: bytes): pass @staticmethod def update(cr: Any, provider_uri: str, account: Any, data: bytes): pass class LiveFolders(Stub): """LiveFolders — 实时文件夹""" NAME = "name" DESCRIPTION = "description" INTENT = "intent" ICON_BITMAP = "icon_bitmap" ICON_PACKAGE = "icon_package" ICON_RESOURCE = "icon_resource" DISPLAY_MODE = "display_mode" DISPLAY_MODE_GRID = 1 DISPLAY_MODE_LIST = 2 # ═══════════════════════════════════════════════════════════════ # __all__ # ═══════════════════════════════════════════════════════════════ __all__ = [ "Settings", "SettingsSystem", "SettingsSecure", "SettingsGlobal", "CallLog", "ContactsContract", "ContactsContractContacts", "ContactsContractData", "MediaStore", "DocumentsContract", "CalendarContract", "Browser", "SearchRecentSuggestions", "UserDictionary", "SyncStateContract", "LiveFolders", "OpenableColumns", ]