""" Android Provider ContactsContract 扩展组件 方法签名完全对齐 Android SDK """ from __future__ import annotations import os import json import time import threading from typing import Any, Dict, List, Optional, Set, Tuple, Union from .._core import Stub, AndroidOnlyError from ..java.lang import RuntimeException # ═══════════════════════════════════════════════════════════════ # 联系人常量 # ═══════════════════════════════════════════════════════════════ TIMES_CONTACTED = "times_contacted" LAST_TIME_CONTACTED = "last_time_contacted" STARRED = "starred" CUSTOM_RINGTONE = "custom_ringtone" SEND_TO_VOICEMAIL = "send_to_voicemail" CONTACT_PRESENCE = "contact_presence" CONTACT_CHAT_CAPABILITY = "contact_chat_capability" CONTACT_STATUS = "contact_status" CONTACT_STATUS_TIMESTAMP = "contact_status_ts" CONTACT_STATUS_RES_PACKAGE = "contact_status_res_package" CONTACT_STATUS_LABEL = "contact_status_label" CONTACT_STATUS_ICON = "contact_status_icon" CONTACT_ID = "_id" CONTACT_LOOKUP_KEY = "lookup" CONTACT_DISPLAY_NAME = "display_name" CONTACT_PHOTO_ID = "photo_id" CONTACT_PHOTO_URI = "photo_uri" CONTACT_PHOTO_THUMBNAIL_URI = "photo_thumb_uri" CONTACT_LAST_TIME_CONTACTED = LAST_TIME_CONTACTED CONTACT_TIMES_CONTACTED = TIMES_CONTACTED CONTACT_STARRED = STARRED CONTACT_HAS_PHONE_NUMBER = "has_phone_number" CONTACT_IS_USER_PROFILE = "is_user_profile" CONTACT_IS_PRIVATE = "is_private" CONTACT_NAME_RAW_CONTACT_ID = "name_raw_contact_id" CONTACT_DISPLAY_NAME_SOURCE = "display_name_source" CONTACT_LOOKUP_KEY = CONTACT_LOOKUP_KEY CONTACT_SORT_KEY_PRIMARY = "sort_key" CONTACT_SORT_KEY_ALTERNATIVE = "sort_key_alt" LOOKUP_KEY = CONTACT_LOOKUP_KEY LOOKUP_ID = "_id" DATA_ID = "_id" DATA_CONTACT_ID = "contact_id" DATA_RAW_CONTACT_ID = "raw_contact_id" DATA_MIMETYPE = "mimetype" DATA_IS_PRIMARY = "is_primary" DATA_IS_SUPER_PRIMARY = "is_super_primary" DATA_DATA_VERSION = "data_version" DATA_DELETED = "deleted" DATA_SYNC1 = "data_sync1" DATA_SYNC2 = "data_sync2" DATA_SYNC3 = "data_sync3" DATA_SYNC4 = "data_sync4" DATA1 = "data1" DATA2 = "data2" DATA3 = "data3" DATA4 = "data4" DATA5 = "data5" DATA6 = "data6" DATA7 = "data7" DATA8 = "data8" DATA9 = "data9" DATA10 = "data10" DATA11 = "data11" DATA12 = "data12" DATA13 = "data13" DATA14 = "data14" DATA15 = "data15" RAW_CONTACT_ID = "_id" RAW_CONTACT_CONTACT_ID = "contact_id" RAW_CONTACT_ACCOUNT_NAME = "account_name" RAW_CONTACT_ACCOUNT_TYPE = "account_type" RAW_CONTACT_SOURCE_ID = "sourceid" RAW_CONTACT_VERSION = "version" RAW_CONTACT_DELETED = "deleted" RAW_CONTACT_AGGREGATION_MODE = "aggregation_mode" RAW_CONTACT_AGGREGATION_NEEDED = 0 RAW_CONTACT_AGGREGATION_SUSPENDED = 1 RAW_CONTACT_AGGREGATION_DEFAULT = 2 RAW_CONTACT_SYNC1 = "sync1" RAW_CONTACT_SYNC2 = "sync2" RAW_CONTACT_SYNC3 = "sync3" RAW_CONTACT_SYNC4 = "sync4" RAW_CONTACT_METADATA = "metadata" # MIME 类型 MIMETYPE_EMAIL = "vnd.android.cursor.item/email_v2" MIMETYPE_PHONE = "vnd.android.cursor.item/phone_v2" MIMETYPE_NAME = "vnd.android.cursor.item/name" MIMETYPE_POSTAL = "vnd.android.cursor.item/postal-address_v2" MIMETYPE_IM = "vnd.android.cursor.item/im" MIMETYPE_NOTE = "vnd.android.cursor.item/note" MIMETYPE_ORGANIZATION = "vnd.android.cursor.item/organization" MIMETYPE_PHOTO = "vnd.android.cursor.item/photo" MIMETYPE_WEBSITE = "vnd.android.cursor.item/website" MIMETYPE_SIP_ADDRESS = "vnd.android.cursor.item/sip_address" MIMETYPE_NICKNAME = "vnd.android.cursor.item/nickname" MIMETYPE_GROUP_MEMBERSHIP = "vnd.android.cursor.item/group_membership" MIMETYPE_EVENT = "vnd.android.cursor.item/contact_event" MIMETYPE_RELATION = "vnd.android.cursor.item/relation" FULL_NAME_STYLE_UNDEFINED = 0 FULL_NAME_STYLE_WESTERN = 1 FULL_NAME_STYLE_EASTERN = 2 FULL_NAME_STYLE_CHINESE = 3 FULL_NAME_STYLE_JAPANESE = 4 FULL_NAME_STYLE_KOREAN = 5 PHONETIC_NAME_STYLE_UNDEFINED = 0 PHONETIC_NAME_STYLE_JAPANESE = 1 PHONETIC_NAME_STYLE_KOREAN = 2 PHONETIC_NAME_STYLE_PINYIN = 3 PHONETIC_NAME_STYLE_ROMANJI = 4 # ═══════════════════════════════════════════════════════════════ # ContactsContract 命名空间容器 # ═══════════════════════════════════════════════════════════════ class ContactsContractBase(Stub): """ContactsContract — 联系人合同基类""" pass class SyncColumns(Stub): """ContactsContract.SyncColumns — 同步列""" ACCOUNT_NAME = RAW_CONTACT_ACCOUNT_NAME ACCOUNT_TYPE = RAW_CONTACT_ACCOUNT_TYPE SOURCE_ID = RAW_CONTACT_SOURCE_ID VERSION = RAW_CONTACT_VERSION DIRTY = "dirty" SYNC1 = RAW_CONTACT_SYNC1 SYNC2 = RAW_CONTACT_SYNC2 SYNC3 = RAW_CONTACT_SYNC3 SYNC4 = RAW_CONTACT_SYNC4 class PresenceColumns(Stub): """ContactsContract.PresenceColumns — 在线状态列""" DATA_ID = "presence_data_id" PROTOCOL = "protocol" CUSTOM_PROTOCOL = "custom_protocol" IM_HANDLE = "im_handle" IM_ACCOUNT = "im_account" STATUS = "mode" STATUS_LABEL = "status_label" STATUS_RES_PACKAGE = "status_res_package" STATUS_ICON = "status_icon" STATUS_TIMESTAMP = "status_ts" AVAILABLE = 5 AWAY = 2 BUSY = 4 DO_NOT_DISTURB = 4 IDLE = 3 INVISIBLE = 1 OFFLINE = 0 class Presence(PresenceColumns): """ContactsContract.Presence — 在线状态""" pass class FullNameStyle(Stub): """ContactsContract.FullNameStyle — 全名风格""" UNDEFINED = FULL_NAME_STYLE_UNDEFINED WESTERN = FULL_NAME_STYLE_WESTERN EASTERN = FULL_NAME_STYLE_EASTERN CHINESE = FULL_NAME_STYLE_CHINESE JAPANESE = FULL_NAME_STYLE_JAPANESE KOREAN = FULL_NAME_STYLE_KOREAN class PhoneticNameStyle(Stub): """ContactsContract.PhoneticNameStyle — 拼音名称风格""" UNDEFINED = PHONETIC_NAME_STYLE_UNDEFINED JAPANESE = PHONETIC_NAME_STYLE_JAPANESE KOREAN = PHONETIC_NAME_STYLE_KOREAN PINYIN = PHONETIC_NAME_STYLE_PINYIN # ═══════════════════════════════════════════════════════════════ # ContactsContract.Contacts # ═══════════════════════════════════════════════════════════════ class ContactData(Stub): """内部联系人数据存储""" def __init__(self): self.id: int = 0 self.lookup_key: str = "" self.display_name: str = "" self.photo_id: int = 0 self.photo_uri: Optional[str] = None self.photo_thumb_uri: Optional[str] = None self.times_contacted: int = 0 self.last_time_contacted: int = 0 self.starred: bool = False self.custom_ringtone: Optional[str] = None self.send_to_voicemail: bool = False self.has_phone_number: bool = False self.is_user_profile: bool = False self.name_raw_contact_id: int = 0 self.display_name_source: int = 0 self.sort_key: str = "" self.sort_key_alt: str = "" class AggregationSuggestions(Stub): """ContactsContract.Contacts.AggregationSuggestions — 聚合建议""" class Builder(Stub): """聚合建议构建器""" def __init__(self): super().__init__() self._contact_id: int = 0 self._limit: int = 0 self._name: Optional[str] = None def setContactId(self, contact_id: int) -> "AggregationSuggestions.Builder": self._contact_id = contact_id return self def setLimit(self, limit: int) -> "AggregationSuggestions.Builder": self._limit = limit return self def addNameParameter(self, name: str) -> "AggregationSuggestions.Builder": self._name = name return self def build(self) -> str: return f"suggestions?contact_id={self._contact_id}&limit={self._limit}" CONTENT_DIRECTORY = "suggestions" PARAMETER_MATCH_NAME = "name" class Photo(Stub): """ContactsContract.Contacts.Photo — 联系人照片""" CONTENT_DIRECTORY = "photo" PHOTO = "data15" PHOTO_FILE_ID = "data14" DISPLAY_PHOTO = "display_photo" class Contacts(Stub): """ContactsContract.Contacts — 联系人表""" CONTENT_URI = "content://com.android.contacts/contacts" CONTENT_LOOKUP_URI = "content://com.android.contacts/contacts/lookup" CONTENT_FILTER_URI = "content://com.android.contacts/contacts/filter" CONTENT_STREQUENT_URI = "content://com.android.contacts/contacts/strequent" CONTENT_FREQUENT_URI = "content://com.android.contacts/contacts/frequent" CONTENT_STREQUENT_EMAIL_URI = "content://com.android.contacts/contacts/strequent/email" CONTENT_GROUP_URI = "content://com.android.contacts/contacts/group" AggregationSuggestions = AggregationSuggestions Photo = Photo # 列 _ID = "_id" LOOKUP_KEY = "lookup" DISPLAY_NAME = "display_name" PHOTO_ID = "photo_id" PHOTO_URI = "photo_uri" PHOTO_THUMBNAIL_URI = "photo_thumb_uri" TIMES_CONTACTED = TIMES_CONTACTED LAST_TIME_CONTACTED = LAST_TIME_CONTACTED STARRED = STARRED CUSTOM_RINGTONE = CUSTOM_RINGTONE SEND_TO_VOICEMAIL = SEND_TO_VOICEMAIL HAS_PHONE_NUMBER = "has_phone_number" IS_USER_PROFILE = "is_user_profile" NAME_RAW_CONTACT_ID = "name_raw_contact_id" DISPLAY_NAME_SOURCE = "display_name_source" SORT_KEY_PRIMARY = "sort_key" SORT_KEY_ALTERNATIVE = "sort_key_alt" CONTACT_LAST_UPDATED_TIMESTAMP = "contact_last_updated_timestamp" CONTACT_PRESENCE = CONTACT_PRESENCE CONTACT_CHAT_CAPABILITY = CONTACT_CHAT_CAPABILITY CONTACT_STATUS = CONTACT_STATUS CONTACT_STATUS_TIMESTAMP = CONTACT_STATUS_TIMESTAMP @staticmethod def getLookupUri(contact_id: int, lookup_key: str) -> str: return f"{Contacts.CONTENT_LOOKUP_URI}/{lookup_key}/{contact_id}" @staticmethod def lookupContact(content_resolver: Any, lookup_uri: str) -> Optional[Dict[str, Any]]: return None # ═══════════════════════════════════════════════════════════════ # ContactsContract.Data # ═══════════════════════════════════════════════════════════════ class Data(Stub): """ContactsContract.Data — 数据表""" CONTENT_URI = "content://com.android.contacts/data" _ID = DATA_ID CONTACT_ID = DATA_CONTACT_ID RAW_CONTACT_ID = DATA_RAW_CONTACT_ID MIMETYPE = DATA_MIMETYPE IS_PRIMARY = DATA_IS_PRIMARY IS_SUPER_PRIMARY = DATA_IS_SUPER_PRIMARY DATA_VERSION = DATA_DATA_VERSION DELETED = DATA_DELETED SYNC1 = DATA_SYNC1 SYNC2 = DATA_SYNC2 SYNC3 = DATA_SYNC3 SYNC4 = DATA_SYNC4 DATA1 = DATA1 DATA2 = DATA2 DATA3 = DATA3 DATA4 = DATA4 DATA5 = DATA5 DATA6 = DATA6 DATA7 = DATA7 DATA8 = DATA8 DATA9 = DATA9 DATA10 = DATA10 DATA11 = DATA11 DATA12 = DATA12 DATA13 = DATA13 DATA14 = DATA14 DATA15 = DATA15 # 偏好常量 CONTACT_PREFERRED_ORDER = "contact_preferred_order" CONTACT_PREFERRED_PHONE = "contact_preferred_phone" CONTACT_PREFERRED_EMAIL = "contact_preferred_email" CONTACT_PREFERRED_PHONE_ORDER = "contact_preferred_phone_order" CONTACT_PREFERRED_EMAIL_ORDER = "contact_preferred_email_order" # 可见性 VISIBILITY_VISIBLE = 0 VISIBILITY_HIDDEN = 1 VISIBILITY_DEFAULT = 0 VISIBILITY = "visibility" @staticmethod def getContactPreferred(data_kind: str, data_set: List[Dict]) -> Optional[Dict]: if not data_set: return None primaries = [d for d in data_set if d.get("is_super_primary") or d.get("is_primary")] if primaries: return primaries[0] return data_set[0] # ═══════════════════════════════════════════════════════════════ # ContactsContract.CommonDataKinds # ═══════════════════════════════════════════════════════════════ class CommonDataKinds(Stub): """ContactsContract.CommonDataKinds — 通用数据类型""" class Email(Stub): CONTENT_ITEM_TYPE = MIMETYPE_EMAIL TYPE_HOME = 1 TYPE_WORK = 2 TYPE_OTHER = 3 TYPE_MOBILE = 4 ADDRESS = DATA1 DISPLAY_NAME = DATA4 TYPE = DATA2 LABEL = DATA3 class Phone(Stub): CONTENT_ITEM_TYPE = MIMETYPE_PHONE TYPE_HOME = 1 TYPE_MOBILE = 2 TYPE_WORK = 3 TYPE_FAX_WORK = 4 TYPE_FAX_HOME = 5 TYPE_PAGER = 6 TYPE_OTHER = 7 TYPE_CALLBACK = 8 TYPE_CAR = 9 TYPE_COMPANY_MAIN = 10 TYPE_ISDN = 11 TYPE_MAIN = 12 TYPE_OTHER_FAX = 13 TYPE_RADIO = 14 TYPE_TELEX = 15 TYPE_TTY_TDD = 16 TYPE_WORK_MOBILE = 17 TYPE_WORK_PAGER = 18 TYPE_ASSISTANT = 19 TYPE_MMS = 20 NUMBER = DATA1 NUMBER_KEY = "data4" TYPE = DATA2 LABEL = DATA3 class StructuredName(Stub): CONTENT_ITEM_TYPE = MIMETYPE_NAME DISPLAY_NAME = DATA1 GIVEN_NAME = DATA2 FAMILY_NAME = DATA3 PREFIX = DATA4 MIDDLE_NAME = DATA5 SUFFIX = DATA6 PHONETIC_GIVEN_NAME = DATA7 PHONETIC_FAMILY_NAME = DATA8 PHONETIC_MIDDLE_NAME = DATA9 FULL_NAME_STYLE = DATA10 PHONETIC_NAME_STYLE = DATA11 class StructuredPostal(Stub): CONTENT_ITEM_TYPE = MIMETYPE_POSTAL FORMATTED_ADDRESS = DATA1 STREET = DATA4 POBOX = DATA5 NEIGHBORHOOD = DATA6 CITY = DATA7 REGION = DATA8 POSTCODE = DATA9 COUNTRY = DATA10 TYPE = DATA2 LABEL = DATA3 TYPE_HOME = 1 TYPE_WORK = 2 TYPE_OTHER = 3 class Im(Stub): CONTENT_ITEM_TYPE = MIMETYPE_IM PROTOCOL = DATA5 CUSTOM_PROTOCOL = DATA6 DATA = DATA1 TYPE = DATA2 PROTOCOL_AIM = 0 PROTOCOL_MSN = 1 PROTOCOL_YAHOO = 2 PROTOCOL_SKYPE = 3 PROTOCOL_QQ = 4 PROTOCOL_GTALK = 5 PROTOCOL_ICQ = 6 PROTOCOL_JABBER = 7 PROTOCOL_CUSTOM = -1 class Organization(Stub): CONTENT_ITEM_TYPE = MIMETYPE_ORGANIZATION COMPANY = DATA1 TYPE = DATA2 LABEL = DATA3 TITLE = DATA4 DEPARTMENT = DATA5 JOB_DESCRIPTION = DATA6 SYMBOL = DATA7 PHONETIC_NAME = DATA8 OFFICE_LOCATION = DATA9 PHONETIC_NAME_STYLE = DATA10 TYPE_WORK = 1 TYPE_OTHER = 2 class Photo(Stub): CONTENT_ITEM_TYPE = MIMETYPE_PHOTO PHOTO = DATA15 PHOTO_FILE_ID = DATA14 class Note(Stub): CONTENT_ITEM_TYPE = MIMETYPE_NOTE NOTE = DATA1 class Website(Stub): CONTENT_ITEM_TYPE = MIMETYPE_WEBSITE URL = DATA1 TYPE = DATA2 TYPE_HOMEPAGE = 1 TYPE_BLOG = 2 TYPE_PROFILE = 3 TYPE_HOME = 4 TYPE_WORK = 5 TYPE_FTP = 6 TYPE_OTHER = 7 class Event(Stub): CONTENT_ITEM_TYPE = MIMETYPE_EVENT START_DATE = DATA1 TYPE = DATA2 TYPE_ANNIVERSARY = 1 TYPE_OTHER = 2 TYPE_BIRTHDAY = 3 class Relation(Stub): CONTENT_ITEM_TYPE = MIMETYPE_RELATION NAME = DATA1 TYPE = DATA2 TYPE_ASSISTANT = 1 TYPE_BROTHER = 2 TYPE_CHILD = 3 TYPE_DOMESTIC_PARTNER = 4 TYPE_FATHER = 5 TYPE_FRIEND = 6 TYPE_MANAGER = 7 TYPE_MOTHER = 8 TYPE_PARENT = 9 TYPE_PARTNER = 10 TYPE_REFERRED_BY = 11 TYPE_RELATIVE = 12 TYPE_SISTER = 13 TYPE_SPOUSE = 14 class Nickname(Stub): CONTENT_ITEM_TYPE = MIMETYPE_NICKNAME NAME = DATA1 TYPE = DATA2 TYPE_DEFAULT = 1 TYPE_OTHER_NAME = 2 TYPE_MAIDEN_NAME = 3 TYPE_SHORT_NAME = 4 TYPE_INITIALS = 5 class SipAddress(Stub): CONTENT_ITEM_TYPE = MIMETYPE_SIP_ADDRESS SIP_ADDRESS = DATA1 TYPE = DATA2 TYPE_HOME = 1 TYPE_WORK = 2 # ═══════════════════════════════════════════════════════════════ # ContactsContract.RawContacts # ═══════════════════════════════════════════════════════════════ class RawContacts(Stub): """ContactsContract.RawContacts — 原始联系人""" CONTENT_URI = "content://com.android.contacts/raw_contacts" _ID = RAW_CONTACT_ID CONTACT_ID = RAW_CONTACT_CONTACT_ID ACCOUNT_NAME = RAW_CONTACT_ACCOUNT_NAME ACCOUNT_TYPE = RAW_CONTACT_ACCOUNT_TYPE SOURCE_ID = RAW_CONTACT_SOURCE_ID VERSION = RAW_CONTACT_VERSION DELETED = RAW_CONTACT_DELETED AGGREGATION_MODE = RAW_CONTACT_AGGREGATION_MODE SYNC1 = RAW_CONTACT_SYNC1 SYNC2 = RAW_CONTACT_SYNC2 SYNC3 = RAW_CONTACT_SYNC3 SYNC4 = RAW_CONTACT_SYNC4 METADATA = RAW_CONTACT_METADATA RAW_CONTACT_IS_USER_PROFILE = "raw_contact_is_user_profile" RAW_CONTACT_IS_PRIVATE = "raw_contact_is_private" DISPLAY_NAME_SOURCE = "display_name_source" NAME_VERIFIED = "name_verified" GEOLOCATION = "geolocation" AGGREGATION_MODE_DEFAULT = 0 AGGREGATION_MODE_IMMEDIATE = 1 AGGREGATION_MODE_SUSPENDED = 2 AGGREGATION_MODE_DISABLED = 3 @staticmethod def getContactId(raw_contact_id: int) -> int: return 0 @staticmethod def getLookupUri(raw_contact_id: int, lookup_key: str) -> str: return f"content://com.android.contacts/raw_contacts/{raw_contact_id}" # ═══════════════════════════════════════════════════════════════ # ContactsContract.StatusUpdates / Profile / Settings # ═══════════════════════════════════════════════════════════════ class StatusUpdates(PresenceColumns): """ContactsContract.StatusUpdates — 状态更新""" CONTENT_URI = "content://com.android.contacts/status_updates" class Profile(Stub): """ContactsContract.Profile — 用户个人资料""" CONTENT_URI = "content://com.android.contacts/profile" CONTENT_VCARD_URI = "content://com.android.contacts/profile/vcard" CONTENT_RAW_CONTACTS_URI = "content://com.android.contacts/profile/raw_contacts" MIN_ID = 1000000000 MAX_ID = 2000000000 class Settings(Stub): """ContactsContract.Settings — 联系人设置""" CONTENT_URI = "content://com.android.contacts/settings" ACCOUNT_NAME = RAW_CONTACT_ACCOUNT_NAME ACCOUNT_TYPE = RAW_CONTACT_ACCOUNT_TYPE SHOULD_SYNC = "should_sync" UNGROUPED_VISIBLE = "ungrouped_visible" ANY_UNGROUPED = "any_ungrouped" UNGROUPED_COUNT = "ungrouped_count" UNGROUPED_WITH_PHONES = "ungrouped_with_phones" # ═══════════════════════════════════════════════════════════════ # ContactsContract.PhoneLookup / DisplayPhoto / DataUsageFeedback # ═══════════════════════════════════════════════════════════════ class PhoneLookup(Stub): """ContactsContract.PhoneLookup — 电话查找""" CONTENT_URI = "content://com.android.contacts/phone_lookup" CONTENT_FILTER_URI = "content://com.android.contacts/phone_lookup/filter" QUERY_PARAMETER_SIP_ADDRESS = "sip" NUMBER = "number" TYPE = "type" LABEL = "label" NORMALIZED_NUMBER = "normalized_number" CONTACT_ID = "contact_id" LOOKUP_KEY = "lookup" DISPLAY_NAME = "display_name" PHOTO_ID = "photo_id" PHOTO_URI = "photo_uri" PHOTO_THUMBNAIL_URI = "photo_thumb_uri" class DisplayPhoto(Stub): """ContactsContract.DisplayPhoto — 显示照片""" CONTENT_URI = "content://com.android.contacts/display_photo" DISPLAY_MAX_DIM = "display_max_dim" THUMBNAIL_MAX_DIM = "thumbnail_max_dim" CONTENT_DIRECTORY = "display_photo" class DataUsageFeedback(Stub): """ContactsContract.DataUsageFeedback — 数据使用反馈""" FEEDBACK_URI = "content://com.android.contacts/data/usagefeedback" USAGE_TYPE = "type" USAGE_TYPE_CALL = "call" USAGE_TYPE_TEXT = "text" USAGE_TYPE_EMAIL = "email" USAGE_TYPE_LONG_TEXT = "long_text" USAGE_TYPE_VIDEO_CALL = "video_call" class QuickContact(Stub): """ContactsContract.QuickContact — 快速联系人""" ACTION_QUICK_CONTACT = "android.provider.ContactsContract.QuickContact" MODE_SMALL = 0 MODE_MEDIUM = 1 MODE_LARGE = 2 EXTRA_EXCLUDE_MIMES = "android.provider.extra.EXCLUDE_MIMES" EXTRA_MODE = "android.provider.extra.MODE" EXTRA_PRIORITIZED_MIMETYPE = "android.provider.extra.PRIORITIZED_MIMETYPE" # ═══════════════════════════════════════════════════════════════ # ContactsContract 控制器 — 模拟 ContentProvider 的数据存储 # ═══════════════════════════════════════════════════════════════ class ContactsContract(ContactsContractBase): """ContactsContract — 联系人合同""" Contacts = Contacts Data = Data CommonDataKinds = CommonDataKinds RawContacts = RawContacts StatusUpdates = StatusUpdates Profile = Profile Settings = Settings SyncColumns = SyncColumns Presence = Presence PhoneLookup = PhoneLookup DisplayPhoto = DisplayPhoto FullNameStyle = FullNameStyle PhoneticNameStyle = PhoneticNameStyle QuickContact = QuickContact DataUsageFeedback = DataUsageFeedback AUTHORITY = "com.android.contacts" CALLER_IS_SYNCADAPTER = "caller_is_syncadapter" DEFERRED_SNIPPETING = "deferred_snippeting" DEFERRED_SNIPPETING_QUERY = "deferred_snippeting_query" DIRECTORY_CONTENT_URI = "content://com.android.contacts/directories" LIMIT_PARAM_KEY = "limit" PRIMARY_ACCOUNT_NAME = "name_forPrimaryAccount" PRIMARY_ACCOUNT_TYPE = "type_forPrimaryAccount" PROVIDER_INTERFACE = "android.content.ContactsContract" REMOVE_DUPLICATE_ENTRIES = "remove_duplicate_entries" STREQUENT_PHONE_ONLY = "strequent_phone_only" _store: Dict[str, List[Dict[str, Any]]] = { "contacts": [], "raw_contacts": [], "data": [], } _lock = threading.Lock() @classmethod def addContact(cls, display_name: str = "", phone: str = "", email: str = "") -> int: with cls._lock: contact_id = len(cls._store["contacts"]) + 1 contact = { "_id": contact_id, "display_name": display_name, "has_phone_number": bool(phone), "times_contacted": 0, "last_time_contacted": 0, "starred": False, } cls._store["contacts"].append(contact) raw_id = len(cls._store["raw_contacts"]) + 1 raw = { "_id": raw_id, "contact_id": contact_id, "aggregation_mode": 0, "deleted": False, "account_name": "", "account_type": "", } cls._store["raw_contacts"].append(raw) if phone: cls._store["data"].append({ "_id": len(cls._store["data"]) + 1, "raw_contact_id": raw_id, "mimetype": MIMETYPE_PHONE, "data1": phone, "data2": 2, "is_primary": True, }) if email: cls._store["data"].append({ "_id": len(cls._store["data"]) + 1, "raw_contact_id": raw_id, "mimetype": MIMETYPE_EMAIL, "data1": email, "data2": 1, "is_primary": True, }) return contact_id @classmethod def query(cls, table: str, filters: Optional[Dict] = None) -> List[Dict[str, Any]]: with cls._lock: results = list(cls._store.get(table, [])) if filters: filtered = [] for item in results: match = True for k, v in filters.items(): if item.get(k) != v: match = False break if match: filtered.append(item) return filtered return results @classmethod def toString(cls) -> str: return (f"ContactsContract(contacts={len(cls._store['contacts'])}, " f"raw={len(cls._store['raw_contacts'])}, " f"data={len(cls._store['data'])})")