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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Примечания

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

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

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

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

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

  • emily.dickinson@gmail.com
  • emilyd@gmail.com
  • Твиттер-аккаунт "belle_of_amherst"

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

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

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

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

Данные

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Контакты

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

Примечание: Если вы попытаетесь добавить контакт в Contacts Provider с помощью 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

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

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

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

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

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

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

Рисунок 4 показывает, как Contacts Provider вписывается в поток данных о людях. В поле с надписью «адаптеры синхронизации» каждый адаптер помечен типом его учетной записи.

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

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

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

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

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

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

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

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

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

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

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

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

Котлин

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

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

Ява

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

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

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

Контакты Поставщик метаданных

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Следующий фрагмент показывает, как получить все строки необработанных контактов для контакта. Фрагмент является частью более крупного приложения, которое имеет два действия: «main» и «detail». Основное действие показывает список строк контактов; когда пользователь выбирает одно, действие отправляет свой идентификатор в действие detail. Действие detail использует 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 для их отображения или дальнейшей работы с ними.

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

По возможности следует вставлять, обновлять и удалять данные в Contacts Provider в "пакетном режиме", создавая ArrayList объектов ContentProviderOperation и вызывая applyBatch() . Поскольку Contacts Provider выполняет все операции в applyBatch() в одной транзакции, ваши изменения никогда не оставят репозиторий контактов в несогласованном состоянии. Пакетная модификация также облегчает вставку необработанного контакта и его подробных данных одновременно.

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

Точки текучести

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

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

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

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

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

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

key
Ключ пары ключ-значение. Значение этого аргумента должно быть именем столбца в таблице, которую вы изменяете.
previousResult
Индекс значения в массиве объектов ContentProviderResult из applyBatch() , отсчитываемый от 0. По мере применения пакетных операций результат каждой операции сохраняется в промежуточном массиве результатов. Значение 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.Data ContactsContract.RawContacts
Строка ContactsContract.Data ContactsContract.RawContacts
Измените данные только для тех необработанных контактов, которые у вас есть.
Помните, что поставщик контактов обычно управляет данными из нескольких различных типов учетных записей/онлайн -сервисов. Вы должны убедиться, что ваше приложение только изменяет или удаляет данные для строк, которые вам принадлежат, и что оно только вводит данные с типом учетной записи и именем, которые вы управляете.
Всегда используйте константы, определенные в ContactsContract , и его подклассы для властей, контент URI, пути URI, имена столбцов, типов MIME и значения TYPE .
Использование этих констант помогает избежать ошибок. Вы также будете уведомлены с предупреждениями компилятора, если какая -либо из констант устарел.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Contacts.xml Ссылка

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

<ContactSaccountType> элемент

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

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

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

res/xml/contacts.xml

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

<ContactsDataKind>

Описание:

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

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

Атрибуты:

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

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

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

<ContactSdatakind> элемент

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

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

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

<ContactsAccountType>

Описание:

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

Атрибуты:

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

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

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

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

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

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 .