Files
Android-pyApi/android_pyapi/android/provider.py
T
shuiyu1123 bfe27fdc08 feat: Android-pyApi v1.0 - 全面翻译 Android SDK API 为 Python
52 个模块覆盖 Android + Java + org.json 标准库
2026-06-24 11:16:16 +00:00

550 lines
19 KiB
Python

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