Поставщик контактов

Поставщик контактов — это мощный и гибкий компонент Android, который управляет центральным хранилищем данных о людях на устройстве. Поставщик контактов — это источник данных, которые вы видите в приложении «Контакты» устройства, и вы также можете получить доступ к его данным в своем приложении и передавать данные между устройством и онлайн-сервисами. Поставщик работает с широким спектром источников данных и стремится управлять как можно большим объемом данных для каждого человека, что обуславливает сложность его организации. В связи с этим API поставщика включает в себя обширный набор классов контрактов и интерфейсов, упрощающих как извлечение, так и изменение данных.

В этом руководстве описывается следующее:

  • Базовая структура провайдера.
  • Как получить данные от провайдера.
  • Как изменить данные в провайдере.
  • Как написать адаптер синхронизации для синхронизации данных с вашего сервера с поставщиком контактов.

Это руководство предполагает, что вы знакомы с основами работы с поставщиками контента для Android. Чтобы узнать больше о поставщиках контента для Android, ознакомьтесь с руководством «Основы работы с поставщиками контента» .

Контакты организации-поставщика

Contacts Provider — это компонент поставщика контента Android. Он поддерживает три типа данных о человеке, каждый из которых соответствует таблице, предлагаемой поставщиком, как показано на рисунке 1:

Рисунок 1. Структура таблицы поставщика контактов.

Эти три таблицы обычно называются по именам их контрактных классов. Классы определяют константы для URI контента, имена столбцов и значения столбцов, используемые таблицами:

Таблица ContactsContract.Contacts
Строки, представляющие разных людей, на основе агрегации необработанных строк контактов.
Таблица ContactsContract.RawContacts
Строки, содержащие сводку данных о человеке, относящихся к учетной записи и типу пользователя.
Таблица « ContactsContract.Data
Строки, содержащие данные необработанных контактов, такие как адреса электронной почты или номера телефонов.

Другие таблицы, представленные классами контрактов в ContactsContract являются вспомогательными таблицами, которые поставщик контактов использует для управления своими операциями или поддержки определенных функций в контактах устройства или приложениях телефонии.

Необработанные контакты

Необработанный контакт представляет собой данные человека, полученные из учётной записи одного типа и имени. Поскольку поставщик контактов допускает использование нескольких онлайн-сервисов в качестве источника данных о человеке, он допускает несколько необработанных контактов для одного и того же человека. Наличие нескольких необработанных контактов также позволяет пользователю объединять данные человека из нескольких учётных записей одного типа.

Большая часть данных необработанного контакта хранится не в таблице ContactsContract.RawContacts , а в одной или нескольких строках таблицы ContactsContract.Data . Каждая строка данных содержит столбец Data.RAW_CONTACT_ID , содержащий значение RawContacts._ID родительской строки ContactsContract.RawContacts .

Важные столбцы необработанных контактов

Важные столбцы в таблице ContactsContract.RawContacts перечислены в таблице 1. Пожалуйста, прочтите примечания, следующие за таблицей:

Таблица 1. Важные столбцы необработанных контактов.

Имя столбца Использовать Примечания
ACCOUNT_NAME Имя учётной записи для типа учётной записи, являющегося источником этого необработанного контакта. Например, имя учётной записи Google — это один из адресов Gmail владельца устройства. Подробнее см. в следующей записи для ACCOUNT_TYPE . Формат этого имени зависит от типа учётной записи. Оно не обязательно является адресом электронной почты.
ACCOUNT_TYPE Тип учётной записи, являющейся источником этого необработанного контакта. Например, тип учётной записи Google — com.google . Всегда указывайте в качестве типа учётной записи идентификатор домена, которым вы владеете или управляете. Это обеспечит уникальность типа вашей учётной записи. Тип учетной записи, предлагающий данные о контактах, обычно имеет связанный с ним адаптер синхронизации, который синхронизируется с поставщиком контактов.
DELETED Флаг «удалено» для необработанного контакта. Этот флаг позволяет поставщику контактов поддерживать строку внутренне до тех пор, пока адаптеры синхронизации не смогут удалить строку со своих серверов, а затем окончательно удалить строку из репозитория.

Примечания

Ниже приведены важные замечания о таблице ContactsContract.RawContacts :

  • Имя необработанного контакта хранится не в строке таблицы ContactsContract.RawContacts , а в строке типа ContactsContract.Data таблицы ContactsContract.CommonDataKinds.StructuredName . Необработанный контакт имеет только одну строку этого типа в таблице ContactsContract.Data .
  • Внимание: Чтобы использовать данные вашей учётной записи в строке необработанного контакта, её необходимо сначала зарегистрировать в AccountManager . Для этого предложите пользователям добавить тип учётной записи и её имя в список учётных записей. В противном случае поставщик контактов автоматически удалит вашу строку необработанного контакта.

    Например, если вы хотите, чтобы ваше приложение поддерживало данные о контактах для вашего веб-сервиса с доменом com.example.dataservice , а учётная запись пользователя для вашего сервиса — becky.sharp@dataservice.example.com , пользователь должен сначала добавить «тип» учётной записи ( com.example.dataservice ) и «имя» учётной записи ( becky.smart@dataservice.example.com ), прежде чем ваше приложение сможет добавлять строки необработанных контактов. Вы можете объяснить это требование пользователю в документации или предложить ему добавить тип и имя, или и то, и другое. Типы и имена учётных записей более подробно описаны в следующем разделе.

Источники необработанных данных о контактах

Чтобы понять, как работают необработанные контакты, рассмотрим пользователя «Эмили Дикинсон», на устройстве которой определены следующие три учетные записи:

  • emily.dickinson@gmail.com
  • emilyd@gmail.com
  • Аккаунт в Twitter "belle_of_amherst"

Этот пользователь включил синхронизацию контактов для всех трех учетных записей в настройках учетных записей .

Предположим, Эмили Дикинсон открывает окно браузера, входит в Gmail как emily.dickinson@gmail.com , открывает Контакты и добавляет «Томаса Хиггинсона». Позже она входит в Gmail как emilyd@gmail.com и отправляет электронное письмо «Томасу Хиггинсону», что автоматически добавляет его в контакты. Она также подписана на «colonel_tom» (идентификатор Томаса Хиггинсона в Twitter).

В результате этой работы поставщик контактов создает три необработанных контакта:

  1. Необработанный контакт для «Томаса Хиггинсона», связанный с emily.dickinson@gmail.com . Тип учётной записи — Google.
  2. Второй необработанный контакт для «Томаса Хиггинсона», связанный с emilyd@gmail.com . Тип учётной записи пользователя также Google. Имеется второй необработанный контакт, хотя имя совпадает с предыдущим, поскольку человек был добавлен для другой учётной записи.
  3. Третий необработанный контакт для «Томаса Хиггинсона», связанный с «belle_of_amherst». Тип учётной записи — Twitter.

Данные

Как отмечалось ранее, данные необработанного контакта хранятся в строке ContactsContract.Data , связанной со значением _ID необработанного контакта. Это позволяет одному необработанному контакту иметь несколько экземпляров данных одного типа, таких как адреса электронной почты или номера телефонов. Например, если у «Томаса Хиггинсона» для emilyd@gmail.com (строка необработанного контакта для Томаса Хиггинсона, связанная с учётной записью Google emilyd@gmail.com ) домашний адрес электронной почты thigg@gmail.com и рабочий адрес электронной почты thomas.higginson@gmail.com , поставщик контактов сохранит обе строки адресов электронной почты и свяжет их обе с необработанным контактом.

Обратите внимание, что в этой таблице хранятся разные типы данных. Строки с отображаемым именем, номером телефона, адресом электронной почты, почтовым адресом, фотографией и информацией о веб-сайте находятся в таблице ContactsContract.Data . Для этого таблица ContactsContract.Data содержит столбцы с описательными названиями и столбцы с общими названиями. Содержимое столбца с описательным названием имеет одинаковое значение независимо от типа данных в строке, в то время как содержимое столбца с общим названием имеет разное значение в зависимости от типа данных.

Описательные названия столбцов

Вот некоторые примеры описательных названий столбцов:

RAW_CONTACT_ID
Значение столбца _ID необработанного контакта для этих данных.
MIMETYPE
Тип данных, хранящихся в этой строке, выраженный в виде пользовательского MIME-типа. Поставщик контактов использует MIME-типы, определенные в подклассах ContactsContract.CommonDataKinds . Эти MIME-типы имеют открытый исходный код и могут использоваться любым приложением или адаптером синхронизации, работающим с поставщиком контактов.
IS_PRIMARY
Если строка данных такого типа может встречаться для необработанного контакта более одного раза, столбец IS_PRIMARY помечает строку данных, содержащую первичные данные для этого типа. Например, если пользователь нажимает и удерживает номер телефона контакта и выбирает «Установить значение по умолчанию» , то в строке ContactsContract.Data , содержащей этот номер, столбец IS_PRIMARY будет установлен в ненулевое значение.

Общие имена столбцов

Существует 15 общих столбцов с именами DATA1DATA15 , которые доступны всем, и ещё четыре общих столбца с именами SYNC1SYNC4 , которые должны использоваться только адаптерами синхронизации. Константы общих имён столбцов работают всегда, независимо от типа данных, содержащихся в строке.

Столбец DATA1 индексирован. Поставщик контактов всегда использует этот столбец для данных, которые, по его мнению, будут наиболее часто встречающимися в запросах. Например, в строке адреса электронной почты этот столбец содержит фактический адрес электронной почты.

По соглашению столбец DATA15 зарезервирован для хранения данных больших двоичных объектов (BLOB), таких как миниатюры фотографий.

Имена столбцов, специфичные для типа

Для упрощения работы со столбцами для строк определённого типа поставщик контактов также предоставляет константы имён столбцов, специфичные для конкретного типа, определённые в подклассах ContactsContract.CommonDataKinds . Эти константы просто присваивают одному и тому же столбцу другое имя, что упрощает доступ к данным в строке определённого типа.

Например, класс ContactsContract.CommonDataKinds.Email определяет константы имён столбцов, специфичные для конкретного типа, для строки ContactsContract.Data с MIME-типом Email.CONTENT_ITEM_TYPE . Класс содержит константу ADDRESS для столбца адреса электронной почты. Фактическое значение ADDRESS — «data1», что совпадает с общим именем столбца.

Внимание: не добавляйте собственные данные в таблицу ContactsContract.Data , используя строку с одним из предопределенных MIME-типов поставщика. Это может привести к потере данных или сбоям в работе поставщика. Например, не следует добавлять строку с MIME-типом Email.CONTENT_ITEM_TYPE , содержащую имя пользователя вместо адреса электронной почты в столбце DATA1 . Если вы используете собственный MIME-тип для строки, вы можете задать собственные имена столбцов, соответствующие типу, и использовать столбцы по своему усмотрению.

На рисунке 2 показано, как описательные столбцы и столбцы данных отображаются в строке ContactsContract.Data , а также как имена столбцов, специфичные для конкретного типа, «накладываются» на общие имена столбцов.

Как имена столбцов, специфичные для конкретного типа, сопоставляются с именами столбцов общего назначения

Рисунок 2. Имена столбцов, специфичные для типа, и общие имена столбцов.

Классы имен столбцов, специфичные для определенного типа

В таблице 2 перечислены наиболее часто используемые классы имен столбцов, зависящие от типа:

Таблица 2. Классы имен столбцов, специфичные для определенного типа

Класс отображения Тип данных Примечания
ContactsContract.CommonDataKinds.StructuredName Данные имени для необработанного контакта, связанного с этой строкой данных. Необработанный контакт имеет только одну из этих строк.
ContactsContract.CommonDataKinds.Photo Основная фотография необработанного контакта, связанного с этой строкой данных. Необработанный контакт имеет только одну из этих строк.
ContactsContract.CommonDataKinds.Email Адрес электронной почты необработанного контакта, связанного с этой строкой данных. У необработанного контакта может быть несколько адресов электронной почты.
ContactsContract.CommonDataKinds.StructuredPostal Почтовый адрес необработанного контакта, связанного с этой строкой данных. Необработанный контакт может иметь несколько почтовых адресов.
ContactsContract.CommonDataKinds.GroupMembership Идентификатор, который связывает необработанный контакт с одной из групп в поставщике контактов. Группы — это необязательный параметр, определяемый типом и именем учётной записи. Более подробно они описаны в разделе Группы контактов .

Контакты

Поставщик контактов объединяет необработанные строки контактов по всем типам и именам учётных записей, формируя контакт . Это упрощает отображение и изменение всех данных, собранных пользователем для конкретного человека. Поставщик контактов управляет созданием новых строк контактов и агрегацией необработанных контактов с существующей строкой контактов. Ни приложения, ни адаптеры синхронизации не могут добавлять контакты, а некоторые столбцы в строке контактов доступны только для чтения.

Примечание: при попытке добавить контакт в поставщик контактов с помощью метода insert() возникнет исключение UnsupportedOperationException . При попытке обновить столбец, доступный только для чтения, обновление будет проигнорировано.

Поставщик контактов создаёт новый контакт в ответ на добавление нового необработанного контакта, который не соответствует ни одному из существующих контактов. Поставщик также делает это, если данные существующего необработанного контакта изменяются таким образом, что он перестаёт соответствовать контакту, к которому он был ранее прикреплён. Если приложение или адаптер синхронизации создаёт новый необработанный контакт, соответствующий существующему контакту, новый необработанный контакт агрегируется с существующим контактом.

Поставщик контактов связывает строку контакта со строками необработанных контактов с помощью столбца _ID строки контакта в таблице « Contacts . Столбец CONTACT_ID таблицы необработанных контактов ContactsContract.RawContacts содержит значения _ID для строки контакта, связанной с каждой строкой необработанных контактов.

Таблица ContactsContract.Contacts также содержит столбец LOOKUP_KEY , который представляет собой «постоянную» ссылку на строку контакта. Поскольку поставщик контактов автоматически поддерживает контакты, он может изменить значение _ID строки контакта в ответ на агрегацию или синхронизацию. Даже в этом случае URI контента CONTENT_LOOKUP_URI в сочетании с LOOKUP_KEY контакта по-прежнему будет указывать на строку контакта, поэтому вы можете использовать LOOKUP_KEY для управления ссылками на «избранные» контакты и т. д. Этот столбец имеет собственный формат, не связанный с форматом столбца _ID .

На рисунке 3 показано, как три основные таблицы соотносятся друг с другом.

Основные таблицы поставщика контактов

Рисунок 3. Связи таблиц «Контакты», «Необработанные контакты» и «Подробности».

Внимание: если вы публикуете свое приложение в магазине Google Play или если ваше приложение работает на устройстве под управлением Android 10 (уровень API 29) или выше, имейте в виду, что ограниченный набор полей и методов данных контактов устарели.

При указанных условиях система периодически очищает любые значения, записанные в следующие поля данных:

API, используемые для настройки вышеуказанных полей данных, также устарели:

Кроме того, следующие поля больше не возвращают частые контакты. Обратите внимание, что некоторые из этих полей влияют на рейтинг контактов только в том случае, если контакты относятся к определённому типу данных .

Если ваши приложения обращаются к этим полям или API или обновляют их, используйте альтернативные методы. Например, вы можете реализовать определённые сценарии, используя поставщиков частного контента или другие данные, хранящиеся в вашем приложении или внутренних системах.

Чтобы убедиться, что это изменение не повлияет на функциональность вашего приложения, вы можете вручную очистить эти поля данных. Для этого выполните следующую команду ADB на устройстве под управлением Android 4.1 (API уровня 16) или выше:

adb shell content delete \
--uri content://com.android.contacts/contacts/delete_usage

Данные от адаптеров синхронизации

Пользователи вводят данные о контактах непосредственно в устройство, но данные также поступают в поставщик контактов из веб-сервисов через адаптеры синхронизации , которые автоматизируют передачу данных между устройством и сервисами. Адаптеры синхронизации работают в фоновом режиме под управлением системы и вызывают методы ContentResolver для управления данными.

В Android веб-служба, с которой работает адаптер синхронизации, определяется типом учётной записи. Каждый адаптер синхронизации работает с одним типом учётной записи, но может поддерживать несколько имён для этого типа. Типы и имена учётных записей кратко описаны в разделе «Источники необработанных данных о контактах» . Следующие определения содержат более подробную информацию и описывают, как тип и имя учётной записи связаны с адаптерами синхронизации и службами.

Тип счета
Идентифицирует службу, в которой пользователь сохранил данные. В большинстве случаев пользователю необходимо пройти аутентификацию в службе. Например, Google Контакты — это тип учётной записи, идентифицируемый кодом google.com . Это значение соответствует типу учётной записи, используемому AccountManager .
Имя учетной записи
Определяет конкретную учётную запись или имя пользователя для типа учётной записи. Учётные записи Google Контакты аналогичны учётным записям Google, в которых в качестве имени используется адрес электронной почты. Другие сервисы могут использовать имя пользователя, состоящее из одного слова, или числовой идентификатор.

Типы учётных записей не обязательно должны быть уникальными. Пользователь может настроить несколько учётных записей Google Контактов и загрузить их данные в Поставщик контактов. Это может произойти, если у пользователя есть один набор личных контактов для имени личного аккаунта и другой набор для рабочего. Имена учётных записей обычно уникальны. Вместе они определяют конкретный поток данных между Поставщиком контактов и внешней службой.

Если вы хотите перенести данные вашего сервиса в Contacts Provider, вам необходимо написать собственный адаптер синхронизации. Подробнее об этом см. в разделе Адаптеры синхронизации Contacts Provider .

На рисунке 4 показано, как поставщик контактов вписывается в поток данных о людях. В поле «Адаптеры синхронизации» каждый адаптер отмечен типом своей учётной записи.

Поток данных о людях

Рисунок 4. Поток данных поставщика контактов.

Требуемые разрешения

Приложения, желающие получить доступ к поставщику контактов, должны запросить следующие разрешения:

Доступ для чтения к одной или нескольким таблицам
READ_CONTACTS , указанный в AndroidManifest.xml с элементом <uses-permission> как <uses-permission android:name="android.permission.READ_CONTACTS"> .
Доступ для записи к одной или нескольким таблицам
WRITE_CONTACTS , указанный в AndroidManifest.xml с элементом <uses-permission> как <uses-permission android:name="android.permission.WRITE_CONTACTS"> .

Эти разрешения не распространяются на данные профиля пользователя. Профиль пользователя и необходимые для него разрешения обсуждаются в следующем разделе «Профиль пользователя» .

Помните, что данные контактов пользователя являются персональными и конфиденциальными. Пользователи заботятся о своей конфиденциальности, поэтому они не хотят, чтобы приложения собирали данные о них или их контактах. Если им неясно, зачем вам нужно разрешение на доступ к данным их контактов, они могут поставить вашему приложению низкую оценку или просто отказаться от его установки.

Профиль пользователя

Таблица ContactsContract.Contacts содержит одну строку с данными профиля пользователя устройства. Эти данные описывают user устройства, а не один из его контактов. Строка контактов профиля связана со строкой необработанных контактов для каждой системы, использующей профиль. Каждая строка необработанных контактов профиля может содержать несколько строк данных. Константы для доступа к профилю пользователя доступны в классе ContactsContract.Profile .

Доступ к профилю пользователя требует специальных разрешений. Помимо разрешений READ_CONTACTS и WRITE_CONTACTS , необходимых для чтения и записи, для доступа к профилю пользователя требуются разрешения android.Manifest.permission#READ_PROFILE и android.Manifest.permission#WRITE_PROFILE для чтения и записи соответственно.

Помните, что профиль пользователя следует рассматривать как конфиденциальную информацию. Разрешение android.Manifest.permission#READ_PROFILE позволяет получить доступ к персональным данным пользователя устройства. Обязательно укажите пользователю, зачем вам нужны разрешения на доступ к профилю, в описании вашего приложения.

Чтобы получить строку контактов, содержащую профиль пользователя, вызовите метод ContentResolver.query() . Установите URI контента на CONTENT_URI и не указывайте критерии выбора. Вы также можете использовать этот URI контента в качестве базового URI для получения необработанных контактов или данных профиля. Например, этот фрагмент кода извлекает данные профиля:

Котлин

// Sets the columns to retrieve for the user profile
projection = arrayOf(
        ContactsContract.Profile._ID,
        ContactsContract.Profile.DISPLAY_NAME_PRIMARY,
        ContactsContract.Profile.LOOKUP_KEY,
        ContactsContract.Profile.PHOTO_THUMBNAIL_URI
)

// Retrieves the profile from the Contacts Provider
profileCursor = contentResolver.query(
        ContactsContract.Profile.CONTENT_URI,
        projection,
        null,
        null,
        null
)

Ява

// Sets the columns to retrieve for the user profile
projection = new String[]
    {
        Profile._ID,
        Profile.DISPLAY_NAME_PRIMARY,
        Profile.LOOKUP_KEY,
        Profile.PHOTO_THUMBNAIL_URI
    };

// Retrieves the profile from the Contacts Provider
profileCursor =
        getContentResolver().query(
                Profile.CONTENT_URI,
                projection ,
                null,
                null,
                null);

Примечание: Если вы извлекаете несколько строк контактов и хотите определить, является ли одна из них профилем пользователя, проверьте столбец IS_USER_PROFILE этой строки. Значение этого столбца равно «1», если контакт является профилем пользователя.

Метаданные поставщика контактов

Поставщик контактов управляет данными, отслеживающими состояние данных контактов в репозитории. Эти метаданные о репозитории хранятся в различных местах, включая строки таблиц Raw Contacts, Data и Contacts, таблицы ContactsContract.Settings и ContactsContract.SyncState . В следующей таблице показано влияние каждого из этих элементов метаданных:

Таблица 3. Метаданные в поставщике контактов

Стол Столбец Ценности Значение
ContactsContract.RawContacts DIRTY "0" - не изменилось с момента последней синхронизации. Отмечает необработанные контакты, которые были изменены на устройстве и должны быть синхронизированы с сервером. Значение автоматически устанавливается поставщиком контактов при обновлении строки приложениями Android.

Адаптеры синхронизации, изменяющие необработанные контакты или таблицы данных, всегда должны добавлять строку CALLER_IS_SYNCADAPTER к используемому URI контента. Это предотвращает пометку строк поставщиком как «грязных». В противном случае изменения, вносимые адаптером синхронизации, воспринимаются как локальные и отправляются на сервер, даже если именно сервер был источником этих изменений.

«1» — изменено с момента последней синхронизации, необходимо синхронизировать обратно с сервером.
ContactsContract.RawContacts VERSION Номер версии этой строки. Поставщик контактов автоматически увеличивает это значение при каждом изменении строки или связанных с ней данных.
ContactsContract.Data DATA_VERSION Номер версии этой строки. Поставщик контактов автоматически увеличивает это значение при каждом изменении строки данных.
ContactsContract.RawContacts SOURCE_ID Строковое значение, однозначно идентифицирующее этот необработанный контакт в учетной записи, в которой он был создан. Когда адаптер синхронизации создаёт новый необработанный контакт, в этом столбце должен быть указан уникальный идентификатор сервера для этого необработанного контакта. Когда приложение Android создаёт новый необработанный контакт, оно должно оставить этот столбец пустым. Это даёт адаптеру синхронизации сигнал о необходимости создать новый необработанный контакт на сервере и получить значение для SOURCE_ID .

В частности, идентификатор источника должен быть уникальным для каждого типа учетной записи и должен быть стабильным при синхронизациях:

  • Уникальный: каждый необработанный контакт для учётной записи должен иметь свой собственный идентификатор источника. Если вы не будете соблюдать это требование, в приложении «Контакты» возникнут проблемы. Обратите внимание, что два необработанных контакта для одного типа учётной записи могут иметь одинаковый идентификатор источника. Например, необработанный контакт «Томас Хиггинсон» для учётной записи emily.dickinson@gmail.com может иметь тот же идентификатор источника, что и необработанный контакт «Томас Хиггинсон» для учётной записи emilyd@gmail.com .
  • Стабильная версия: Идентификаторы источников являются постоянной частью данных онлайн-сервиса для необработанных контактов. Например, если пользователь очистит хранилище контактов в настройках приложений и повторно синхронизируется, восстановленные необработанные контакты будут иметь те же идентификаторы источников, что и раньше. Если не настроить это принудительно, ярлыки перестанут работать.
ContactsContract.Groups GROUP_VISIBLE «0» — контакты в этой группе не должны отображаться в интерфейсах приложений Android. Этот столбец предназначен для совместимости с серверами, которые позволяют пользователю скрывать контакты в определенных группах.
«1» — контакты в этой группе могут быть видны в интерфейсах приложений.
ContactsContract.Settings UNGROUPED_VISIBLE «0» — для этой учетной записи и типа учетной записи контакты, не принадлежащие ни одной группе, невидимы для пользовательских интерфейсов приложений Android. По умолчанию контакты невидимы, если ни один из их необработанных контактов не принадлежит группе (принадлежность к группе для необработанного контакта определяется одной или несколькими строками ContactsContract.CommonDataKinds.GroupMembership в таблице ContactsContract.Data ). Установив этот флаг в строке таблицы ContactsContract.Settings для типа учётной записи и учётной записи, можно принудительно сделать видимыми контакты без групп. Этот флаг используется, например, для отображения контактов с серверов, не использующих группы.
«1» — для этой учетной записи и типа учетной записи контакты, не принадлежащие ни одной группе, видны пользовательским интерфейсам приложений.
ContactsContract.SyncState (все) Используйте эту таблицу для хранения метаданных вашего адаптера синхронизации. С помощью этой таблицы вы можете постоянно хранить на устройстве состояние синхронизации и другие данные, связанные с синхронизацией.

Контакты Доступ к провайдеру

В этом разделе описываются рекомендации по доступу к данным поставщика контактов с акцентом на следующее:

  • Запросы сущностей.
  • Пакетная модификация.
  • Извлечение и изменение с намерениями.
  • Целостность данных.

Внесение изменений с помощью адаптера синхронизации также более подробно описано в разделе Контакты Поставщик адаптеров синхронизации .

Запрос сущностей

Поскольку таблицы поставщика контактов организованы иерархически, часто бывает полезно извлечь строку и все связанные с ней «дочерние» строки. Например, чтобы отобразить всю информацию о человеке, может потребоваться извлечь все строки ContactsContract.RawContacts для одной строки ContactsContract.Contacts или все строки ContactsContract.CommonDataKinds.Email для одной строки ContactsContract.RawContacts . Для этого поставщик контактов предлагает конструкции сущностей , которые действуют как соединения таблиц в базе данных.

Сущность подобна таблице, состоящей из выбранных столбцов родительской и дочерней таблиц. При запросе сущности вы указываете проекцию и критерии поиска на основе доступных столбцов сущности. Результатом является Cursor , содержащий по одной строке для каждой извлеченной строки дочерней таблицы. Например, если вы запрашиваете ContactsContract.Contacts.Entity имя контакта и все строки ContactsContract.CommonDataKinds.Email для всех необработанных контактов с этим именем, вы получите Cursor содержащий по одной строке для каждой строки ContactsContract.CommonDataKinds.Email .

Сущности упрощают запросы. Используя сущность, вы можете получить все данные о контактах или необработанных контактах одновременно, вместо того, чтобы сначала запрашивать идентификатор из родительской таблицы, а затем запрашивать этот идентификатор из дочерней таблицы. Кроме того, поставщик контактов обрабатывает запрос к сущности в рамках одной транзакции, что гарантирует внутреннюю согласованность полученных данных.

Примечание: Сущность обычно не содержит все столбцы родительской и дочерней таблиц. При попытке работы с именем столбца, которого нет в списке констант имён столбцов для сущности, возникнет Exception .

В следующем фрагменте кода показано, как получить все необработанные строки контактов для контакта. Этот фрагмент является частью более крупного приложения, состоящего из двух действий: «main» и «detail». Основное действие отображает список строк контактов; когда пользователь выбирает одно из них, действие отправляет его идентификатор в действие «detail». Действие «detail» использует ContactsContract.Contacts.Entity для отображения всех строк данных всех необработанных контактов, связанных с выбранным контактом.

Этот фрагмент взят из упражнения «detail»:

Котлин

...
    /*
     * Appends the entity path to the URI. In the case of the Contacts Provider, the
     * expected URI is content://com.google.contacts/#/entity (# is the ID value).
     */
    contactUri = Uri.withAppendedPath(
            contactUri,
            ContactsContract.Contacts.Entity.CONTENT_DIRECTORY
    )

    // Initializes the loader identified by LOADER_ID.
    loaderManager.initLoader(
            LOADER_ID,  // The identifier of the loader to initialize
            null,       // Arguments for the loader (in this case, none)
            this        // The context of the activity
    )

    // Creates a new cursor adapter to attach to the list view
    cursorAdapter = SimpleCursorAdapter(
            this,                       // the context of the activity
            R.layout.detail_list_item,  // the view item containing the detail widgets
            mCursor,                    // the backing cursor
            fromColumns,               // the columns in the cursor that provide the data
            toViews,                   // the views in the view item that display the data
            0)                          // flags

    // Sets the ListView's backing adapter.
    rawContactList.adapter = cursorAdapter
...
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
    /*
     * Sets the columns to retrieve.
     * RAW_CONTACT_ID is included to identify the raw contact associated with the data row.
     * DATA1 contains the first column in the data row (usually the most important one).
     * MIMETYPE indicates the type of data in the data row.
     */
    val projection: Array<String> = arrayOf(
            ContactsContract.Contacts.Entity.RAW_CONTACT_ID,
            ContactsContract.Contacts.Entity.DATA1,
            ContactsContract.Contacts.Entity.MIMETYPE
    )

    /*
     * Sorts the retrieved cursor by raw contact id, to keep all data rows for a single raw
     * contact collated together.
     */
    val sortOrder = "${ContactsContract.Contacts.Entity.RAW_CONTACT_ID} ASC"

    /*
     * Returns a new CursorLoader. The arguments are similar to
     * ContentResolver.query(), except for the Context argument, which supplies the location of
     * the ContentResolver to use.
     */
    return CursorLoader(
            applicationContext, // The activity's context
            contactUri,        // The entity content URI for a single contact
            projection,         // The columns to retrieve
            null,               // Retrieve all the raw contacts and their data rows.
            null,               //
            sortOrder           // Sort by the raw contact ID.
    )
}

Ява

...
    /*
     * Appends the entity path to the URI. In the case of the Contacts Provider, the
     * expected URI is content://com.google.contacts/#/entity (# is the ID value).
     */
    contactUri = Uri.withAppendedPath(
            contactUri,
            ContactsContract.Contacts.Entity.CONTENT_DIRECTORY);

    // Initializes the loader identified by LOADER_ID.
    getLoaderManager().initLoader(
            LOADER_ID,  // The identifier of the loader to initialize
            null,       // Arguments for the loader (in this case, none)
            this);      // The context of the activity

    // Creates a new cursor adapter to attach to the list view
    cursorAdapter = new SimpleCursorAdapter(
            this,                        // the context of the activity
            R.layout.detail_list_item,   // the view item containing the detail widgets
            mCursor,                     // the backing cursor
            fromColumns,                // the columns in the cursor that provide the data
            toViews,                    // the views in the view item that display the data
            0);                          // flags

    // Sets the ListView's backing adapter.
    rawContactList.setAdapter(cursorAdapter);
...
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {

    /*
     * Sets the columns to retrieve.
     * RAW_CONTACT_ID is included to identify the raw contact associated with the data row.
     * DATA1 contains the first column in the data row (usually the most important one).
     * MIMETYPE indicates the type of data in the data row.
     */
    String[] projection =
        {
            ContactsContract.Contacts.Entity.RAW_CONTACT_ID,
            ContactsContract.Contacts.Entity.DATA1,
            ContactsContract.Contacts.Entity.MIMETYPE
        };

    /*
     * Sorts the retrieved cursor by raw contact id, to keep all data rows for a single raw
     * contact collated together.
     */
    String sortOrder =
            ContactsContract.Contacts.Entity.RAW_CONTACT_ID +
            " ASC";

    /*
     * Returns a new CursorLoader. The arguments are similar to
     * ContentResolver.query(), except for the Context argument, which supplies the location of
     * the ContentResolver to use.
     */
    return new CursorLoader(
            getApplicationContext(),  // The activity's context
            contactUri,              // The entity content URI for a single contact
            projection,               // The columns to retrieve
            null,                     // Retrieve all the raw contacts and their data rows.
            null,                     //
            sortOrder);               // Sort by the raw contact ID.
}

После завершения загрузки LoaderManager вызывает обратный вызов метода onLoadFinished() . Одним из входящих аргументов этого метода является Cursor с результатами запроса. В вашем приложении вы можете получить данные из этого Cursor для отображения или дальнейшей работы с ними.

Пакетная модификация

По возможности следует добавлять, обновлять и удалять данные в поставщике контактов в «пакетном режиме», создавая ArrayList объектов ContentProviderOperation и вызывая applyBatch() . Поскольку поставщик контактов выполняет все операции в applyBatch() в одной транзакции, ваши изменения никогда не приведут к несогласованности данных в репозитории контактов. Пакетное изменение также упрощает одновременную вставку необработанного контакта и его подробных данных.

Примечание: Чтобы изменить отдельный необработанный контакт, рассмотрите возможность отправки намерения в приложение «Контакты» устройства, а не обработку изменений в самом приложении. Подробнее об этом см. в разделе «Извлечение и изменение с помощью намерений» .

Пределы текучести

Пакетное изменение, содержащее большое количество операций, может блокировать другие процессы, что в целом негативно скажется на пользовательском опыте. Чтобы организовать все необходимые изменения в минимальное количество отдельных списков и одновременно предотвратить блокировку системы, следует установить точки выхода для одной или нескольких операций. Точка выхода — это объект ContentProviderOperation , значение isYieldAllowed() которого равно true . Когда поставщик контактов достигает точки выхода, он приостанавливает свою работу, чтобы дать возможность другим процессам выполниться, и закрывает текущую транзакцию. При повторном запуске поставщик продолжает работу со следующей операцией в ArrayList и начинает новую транзакцию.

Точки выхода приводят к выполнению более одной транзакции за один вызов applyBatch() . Поэтому следует установить точку выхода для последней операции для набора связанных строк. Например, следует установить точку выхода для последней операции в наборе, которая добавляет строки необработанного контакта и связанные с ним строки данных, или для последней операции для набора строк, связанных с одним контактом.

Точки выхода также являются единицей атомарной операции. Все обращения между двумя точками выхода будут либо успешными, либо неудачными как единое целое. Если точки выхода не установлены, наименьшей атомарной операцией будет весь пакет операций. Использование точек выхода предотвращает снижение производительности системы операциями, одновременно гарантируя атомарность подмножества операций.

Модификация обратных ссылок

При вставке новой строки необработанного контакта и связанных с ней строк данных как набора объектов ContentProviderOperation необходимо связать строки данных со строкой необработанного контакта, вставив значение _ID необработанного контакта в качестве значения RAW_CONTACT_ID . Однако это значение недоступно при создании ContentProviderOperation для строки данных, поскольку вы ещё не применили ContentProviderOperation к строке необработанного контакта. Для решения этой проблемы класс ContentProviderOperation.Builder имеет метод withValueBackReference() . Этот метод позволяет вставить или изменить столбец с результатом предыдущей операции.

Метод withValueBackReference() имеет два аргумента:

key
Ключ пары «ключ-значение». Значением этого аргумента должно быть имя столбца в таблице, которую вы изменяете.
previousResult
Индекс (с нуля) значения в массиве объектов ContentProviderResult из applyBatch() . По мере применения пакетных операций результат каждой операции сохраняется в промежуточном массиве результатов. Значение previousResult — это индекс одного из этих результатов, который извлекается и сохраняется вместе с key . Это позволяет вставить новую запись необработанного контакта и получить значение его _ID , а затем создать обратную ссылку на это значение при добавлении строки ContactsContract.Data .

Весь массив результатов создаётся при первом вызове метода applyBatch() и имеет размер, равный размеру ArrayList предоставленных вами объектов ContentProviderOperation . Однако все элементы массива результатов устанавливаются в null , и если вы попытаетесь сделать обратную ссылку на результат операции, которая ещё не была применена, withValueBackReference() выдаст Exception .

В следующих фрагментах показано, как вставить новый необработанный контакт и данные в пакетном режиме. Они включают код, который устанавливает точку выхода и использует обратную ссылку.

Первый фрагмент кода извлекает контактные данные из пользовательского интерфейса. На этом этапе пользователь уже выбрал учётную запись, для которой необходимо добавить новый необработанный контакт.

Котлин

// Creates a contact entry from the current UI values, using the currently-selected account.
private fun createContactEntry() {
    /*
     * Gets values from the UI
     */
    val name = contactNameEditText.text.toString()
    val phone = contactPhoneEditText.text.toString()
    val email = contactEmailEditText.text.toString()

    val phoneType: String = contactPhoneTypes[mContactPhoneTypeSpinner.selectedItemPosition]

    val emailType: String = contactEmailTypes[mContactEmailTypeSpinner.selectedItemPosition]

Ява

// Creates a contact entry from the current UI values, using the currently-selected account.
protected void createContactEntry() {
    /*
     * Gets values from the UI
     */
    String name = contactNameEditText.getText().toString();
    String phone = contactPhoneEditText.getText().toString();
    String email = contactEmailEditText.getText().toString();

    int phoneType = contactPhoneTypes.get(
            contactPhoneTypeSpinner.getSelectedItemPosition());

    int emailType = contactEmailTypes.get(
            contactEmailTypeSpinner.getSelectedItemPosition());

Следующий фрагмент создает операцию по вставке строки необработанного контакта в таблицу ContactsContract.RawContacts :

Котлин

    /*
     * Prepares the batch operation for inserting a new raw contact and its data. Even if
     * the Contacts Provider does not have any data for this person, you can't add a Contact,
     * only a raw contact. The Contacts Provider will then add a Contact automatically.
     */

    // Creates a new array of ContentProviderOperation objects.
    val ops = arrayListOf<ContentProviderOperation>()

    /*
     * Creates a new raw contact with its account type (server type) and account name
     * (user's account). Remember that the display name is not stored in this row, but in a
     * StructuredName data row. No other data is required.
     */
    var op: ContentProviderOperation.Builder =
            ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
                    .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.name)
                    .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.type)

    // Builds the operation and adds it to the array of operations
    ops.add(op.build())

Ява

    /*
     * Prepares the batch operation for inserting a new raw contact and its data. Even if
     * the Contacts Provider does not have any data for this person, you can't add a Contact,
     * only a raw contact. The Contacts Provider will then add a Contact automatically.
     */

     // Creates a new array of ContentProviderOperation objects.
    ArrayList<ContentProviderOperation> ops =
            new ArrayList<ContentProviderOperation>();

    /*
     * Creates a new raw contact with its account type (server type) and account name
     * (user's account). Remember that the display name is not stored in this row, but in a
     * StructuredName data row. No other data is required.
     */
    ContentProviderOperation.Builder op =
            ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
            .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.getType())
            .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.getName());

    // Builds the operation and adds it to the array of operations
    ops.add(op.build());

Затем код создает строки данных для отображаемого имени, телефона и адреса электронной почты.

Каждый объект конструктора операций использует withValueBackReference() для получения RAW_CONTACT_ID . Ссылка указывает на объект ContentProviderResult из первой операции, которая добавляет строку необработанного контакта и возвращает её новое значение _ID . В результате каждая строка данных автоматически связывается своим RAW_CONTACT_ID с новой строкой ContactsContract.RawContacts , к которой она принадлежит.

Объект ContentProviderOperation.Builder , который добавляет строку электронной почты, помечается с помощью withYieldAllowed() , который устанавливает точку выхода:

Котлин

    // Creates the display name for the new raw contact, as a StructuredName data row.
    op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * withValueBackReference sets the value of the first argument to the value of
             * the ContentProviderResult indexed by the second argument. In this particular
             * call, the raw contact ID column of the StructuredName data row is set to the
             * value of the result returned by the first operation, which is the one that
             * actually adds the raw contact row.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to StructuredName
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)

            // Sets the data row's display name to the name in the UI.
            .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name)

    // Builds the operation and adds it to the array of operations
    ops.add(op.build())

    // Inserts the specified phone number and type as a Phone data row
    op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * Sets the value of the raw contact id column to the new raw contact ID returned
             * by the first operation in the batch.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to Phone
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)

            // Sets the phone number and type
            .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
            .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType)

    // Builds the operation and adds it to the array of operations
    ops.add(op.build())

    // Inserts the specified email and type as a Phone data row
    op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * Sets the value of the raw contact id column to the new raw contact ID returned
             * by the first operation in the batch.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to Email
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)

            // Sets the email address and type
            .withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email)
            .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType)

    /*
     * Demonstrates a yield point. At the end of this insert, the batch operation's thread
     * will yield priority to other threads. Use after every set of operations that affect a
     * single contact, to avoid degrading performance.
     */
    op.withYieldAllowed(true)

    // Builds the operation and adds it to the array of operations
    ops.add(op.build())

Ява

    // Creates the display name for the new raw contact, as a StructuredName data row.
    op =
            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * withValueBackReference sets the value of the first argument to the value of
             * the ContentProviderResult indexed by the second argument. In this particular
             * call, the raw contact ID column of the StructuredName data row is set to the
             * value of the result returned by the first operation, which is the one that
             * actually adds the raw contact row.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to StructuredName
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)

            // Sets the data row's display name to the name in the UI.
            .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name);

    // Builds the operation and adds it to the array of operations
    ops.add(op.build());

    // Inserts the specified phone number and type as a Phone data row
    op =
            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * Sets the value of the raw contact id column to the new raw contact ID returned
             * by the first operation in the batch.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to Phone
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)

            // Sets the phone number and type
            .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
            .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType);

    // Builds the operation and adds it to the array of operations
    ops.add(op.build());

    // Inserts the specified email and type as a Phone data row
    op =
            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * Sets the value of the raw contact id column to the new raw contact ID returned
             * by the first operation in the batch.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to Email
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)

            // Sets the email address and type
            .withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email)
            .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType);

    /*
     * Demonstrates a yield point. At the end of this insert, the batch operation's thread
     * will yield priority to other threads. Use after every set of operations that affect a
     * single contact, to avoid degrading performance.
     */
    op.withYieldAllowed(true);

    // Builds the operation and adds it to the array of operations
    ops.add(op.build());

Последний фрагмент показывает вызов applyBatch() , который вставляет новые необработанные строки контактов и данных.

Котлин

    // Ask the Contacts Provider to create a new contact
    Log.d(TAG, "Selected account: ${mSelectedAccount.name} (${mSelectedAccount.type})")
    Log.d(TAG, "Creating contact: $name")

    /*
     * Applies the array of ContentProviderOperation objects in batch. The results are
     * discarded.
     */
    try {
        contentResolver.applyBatch(ContactsContract.AUTHORITY, ops)
    } catch (e: Exception) {
        // Display a warning
        val txt: String = getString(R.string.contactCreationFailure)
        Toast.makeText(applicationContext, txt, Toast.LENGTH_SHORT).show()

        // Log exception
        Log.e(TAG, "Exception encountered while inserting contact: $e")
    }
}

Ява

    // Ask the Contacts Provider to create a new contact
    Log.d(TAG,"Selected account: " + selectedAccount.getName() + " (" +
            selectedAccount.getType() + ")");
    Log.d(TAG,"Creating contact: " + name);

    /*
     * Applies the array of ContentProviderOperation objects in batch. The results are
     * discarded.
     */
    try {

            getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
    } catch (Exception e) {

            // Display a warning
            Context ctx = getApplicationContext();

            CharSequence txt = getString(R.string.contactCreationFailure);
            int duration = Toast.LENGTH_SHORT;
            Toast toast = Toast.makeText(ctx, txt, duration);
            toast.show();

            // Log exception
            Log.e(TAG, "Exception encountered while inserting contact: " + e);
    }
}

Пакетные операции также позволяют реализовать оптимистичное управление параллелизмом , метод применения транзакций модификации без необходимости блокировать базовый репозиторий. Чтобы использовать этот метод, вы применяете транзакцию, а затем проверяете другие модификации, которые могли быть сделаны одновременно. Если вы обнаружили, что произошла несовместимая модификация, вы откатаетесь от транзакции и повторно ее.

Оптимистическое управление параллелизмом полезно для мобильного устройства, где есть только один пользователь за раз, а одновременный доступ к хранилищу данных редки. Поскольку блокировка не используется, время на установление блокировки или ожидания других транзакций не теряется от времени.

Чтобы использовать оптимистичное управление параллелизмом при обновлении одной ContactsContract.RawContacts строки.

  1. Получите столбец VERSION Raw Contact вместе с другими данными, которые вы получите.
  2. Создайте объект ContentProviderOperation.Builder , подходящий для обеспечения соблюдения ограничения, используя метод newAssertQuery(Uri) . Для URI содержимого используйте RawContacts.CONTENT_URI с _ID к нему необработанного контакта.
  3. Для объекта ContentProviderOperation.Builder вызовите withValue() , чтобы сравнить столбец VERSION с номером версии, который вы только что получили.
  4. Для того же ContentProviderOperation.Builder вызовите withExpectedCount() , чтобы убедиться, что в этом утверждении проверяется только одна строка.
  5. Вызовите build() , чтобы создать объект ContentProviderOperation , затем добавьте этот объект в качестве первого объекта в ArrayList , который вы передаете в applyBatch() .
  6. Примените пакетную транзакцию.

Если необработанная контактная строка обновляется другой операцией между временем, которое вы читаете строку, и временем, которое вы пытаетесь изменить ее, то «Assert» ContentProviderOperation пройдет сбой, и вся партия операций будет отстранена. Затем вы можете попытаться повторить партию или предпринять какое -то другое действие.

Следующий фрагмент демонстрирует, как создать «Assert» ContentProviderOperation после запроса на один необработанный контакт с помощью курсора: CursorLoader :

Котлин

/*
 * The application uses CursorLoader to query the raw contacts table. The system calls this method
 * when the load is finished.
 */
override fun onLoadFinished(loader: Loader<Cursor>, cursor: Cursor) {
    // Gets the raw contact's _ID and VERSION values
    rawContactID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID))
    mVersion = cursor.getInt(cursor.getColumnIndex(SyncColumns.VERSION))
}

...

// Sets up a Uri for the assert operation
val rawContactUri: Uri = ContentUris.withAppendedId(
        ContactsContract.RawContacts.CONTENT_URI,
        rawContactID
)

// Creates a builder for the assert operation
val assertOp: ContentProviderOperation.Builder =
        ContentProviderOperation.newAssertQuery(rawContactUri).apply {
            // Adds the assertions to the assert operation: checks the version
            withValue(SyncColumns.VERSION, mVersion)

            // and count of rows tested
            withExpectedCount(1)
        }

// Creates an ArrayList to hold the ContentProviderOperation objects
val ops = arrayListOf<ContentProviderOperation>()

ops.add(assertOp.build())

// You would add the rest of your batch operations to "ops" here

...

// Applies the batch. If the assert fails, an Exception is thrown
try {
    val results: Array<ContentProviderResult> = contentResolver.applyBatch(AUTHORITY, ops)
} catch (e: OperationApplicationException) {
    // Actions you want to take if the assert operation fails go here
}

Ява

/*
 * The application uses CursorLoader to query the raw contacts table. The system calls this method
 * when the load is finished.
 */
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {

    // Gets the raw contact's _ID and VERSION values
    rawContactID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID));
    mVersion = cursor.getInt(cursor.getColumnIndex(SyncColumns.VERSION));
}

...

// Sets up a Uri for the assert operation
Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactID);

// Creates a builder for the assert operation
ContentProviderOperation.Builder assertOp = ContentProviderOperation.newAssertQuery(rawContactUri);

// Adds the assertions to the assert operation: checks the version and count of rows tested
assertOp.withValue(SyncColumns.VERSION, mVersion);
assertOp.withExpectedCount(1);

// Creates an ArrayList to hold the ContentProviderOperation objects
ArrayList ops = new ArrayList<ContentProviderOperation>;

ops.add(assertOp.build());

// You would add the rest of your batch operations to "ops" here

...

// Applies the batch. If the assert fails, an Exception is thrown
try
    {
        ContentProviderResult[] results =
                getContentResolver().applyBatch(AUTHORITY, ops);

    } catch (OperationApplicationException e) {

        // Actions you want to take if the assert operation fails go here
    }

Поиск и модификация с намерениями

Отправка намерения приложению контактов устройства позволяет косвенному поставщику контактов. Намерение запускает пользовательский интерфейс приложения для контактов устройства, в котором пользователи могут выполнять работу, связанные с контактами. С этим типом доступа пользователи могут:

  • Выберите контакт из списка и вернитесь в ваше приложение для дальнейшей работы.
  • Редактировать данные существующего контакта.
  • Вставьте новый сырой контакт для любого из их счетов.
  • Удалить данные контакта или контакты.

Если пользователь вставляет или обновляет данные, вы можете сначала собрать данные и отправить их как часть намерения.

Когда вы используете намерения, чтобы получить доступ к поставщику контактов через приложение Device's Contacts, вам не нужно писать свой собственный пользовательский интерфейс или код для доступа к поставщику. Вам также не нужно просить разрешения на чтение или запись поставщику. Приложение для контактов устройства может делегировать разрешение на чтение для контакта для вас, и, поскольку вы вносите изменения поставщику через другое приложение, вам не нужно иметь разрешения на запись.

Общий процесс отправки намерения доступа к поставщику подробно описан в Руководстве по поставщику контента в разделе «Доступ к данным через намерения». Действие, тип MIME и значения данных, которые вы используете для доступных задач, суммированы в таблице 4, в то время как значения дополнений, которые вы можете использовать с помощью putExtra() перечислены в справочной документации для ContactsContract.Intents.Insert

Таблица 4. Контакты поставщика намерения.

Задача Действие Данные MIME-тип Примечания
Выберите контакт из списка ACTION_PICK Один из:
  • Contacts.CONTENT_URI , который отображает список контактов.
  • Phone.CONTENT_URI , который отображает список телефонов для необработанного контакта.
  • StructuredPostal.CONTENT_URI , который отображает список почтовых адресов для необработанного контакта.
  • Email.CONTENT_URI , который отображает список адресов электронной почты для необработанного контакта.
Не используется Отображает список необработанных контактов или список данных из необработанного контакта, в зависимости от типа URI контента, который вы поставляете.

Call startActivityForResult() , который возвращает контент URI выбранной строки. Форма URI - это URI контента таблицы с приложением к нему LOOKUP_ID . Приложение Device's Contacts Делегаты читают и записывают разрешения на этот URI контента в течение жизни вашей деятельности. Смотрите Руководство по основам поставщика контента для получения более подробной информации.

Вставьте новый необработанный контакт Insert.ACTION Н/Д RawContacts.CONTENT_TYPE , тип панели для набора необработанных контактов. Отображает экран Contact Application приложения устройства. Отображаются значения дополнений, которые вы добавляете в намерение. Если отправлено с startActivityForResult() , контент URI недавно добавленного необработанного контакта передается обратно в метод onActivityResult() в аргументе Intent , в поле «Данные». Чтобы получить значение, позвоните getData() .
Изменить контакт ACTION_EDIT CONTENT_LOOKUP_URI для контакта. Активность редактора позволит пользователю редактировать любые данные, связанные с этим контактом. Contacts.CONTENT_ITEM_TYPE , один контакт. Отображает экран «Редактировать контакт» в приложении контактов. Отображаются значения дополнений, которые вы добавляете в намерение. Когда пользователь нажимает, чтобы сохранить изменения, ваша деятельность возвращается на передний план.
Отобразить сборщика, который также может добавить данные. ACTION_INSERT_OR_EDIT Н/Д CONTENT_ITEM_TYPE Это намерение всегда отображает экран «Сборщик приложения контактов». Пользователь может либо выбрать контакт для редактирования, либо добавить новый контакт. Либо отображается либо редактирование, либо экран добавления, в зависимости от выбора пользователя, и отображаются дополнительные данные, которые вы передаете в намерении. Если ваше приложение отображает контактные данные, такие как электронная почта или номер телефона, используйте это намерение, чтобы пользователь позволил пользователю добавить данные в существующий контакт. контакт,

Примечание. В дополнениях этого намерения нет необходимости отправлять значение имени, потому что пользователь всегда выбирает существующее имя или добавляет новое. Более того, если вы отправите имя, и пользователь решит сделать редактирование, приложение Contacts отобразит имя, которое вы отправляете, перезаписывая предыдущее значение. Если пользователь не замечает этого и сохраняет редактирование, старое значение теряется.

Приложение Device's Contacts не позволяет вам удалить необработанный контакт или какие -либо данные с намерением. Вместо этого, чтобы удалить необработанный контакт, используйте ContentResolver.delete() или ContentProviderOperation.newDelete() .

Следующий фрагмент показывает, как построить и отправить намерение, которое вставляет новый необработанный контакт и данные:

Котлин

// Gets values from the UI
val name = contactNameEditText.text.toString()
val phone = contactPhoneEditText.text.toString()
val email = contactEmailEditText.text.toString()

val company = companyName.text.toString()
val jobtitle = jobTitle.text.toString()

/*
 * Demonstrates adding data rows as an array list associated with the DATA key
 */

// Defines an array list to contain the ContentValues objects for each row
val contactData = arrayListOf<ContentValues>()

/*
 * Defines the raw contact row
 */

// Sets up the row as a ContentValues object
val rawContactRow = ContentValues().apply {
    // Adds the account type and name to the row
    put(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.type)
    put(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.name)
}

// Adds the row to the array
contactData.add(rawContactRow)

/*
 * Sets up the phone number data row
 */

// Sets up the row as a ContentValues object
val phoneRow = ContentValues().apply {
    // Specifies the MIME type for this data row (all data rows must be marked by their type)
    put(ContactsContract.Data.MIMETYPE,ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)

    // Adds the phone number and its type to the row
    put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
}

// Adds the row to the array
contactData.add(phoneRow)

/*
 * Sets up the email data row
 */

// Sets up the row as a ContentValues object
val emailRow = ContentValues().apply {
    // Specifies the MIME type for this data row (all data rows must be marked by their type)
    put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)

    // Adds the email address and its type to the row
    put(ContactsContract.CommonDataKinds.Email.ADDRESS, email)
}

// Adds the row to the array
contactData.add(emailRow)

// Creates a new intent for sending to the device's contacts application
val insertIntent = Intent(ContactsContract.Intents.Insert.ACTION).apply {
    // Sets the MIME type to the one expected by the insertion activity
    type = ContactsContract.RawContacts.CONTENT_TYPE

    // Sets the new contact name
    putExtra(ContactsContract.Intents.Insert.NAME, name)

    // Sets the new company and job title
    putExtra(ContactsContract.Intents.Insert.COMPANY, company)
    putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle)

    /*
    * Adds the array to the intent's extras. It must be a parcelable object in order to
    * travel between processes. The device's contacts app expects its key to be
    * Intents.Insert.DATA
    */
    putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData)
}

// Send out the intent to start the device's contacts app in its add contact activity.
startActivity(insertIntent)

Ява

// Gets values from the UI
String name = contactNameEditText.getText().toString();
String phone = contactPhoneEditText.getText().toString();
String email = contactEmailEditText.getText().toString();

String company = companyName.getText().toString();
String jobtitle = jobTitle.getText().toString();

// Creates a new intent for sending to the device's contacts application
Intent insertIntent = new Intent(ContactsContract.Intents.Insert.ACTION);

// Sets the MIME type to the one expected by the insertion activity
insertIntent.setType(ContactsContract.RawContacts.CONTENT_TYPE);

// Sets the new contact name
insertIntent.putExtra(ContactsContract.Intents.Insert.NAME, name);

// Sets the new company and job title
insertIntent.putExtra(ContactsContract.Intents.Insert.COMPANY, company);
insertIntent.putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle);

/*
 * Demonstrates adding data rows as an array list associated with the DATA key
 */

// Defines an array list to contain the ContentValues objects for each row
ArrayList<ContentValues> contactData = new ArrayList<ContentValues>();


/*
 * Defines the raw contact row
 */

// Sets up the row as a ContentValues object
ContentValues rawContactRow = new ContentValues();

// Adds the account type and name to the row
rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.getType());
rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.getName());

// Adds the row to the array
contactData.add(rawContactRow);

/*
 * Sets up the phone number data row
 */

// Sets up the row as a ContentValues object
ContentValues phoneRow = new ContentValues();

// Specifies the MIME type for this data row (all data rows must be marked by their type)
phoneRow.put(
        ContactsContract.Data.MIMETYPE,
        ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE
);

// Adds the phone number and its type to the row
phoneRow.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone);

// Adds the row to the array
contactData.add(phoneRow);

/*
 * Sets up the email data row
 */

// Sets up the row as a ContentValues object
ContentValues emailRow = new ContentValues();

// Specifies the MIME type for this data row (all data rows must be marked by their type)
emailRow.put(
        ContactsContract.Data.MIMETYPE,
        ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE
);

// Adds the email address and its type to the row
emailRow.put(ContactsContract.CommonDataKinds.Email.ADDRESS, email);

// Adds the row to the array
contactData.add(emailRow);

/*
 * Adds the array to the intent's extras. It must be a parcelable object in order to
 * travel between processes. The device's contacts app expects its key to be
 * Intents.Insert.DATA
 */
insertIntent.putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData);

// Send out the intent to start the device's contacts app in its add contact activity.
startActivity(insertIntent);

Целостность данных

Поскольку репозиторий контактов содержит важные и конфиденциальные данные, которые пользователи ожидают, будут правильными и актуальными, у поставщика контактов есть четко определенные правила для целостности данных. Вы обязаны соответствовать этим правилам при изменении данных контактов. Важные правила перечислены здесь:

ContactsContract.RawContacts добавляйте ContactsContract.CommonDataKinds.StructuredName .
A ContactsContract.RawContacts Srow без ContactsContract.CommonDataKinds.StructuredName ContactsContract.Data
Всегда связывайте новые ContactsContract.Data ContactsContract.RawContacts
Строка ContactsContract.Data ContactsContract.RawContacts
Измените данные только для тех необработанных контактов, которые у вас есть.
Помните, что поставщик контактов обычно управляет данными из нескольких различных типов учетных записей/онлайн -сервисов. Вы должны убедиться, что ваше приложение только изменяет или удаляет данные для строк, которые вам принадлежат, и что оно только вводит данные с типом учетной записи и именем, которые вы управляете.
Всегда используйте константы, определенные в ContactsContract , и его подклассы для властей, контент URI, пути URI, имена столбцов, типов MIME и значения TYPE .
Использование этих констант помогает избежать ошибок. Вы также будете уведомлены с предупреждениями компилятора, если какая -либо из констант устарел.

Пользовательские строки данных

Создавая и используя свои собственные типы MIME, вы можете вставить, редактировать, удалять и извлекать свои строки данных в таблице ContactsContract.Data . Ваши строки ограничены использованием столбца, определенного в ContactsContract.DataColumns , хотя вы можете сопоставить свои собственные имена столбцов, специфичных для типа с именами столбцов по умолчанию. В приложении контактов устройства данные для ваших строк отображаются, но не могут быть отредактированы или удалены, и пользователи не могут добавить дополнительные данные. Чтобы пользователи могли изменить ваши пользовательские строки данных, вы должны предоставить активность редактора в своем собственном приложении.

Чтобы отобразить свои пользовательские данные, предоставьте файл contacts.xml , содержащий элемент <ContactsAccountType> , и один или несколько его элементов <ContactsDataKind> дочерних элементов. Это описано более подробно в разделе <ContactsDataKind> element .

Чтобы узнать больше о пользовательских типах MIME, прочитайте Руководство по провайдеру контента .

Синхронизированные адаптеры поставщика контактов

Поставщик контактов специально разработан для обработки синхронизации данных контактов между устройством и онлайн -сервисом. Это позволяет пользователям загружать существующие данные на новое устройство и загружать существующие данные в новую учетную запись. Синхронизация также гарантирует, что пользователи имеют последние данные под рукой, независимо от источника дополнений и изменений. Другим преимуществом синхронизации является то, что он предоставляет контактные данные доступными, даже если устройство не подключено к сети.

Хотя вы можете реализовать синхронизацию различными способами, система Android обеспечивает плагин-синхронизацию, которая автоматизирует следующие задачи:

  • Проверка доступности сети.
  • Планирование и выполнение синхронизации на основе предпочтений пользователей.
  • Перезапуск синхронизации, которые остановились.

Чтобы использовать эту структуру, вы поставляете плагин с адаптером синхронизации. Каждый адаптер синхронизации уникален для поставщика услуг и контента, но может обрабатывать несколько имен учетных записей для одной и той же сервиса. Структура также позволяет нескольким адаптерам синхронизации для одной и той же услуги и поставщика.

Синхронизированные классы адаптеров и файлы

Вы реализуете адаптер синхронизации в качестве подкласса AbstractThreadedSyncAdapter и устанавливаете его как часть приложения Android. Система узнает о синхронизированном адаптере из манифеста в вашем приложении, а также из специального XML -файла, на который указывается манифест. Файл XML определяет тип учетной записи для онлайн -сервиса и полномочия для поставщика контента, которые вместе уникально идентифицируют адаптер. Адаптер синхронизации не становится активным, пока пользователь не добавит учетную запись для типа учетной записи адаптера синхронизации и обеспечивает синхронизацию поставщика контента, с помощью адаптера синхронизируется адаптер. На этом этапе система начинает управлять адаптером, называя его по мере необходимости, чтобы синхронизировать поставщик контента и сервер.

ПРИМЕЧАНИЕ. Использование типа учетной записи как часть идентификации адаптера синхронизации позволяет системе обнаруживать и группировать адаптеры синхронизации, которые получают доступ к различным службам от одной и той же организации. Например, синхронизированные адаптеры для онлайн -сервисов Google имеют одинаковый тип учетной записи com.google . Когда пользователи добавляют учетную запись Google в свои устройства, все установленные адаптеры синхронизации для Google Services перечислены вместе; В каждом адаптере синхронизации перечислены синхронизации с различным поставщиком контента на устройстве.

Поскольку большинство сервисов требуют, чтобы пользователи проверяли свою личность перед получением доступа к данным, система Android предлагает структуру аутентификации, которая аналогична и часто используется в сочетании с структурой адаптера синхронизации. В рамках аутентификации используются подлинные подлинные подключения, которые являются подклассами AbstractAccountAuthenticator . Аутентификатор проверяет личность пользователя в следующих шагах:

  1. Собирает имя пользователя, пароль или аналогичную информацию ( учетные данные пользователя).
  2. Отправляет учетные данные в службу
  3. Проверка ответа службы.

Если служба принимает учетные данные, аутентификатор может хранить учетные данные для последующего использования. Из-за плагина Authenticator Framework AccountManager может предоставить доступ к любым Authtokens, которые поддерживает и выбирает для выставки аутентификатора, например, OAuth2 Authtokens.

Хотя аутентификация не требуется, большинство контактов используют ее. Тем не менее, вам не нужно использовать структуру аутентификации Android для выполнения аутентификации.

Реализация адаптера синхронизации

Чтобы реализовать адаптер синхронизации для поставщика контактов, вы начинаете с создания приложения Android, которое содержит следующее:

Service компонент, который отвечает на запросы из системы для привязки к адаптеру синхронизации.
Когда система хочет запустить синхронизацию, она вызывает метод Service's onBind() , чтобы получить IBinder для адаптера синхронизации. Это позволяет системе выполнять перекрестные вызовы в методах адаптера.
Фактический адаптер синхронизации, реализованный как конкретный подкласс AbstractThreadedSyncAdapter .
Этот класс выполняет работу по загрузке данных с сервера, загрузку данных с устройства и разрешением конфликтов. Основная работа адаптера выполняется в методе onPerformSync() . Этот класс должен быть создан как синглтон.
Подкласс Application .
Этот класс выступает в качестве фабрики для синхронизации Singleton. Используйте метод onCreate() для создания экземпляра адаптера синхронизации и предоставьте статический метод «Getter», чтобы вернуть синглтон в метод onBind() службы адаптера синхронизации.
Необязательно: Service компонент, который отвечает на запросы из системы для аутентификации пользователей.
AccountManager начинает эту службу, чтобы начать процесс аутентификации. Метод Service onCreate() интенсирует объект аутентификатора. Когда система хочет аутентифицировать учетную запись пользователя для адаптера приложения, она вызывает метод Service's onBind() , чтобы получить IBinder для аутентификатора. Это позволяет системе выполнять перекрестные вызовы в методах аутентификатора.
Необязательно: конкретный подкласс AbstractAccountAuthenticator , который обрабатывает запросы на аутентификацию.
Этот класс предоставляет методы, которые AccountManager вызывает для аутентификации учетных данных пользователя с сервером. Детали процесса аутентификации сильно различаются, основываясь на использованной технологии сервера. Вам следует обратиться к документации для вашего серверного программного обеспечения, чтобы узнать больше об аутентификации.
XML -файлы, которые определяют адаптер синхронизации и аутентификатор для системы.
Описанные ранее компоненты службы Sync Adapter и Authenticator, определяются в элементах < service > в манифесте приложения. Эти элементы содержат < meta-data > детские элементы, которые предоставляют конкретные данные для системы:
  • Элемент < meta-data > для службы адаптера синхронизации указывает на XML-файл res/xml/syncadapter.xml . В свою очередь, этот файл определяет URI для веб -службы, который будет синхронизироваться с поставщиком контактов, и тип учетной записи для веб -службы.
  • Необязательно: элемент < meta-data > для точек аутентификатора для XML-файла res/xml/authenticator.xml . В свою очередь, этот файл определяет тип учетной записи, который поддерживает этот аутентификатор, а также ресурсы пользовательского интерфейса, которые появляются в процессе аутентификации. Тип учетной записи, указанный в этом элементе, должен быть таким же, как и тип учетной записи, указанный для адаптера синхронизации.

Данные социального потока

Android.provider.contactsContract.StreamItems и Android.provider.contactScontract.StreamIteMphotos Управляют входящими данными из социальных сетей. Вы можете написать синхронизированный адаптер, который добавляет потоковые данные из вашей собственной сети в эти таблицы, или вы можете прочитать данные потока из этих таблиц и отобразить их в своем собственном приложении, или в обоих. С помощью этих функций ваши услуги и приложения для социальных сетей могут быть интегрированы в опыт работы в социальных сетях Android.

Текст социального потока

Потоковые элементы всегда связаны с необработанным контактом. Android.provider.contactsContract.StreamItemScolumns#RAW_CONTACT_ID Ссылки на значение _ID для необработанного контакта. Тип учетной записи и имя учетной записи необработанного контакта также хранятся в строке элемента потока.

Сохраните данные из вашего потока в следующих столбцах:

android.provider.contactscontract.streamitemcolumns#account_type
Требуется Тип учетной записи пользователя для необработанного контакта, связанного с этим элементом потока. Не забудьте установить это значение, когда вы вставляете элемент потока.
android.provider.contactscontract.streamitemcolumns#account_name
Требуется Имя учетной записи пользователя для необработанного контакта, связанного с этим элементом потока. Не забудьте установить это значение, когда вы вставляете элемент потока.
Идентификатор столбцы
Требуется Вы должны вставить следующие столбцы идентификатора при вставке элемента потока:
  • android.provider.contactscontract.streamitemcolumns#contact_id: android.provider.basecolumns#_id Значение контакта, с которым этот элемент потока связан.
  • android.provider.contactscontract.streamitemscolumns#contact_lookup_key: android.provider.contactscontract.contactscolumns#lookup_key значения контакта, с которым этот элемент потока связан.
  • android.provider.contactscontract.streamitemcolumns#raw_contact_id: android.provider.basecolumns#_id Значение необработанного контакта, с которым связан этот элемент потока.
android.provider.contactscontract.streamitemscolumns#комментарии
Необязательный. Хранит сводную информацию, которую вы можете отобразить в начале элемента потока.
android.provider.contactscontract.streamitemcolumns#текст
Текст элемента потока, либо контент, который был опубликован источником элемента, или описание какого -то действия, которое сгенерировало элемент потока. Этот столбец может содержать любые форматирование и встроенные ресурсные изображения, которые могут быть отображены с помощью fromHtml() . Поставщик может усекнуть или обрезать длинный контент, но он будет стараться избегать нарушения тегов.
android.provider.contactscontract.streamitemscolumns#timeStamp
Текстовая строка, содержащая время, когда элемент потока был вставлен или обновлен в виде миллисекундов с момента эпохи. Приложения, которые вставляют или обновляют элементы потока, отвечают за поддержание этого столбца; Он не поддерживается автоматически поставщиком контактов.

Чтобы отобразить идентифицирующую информацию для ваших элементов потока, используйте android.provider.contactscontract.streamitemcolumns#res_icon, android.provider.contactscontract.streamitemcolumns#res_label и android.provider.contactscontract.streamitemcolumns#res_label и android.provider.contactsContract.StreamItemScolumns#res_package to toot to toot to too.

Таблица Android.provider.contactsContract.StreamItems также содержит столбцы android.provider.contactscontract.streamitemscolumns#sync1 через android.provider.contactscontract.streamitemcolumns#sync4 для исключительного использования адаптеров Sync.

Фотографии социального потока

В таблице Android.provider.contactsContract.StreamIteMphotos фотографии, связанные с элементом потока. Таблица Android.provider.contactsContract.StreamIteMphotoscolumns#Stream_item_id Ссылки на значения в столбце _ID of android.provider.contactscontract.streamitems таблица. Ссылки на фото хранятся в таблице в этих столбцах:

Android.provider.contactsContract.StreamIteMPHOTOS#Фото столбца (Blob).
Бинарное представление фотографии, измененное по провайдеру для хранения и отображения. Этот столбец доступен для обратной совместимости с предыдущими версиями поставщика контактов, которые использовали его для хранения фотографий. Однако в текущей версии вы не должны использовать этот столбец для хранения фотографий. Вместо этого используйте android.provider.contactscontract.streamitemphotoscolumns#photo_file_id или android.provider.contactscontract.streamitemphotoscolumns#photo_uri (оба из которых описаны в следующих моментах) для хранения фотографий в файле. В этом столбце теперь содержится миниатюра фотографии, которая доступна для чтения.
android.provider.contactscontract.streamitemphotoscolumns#photo_file_id
Числовой идентификатор фотографии для необработанного контакта. Добавьте это значение к постоянному DisplayPhoto.CONTENT_URI , чтобы получить URI контента, указывающий на один фото -файл, а затем вызовите openAssetFileDescriptor() чтобы получить ручку в фото -файл.
android.provider.contactscontract.streamitemphotoscolumns#photo_uri
Контент URI, указывающий непосредственно на фото -файл для фотографии, представленной этой строкой. Вызовите openAssetFileDescriptor() с этим URI, чтобы получить ручку с фото -файлом.

Использование таблиц социального потока

Эти таблицы работают так же, как и другие основные таблицы в поставщике контактов, за исключением:

  • Эти таблицы требуют дополнительных разрешений на доступ. Чтобы прочитать из них, ваше приложение должно иметь разрешение android.manifest.permission#read_social_stream. Чтобы изменить их, ваше приложение должно иметь разрешение android.manifest.permission#write_social_stream.
  • Для таблицы Android.provider.contactsContract.StreamItems количество строк, хранящихся для каждого необработанного контакта, ограничено. Как только этот предел достигнут, поставщик контактов предоставляет место для новых элементов потока, автоматически удаляя строки, имеющие самую старую Android.provider.contactsContract.streamItemColumns#TimeStamp. Чтобы получить лимит, введите запрос на контент uri android.provider.contactscontract.streamitems#content_limit_uri. Вы можете оставить все аргументы, кроме контента, установленного в null . Запрос возвращает курсор, содержащий одну строку, с одним столбцом Android.provider.contactsContract.StreamItems#max_items.

Класс android.provider.contactscontract.streamitems.streamitemphotos определяет подтел Android.provider.contactsContract.StreamIteMphotos, содержащий фотозвезда для одного элемента потока.

Взаимодействие социального потока

Данные социального потока, управляемые поставщиком контактов, в сочетании с приложением Device's Contacts предлагают мощный способ подключения вашей системы социальных сетей с существующими контактами. Доступны следующие функции:

  • Синхронизируя вашу службу социальных сетей с поставщиком контактов с адаптером Sync, вы можете получить недавнюю деятельность для контактов пользователя и сохранить его в Android.provider.contactScontract.StreamItems и Android.provider.contactsContract.StreamIteMPHOTOS для последующего использования.
  • Помимо регулярной синхронизации, вы можете запустить ваш адаптер синхронизации, чтобы получить дополнительные данные, когда пользователь выбирает контакт для просмотра. Это позволяет вашему адаптеру синхронизации получать фотографии с высоким разрешением и самые последние элементы потока для контакта.
  • Зарегистрировав уведомление с приложением контактов с устройством и поставщиком контактов, вы можете получить намерение при просмотре контакта, и в этот момент обновите статус контакта из вашей службы. Этот подход может быть быстрее и использовать меньшую полосу пропускания, чем полная синхронизация с адаптером синхронизации.
  • Пользователи могут добавить контакт в вашу службу социальных сетей, просматривая контакт в приложении Device's Contacts. Вы включаете это с помощью функции «Пригласить контакт», которую вы включаете с помощью комбинации деятельности, которая добавляет существующий контакт в вашу сеть, и XML -файл, который предоставляет приложение для контактов устройства и поставщик контактов с подробностями вашего приложения.

Регулярная синхронизация элементов потока с поставщиком контактов такая же, как и другие синхронизации. Чтобы узнать больше о синхронизации, см. В разделе «Синхронизированные адаптеры контактов» . Регистрация уведомлений и приглашения контактов рассматриваются в следующих двух разделах.

Регистрация для обработки просмотров социальных сетей

Чтобы зарегистрировать ваш адаптер синхронизации для получения уведомлений, когда пользователь рассматривает контакт, который управляется вашим адаптером синхронизации:

  1. Создайте файл с именем contacts.xml в res/xml/ вашего проекта. Если у вас уже есть этот файл, вы можете пропустить этот шаг.
  2. В этом файле добавьте элемент <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android"> . Если этот элемент уже существует, вы можете пропустить этот шаг.
  3. Чтобы зарегистрировать службу, которая уведомляется, когда пользователь открывает страницу с подробной информацией в контакте в приложении контактов устройства, добавьте атрибут viewContactNotifyService=" serviceclass " в элемент, где serviceclass является полностью квалифицированным классом имени Сервиса, которое должно получить намерение из приложения контактов устройства. Для службы уведомлений используйте класс, который расширяет IntentService , чтобы позволить службе получать намерения. Данные в входящем намерении содержат URI контента необработанного контакта, который нажал пользователь. Из службы уведомлений вы можете привязать, а затем вызвать свой адаптер синхронизации, чтобы обновить данные для необработанного контакта.

Чтобы зарегистрировать действие, которое будет вызвано, когда пользователь нажимает на элемент потока или фото или оба:

  1. Создайте файл с именем contacts.xml в res/xml/ вашего проекта. Если у вас уже есть этот файл, вы можете пропустить этот шаг.
  2. В этом файле добавьте элемент <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android"> . Если этот элемент уже существует, вы можете пропустить этот шаг.
  3. Чтобы зарегистрировать одну из ваших действий, чтобы справиться с пользователем, который щелкнул по элементу потока в приложении контактов устройства, добавьте атрибут viewStreamItemActivity=" activityclass " в элемент, где activityclass -это полностью квалифицированное классное имя деятельности, которое должно получить намерение от приложения контактов устройства.
  4. Чтобы зарегистрировать одну из ваших действий, чтобы справиться с пользователем, нажимая на фотографию потока в приложении контактов устройства, добавьте атрибут viewStreamItemPhotoActivity=" activityclass " в элемент, где activityclass -это полностью квалифицированное классное имя деятельности, которое должно получить намерение от приложения контактов устройства.

Элемент <ContactsAccountType> более подробно описан в разделе <cotitSAccountType> .

Входящее намерение содержит контент URI элемента или фотографию, которую пользователь щелкнул. Чтобы иметь отдельные действия для текстовых элементов и для фотографий, используйте оба атрибута в одном файле.

Взаимодействие с обслуживанием социальных сетей

Пользователям не нужно оставлять приложение Device's Contacts, чтобы пригласить контакт на ваш сайт социальных сетей. Вместо этого вы можете попросить приложение Device Contacts отправить намерение пригласить контакт в одну из ваших действий. Чтобы настроить это:

  1. Создайте файл с именем contacts.xml в res/xml/ вашего проекта. Если у вас уже есть этот файл, вы можете пропустить этот шаг.
  2. В этом файле добавьте элемент <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android"> . Если этот элемент уже существует, вы можете пропустить этот шаг.
  3. Добавьте следующие атрибуты:
    • inviteContactActivity=" activityclass "
    • inviteContactActionLabel="@string/ invite_action_label "
    Значение activityclass -это полностью квалифицированное имя класса деятельности, которое должно получить намерение. Значение invite_action_label - это текстовая строка, отображаемая в меню «Добавить соединение» в приложении контактов устройства.

Примечание. ContactsSource - это устаревшее имя тега для ContactsAccountType .

Contacts.xml Ссылка

File contacts.xml содержит XML -элементы, которые управляют взаимодействием вашего адаптера и приложения Sync с приложением Contacts и поставщиком контактов. Эти элементы описаны в следующих разделах.

<ContactSaccountType> элемент

Элемент <ContactsAccountType> управляет взаимодействием вашего приложения с приложением Contacts. У него следующий синтаксис:

<ContactsAccountType
        xmlns:android="http://schemas.android.com/apk/res/android"
        inviteContactActivity="activity_name"
        inviteContactActionLabel="invite_command_text"
        viewContactNotifyService="view_notify_service"
        viewGroupActivity="group_view_activity"
        viewGroupActionLabel="group_action_text"
        viewStreamItemActivity="viewstream_activity_name"
        viewStreamItemPhotoActivity="viewphotostream_activity_name">

содержится в:

res/xml/contacts.xml

может содержать:

<ContactsDataKind>

Описание:

Объявляет компоненты Android и этикетки пользовательского интерфейса, которые позволяют пользователям приглашать один из своих контактов в социальную сеть, уведомляют пользователей, когда обновляется один из их социальных сетей.

Обратите внимание, что префикс атрибута android: не требуется для атрибутов <ContactsAccountType> .

Атрибуты:

inviteContactActivity
Полностью квалифицированное имя класса деятельности в вашем приложении, которое вы хотите активировать, когда пользователь выбирает Add Connection из приложения Device's Contacts.
inviteContactActionLabel
Текстовая строка, которая отображается для деятельности, указанной в inviteContactActivity , в меню Add Connection . Например, вы можете использовать строку «Следуйте в моей сети». Вы можете использовать идентификатор строкового ресурса для этой метки.
viewContactNotifyService
Полностью квалифицированное название класса в вашем приложении, которое должно получать уведомления, когда пользователь рассматривает контакт. Это уведомление отправляется приложением контактов устройства; Это позволяет вашему приложению откладывать интенсивные данные до тех пор, пока они не будут необходимыми. Например, ваше приложение может ответить на это уведомление, прочитав и отобразив фотографию контакта с высоким разрешением и последние предметы социального потока. Эта функция более подробно описана во взаимодействии социального потока .
viewGroupActivity
Полностью квалифицированное название класса в вашем приложении, которое может отображать информацию группы. Когда пользователь нажимает на метку группы в приложении контактов устройства, отображается пользовательский интерфейс для этой деятельности.
viewGroupActionLabel
Метка, которую приложение Contacts отображает для управления пользовательским интерфейсом, которая позволяет пользователю просматривать группы в вашем приложении.

Для этого атрибута разрешен идентификатор ресурса строкового ресурса.

viewStreamItemActivity
Полностью квалифицированное название класса в вашем приложении, которое приложение Device's Contacts запускает, когда пользователь нажимает элемент потока для необработанного контакта.
viewStreamItemPhotoActivity
Полностью квалифицированное название класса в вашем приложении, которое запускает приложение для контактов устройства, когда пользователь нажимает на фотографию в элементе потока для необработанного контакта.

<ContactSdatakind> элемент

Элемент <ContactsDataKind> управляет отображением пользовательских строк данных вашего приложения в пользовательском интерфейсе приложения Contacts. У него следующий синтаксис:

<ContactsDataKind
        android:mimeType="MIMEtype"
        android:icon="icon_resources"
        android:summaryColumn="column_name"
        android:detailColumn="column_name">

содержится в:

<ContactsAccountType>

Описание:

Используйте этот элемент, чтобы приложение Contacts отображало содержимое пользовательской строки данных как часть деталей необработанного контакта. Каждый <ContactsDataKind> Дочерний элемент <ContactsAccountType> представляет тип пользовательской строки данных, которую ваш адаптер синхронизации добавляет в таблицу ContactsContract.Data . Добавьте один <ContactsDataKind> элемент для каждого пользовательского типа Mime, который вы используете. Вам не нужно добавлять элемент, если у вас есть пользовательская строка данных, для которой вы не хотите отображать данные.

Атрибуты:

android:mimeType
Пользовательский тип MIME, который вы определили для одного из ваших пользовательских типов строк данных в таблице ContactsContract.Data . Например, значение vnd.android.cursor.item/vnd.example.locationstatus может быть пользовательским типом MIME для строки данных, которая записывает последнее местоположение контакта.
android:icon
Ресурс для привлечения Android, который приложение Contacts отображает рядом с вашими данными. Используйте это, чтобы указать пользователю, что данные поступают из вашей службы.
android:summaryColumn
Имя столбца для первого из двух значений, извлеченных из строки данных. Значение отображается как первая строка записи для этой строки данных. Первая строка предназначена для использования в качестве сводки данных, но это необязательно. См. Также Android: Detailcolumn .
android:detailColumn
Имя столбца для второго из двух значений, извлеченных из строки данных. Значение отображается как вторая строка записи для этой строки данных. Смотрите также android:summaryColumn .

Дополнительные функции поставщика контактов

Помимо основных функций, описанных в предыдущих разделах, поставщик контактов предлагает эти полезные функции для работы с данными контактов:

  • Контактные группы
  • Фотографии

Контактные группы

Поставщик контактов может необязательно помечать коллекции связанных контактов с помощью групповых данных. Если сервер, связанный с учетной записью пользователя, хочет поддерживать группы, адаптер синхронизации для типа учетной записи должна передавать данные между поставщиком контактов и сервером. Когда пользователи добавляют новый контакт на сервер, а затем помещают этот контакт в новую группу, адаптер Sync должен добавить новую группу в таблицу ContactsContract.Groups . The group or groups a raw contact belongs to are stored in the ContactsContract.Data table, using the ContactsContract.CommonDataKinds.GroupMembership MIME type.

If you're designing a sync adapter that will add raw contact data from server to the Contacts Provider, and you aren't using groups, then you need to tell the Provider to make your data visible. In the code that is executed when a user adds an account to the device, update the ContactsContract.Settings row that the Contacts Provider adds for the account. In this row, set the value of the Settings.UNGROUPED_VISIBLE column to 1. When you do this, the Contacts Provider will always make your contacts data visible, even if you don't use groups.

Contact photos

The ContactsContract.Data table stores photos as rows with MIME type Photo.CONTENT_ITEM_TYPE . The row's CONTACT_ID column is linked to the _ID column of the raw contact to which it belongs. The class ContactsContract.Contacts.Photo defines a sub-table of ContactsContract.Contacts containing photo information for a contact's primary photo, which is the primary photo of the contact's primary raw contact. Similarly, the class ContactsContract.RawContacts.DisplayPhoto defines a sub-table of ContactsContract.RawContacts containing photo information for a raw contact's primary photo.

The reference documentation for ContactsContract.Contacts.Photo and ContactsContract.RawContacts.DisplayPhoto contain examples of retrieving photo information. There is no convenience class for retrieving the primary thumbnail for a raw contact, but you can send a query to the ContactsContract.Data table, selecting on the raw contact's _ID , the Photo.CONTENT_ITEM_TYPE , and the IS_PRIMARY column to find the raw contact's primary photo row.

Social stream data for a person may also include photos. These are stored in the android.provider.ContactsContract.StreamItemPhotos table, which is described in more detail in the section Social stream photos .