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

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

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

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

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

Контакты Организация-провайдер

Поставщик контактов — это компонент поставщика контента 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 , а учетная запись пользователя для вашей службы — [email protected] , пользователь должен сначала добавить «тип» учетной записи ( com.example.dataservice ) и «имя» учетной записи ( [email protected] ), прежде чем ваше приложение сможет добавлять необработанные строки контактов. Вы можете объяснить это требование пользователю в документации или предложить пользователю добавить тип и имя или и то, и другое. Типы учетных записей и имена учетных записей более подробно описаны в следующем разделе.

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

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

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

Предположим, Эмили Дикинсон открывает окно браузера, входит в Gmail как [email protected] , открывает контакты и добавляет «Томас Хиггинсон». Позже она входит в Gmail как [email protected] и отправляет электронное письмо «Томасу Хиггинсону», который автоматически добавляет его в список контактов. Она также подписана на «colonel_tom» (идентификатор Томаса Хиггинсона) в Твиттере.

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

  1. Необработанный контакт «Томаса Хиггинсона», связанный с [email protected] . Тип учетной записи пользователя — Google.
  2. Второй необработанный контакт «Томаса Хиггинсона», связанный с [email protected] . Тип учетной записи пользователя также Google. Существует второй необработанный контакт, хотя имя идентично предыдущему имени, поскольку человек был добавлен для другой учетной записи пользователя.
  3. Третий необработанный контакт «Томаса Хиггинсона», связанный с «belle_of_amherst». Тип учетной записи пользователя — Twitter.

Данные

Как отмечалось ранее, данные необработанного контакта хранятся в строке ContactsContract.Data , которая связана со значением _ID необработанного контакта. Это позволяет одному необработанному контакту иметь несколько экземпляров данных одного и того же типа, таких как адреса электронной почты или номера телефонов. Например, если «Томас Хиггинсон» для [email protected] (необработанная контактная строка для Томаса Хиггинсона, связанная с учетной записью Google [email protected] ) имеет домашний адрес электронной почты [email protected] и рабочий адрес электронной почты [email protected] поставщик контактов сохраняет две строки адресов электронной почты и связывает их обе с необработанным контактом.

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

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

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

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

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

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

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

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

Имена столбцов для конкретного типа

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

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

Внимание: не добавляйте собственные данные в таблицу ContactsContract.Data , используя строку, имеющую один из предопределенных типов MIME поставщика. Если вы это сделаете, вы можете потерять данные или вызвать сбой в работе провайдера. Например, не следует добавлять в столбец DATA1 строку с MIME-типом Email.CONTENT_ITEM_TYPE , содержащую имя пользователя вместо адреса электронной почты. Если вы используете свой собственный тип 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 Store или если ваше приложение установлено на устройстве под управлением 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 Contacts аналогичны учетным записям Google, в которых в качестве имени учетной записи указан адрес электронной почты. Другие службы могут использовать имя пользователя, состоящее из одного слова, или числовой идентификатор.

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

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

На рис. 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», если контакт является профилем пользователя.

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

Поставщик контактов управляет данными, которые отслеживают состояние данных контактов в репозитории. Эти метаданные о репозитории хранятся в различных местах, включая строки таблицы «Необработанные контакты», «Данные» и «Контакты», таблицу 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 .

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

  • Уникальность: каждый необработанный контакт для учетной записи должен иметь собственный идентификатор источника. Если вы не примените это, вы вызовете проблемы в приложении контактов. Обратите внимание, что два необработанных контакта для одного и того же типа учетной записи могут иметь один и тот же идентификатор источника. Например, необработанный контакт «Томас Хиггинсон» для учетной записи [email protected] может иметь тот же идентификатор источника, что и необработанный контакт «Томас Хиггинсон» для учетной записи [email protected] .
  • Стабильно: идентификаторы источников являются постоянной частью данных онлайн-службы для необработанных контактов. Например, если пользователь удаляет хранилище контактов из настроек приложений и выполняет повторную синхронизацию, восстановленные необработанные контакты должны иметь те же идентификаторы источников, что и раньше. Если вы не примените это, ярлыки перестанут работать.
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 .

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

Этот фрагмент взят из «подробного» действия:

Котлин

...
    /*
     * 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
Индекс значения, отсчитываемый от 0, в массиве объектов 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. Контакты поставщика намерения.

Задача Действие Данные Тип мима Примечания
Выберите контакт из списка 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.RawContacts ContactsContract.Data .
Строка 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. Чтобы зарегистрировать службу, которая уведомляется, когда пользователь открывает страницу с подробной информацией о контакте в приложении Device's Contacts, добавьте атрибут 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 . Группа или группы, к которому принадлежит необработанный контакт, хранятся в таблице ContactsContract.Data , используя ContactsContract.CommonDataKinds.GroupMembership .

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 .