Поставщик календаря – это хранилище мероприятий из календаря пользователя. Calendar Provider API позволяет выполнять запросы, вставлять, обновлять и удалять календари, мероприятия, участников, напоминания и т. д.
Calendar Provider API может использоваться приложениями и адаптерами синхронизации. Правила зависят от типа программы, которая выполняет вызовы. В этом документе основное внимание уделяется использованию Calendar Provider API в качестве приложения. Подробнее об адаптерах синхронизации…
Обычно для чтения или записи данных календаря в манифесте приложения должны быть указаны соответствующие разрешения, описанные в разделе Разрешения пользователя. Чтобы упростить выполнение распространенных операций, поставщик календаря предлагает набор намерений, описанных в разделе Намерения календаря. Эти намерения перенаправляют пользователей в приложение "Календарь", где они могут добавлять, просматривать и редактировать мероприятия. Пользователь взаимодействует с приложением "Календарь", а затем возвращается в исходное приложение. Таким образом, вашему приложению не нужно запрашивать разрешения или предоставлять пользовательский интерфейс для просмотра или создания мероприятий.
Основные требования
Поставщики контента хранят данные и делают их доступными для приложений. Поставщики контента, предлагаемые платформой Android (включая поставщика календаря), обычно предоставляют данные в виде набора таблиц на основе модели реляционной базы данных, где каждая строка является записью, а каждый столбец – данными определенного типа и значения. С помощью Calendar Provider API приложения и адаптеры синхронизации могут получить доступ для чтения и записи к таблицам базы данных, в которых хранятся данные календаря пользователя.
Каждый поставщик контента предоставляет общедоступный URI (в виде объекта Uri), который уникальным образом идентифицирует его набор данных. Поставщик контента, который управляет несколькими наборами данных (несколькими таблицами), предоставляет отдельный URI для каждого из них. Все URI поставщиков начинаются со строки "content://". Это указывает на то, что данные контролируются поставщиком контента. Поставщик календаря определяет константы для URI каждого из своих классов (таблиц). URI имеют формат <class>.CONTENT_URI. Пример: Events.CONTENT_URI.
На рисунке 1 показано графическое представление модели данных поставщика календаря. Она показывает основные таблицы и поля, которые связывают их друг с другом.
Рисунок 1. Модель данных поставщика календаря.
У пользователя может быть несколько календарей, и разные календари могут быть связаны с разными типами аккаунтов (Google Календарь, Exchange и т. д.).
CalendarContract определяет модель данных календаря и информации о мероприятиях. Эти данные хранятся в нескольких таблицах, перечисленных ниже.
| Таблица (класс) | Описание |
|---|---|
| В этой таблице содержится информация о календаре. Каждая строка в этой таблице содержит сведения об одном календаре, такие как название, цвет, информация о синхронизации и т. д. | |
CalendarContract.Events |
В этой таблице содержится информация о событиях. Каждая строка в этой таблице содержит информацию об одном событии, например название, место проведения, время начала, время окончания и т. д. Мероприятие может быть однократным или повторяющимся. Участники, напоминания и расширенные свойства хранятся в отдельных таблицах.
В каждой из них есть столбец EVENT_ID, который ссылается на столбец _ID в таблице Events. |
CalendarContract.Instances |
В этой таблице содержатся время начала и окончания каждого повторения мероприятия. Каждая строка в этой таблице представляет собой одно событие. Для однократных мероприятий сопоставление экземпляров и мероприятий выполняется в отношении 1:1. Для повторяющихся мероприятий автоматически создается несколько строк, соответствующих каждому повторению. |
CalendarContract.Attendees |
В этой таблице содержится информация об участниках (гостях) мероприятия. Каждая строка представляет одного гостя мероприятия. Он указывает тип гостя и его ответ на приглашение. |
CalendarContract.Reminders |
В этой таблице содержатся данные об оповещениях и уведомлениях. Каждая строка представляет собой одно оповещение о событии. Для одного мероприятия можно настроить несколько напоминаний. Максимальное количество напоминаний для одного мероприятия указывается в MAX_REMINDERS, которое задается адаптером синхронизации, владеющим данным календарем. Напоминания указываются в минутах до мероприятия и имеют метод, определяющий, как пользователь будет предупрежден. |
Calendar Provider API – это гибкий и мощный инструмент. При этом важно обеспечить удобство для конечных пользователей и защитить целостность календаря и его данных. При использовании API необходимо учитывать следующее:
- Добавление, обновление и просмотр мероприятий в календаре. Чтобы вставлять, изменять и читать события из поставщика календаря, вам нужны соответствующие разрешения. Однако если вы не создаете полноценное приложение-календарь или адаптер синхронизации, запрашивать эти разрешения не нужно. Вместо этого вы можете использовать намерения, поддерживаемые приложением "Календарь" для Android, чтобы передавать ему операции чтения и записи. При использовании намерений приложение перенаправляет пользователей в Календарь, где они могут выполнить нужное действие в предварительно заполненной форме. После этого пользователь возвращается в ваше приложение. Если вы создадите приложение, которое будет выполнять стандартные операции через Календарь, вы предоставите пользователям надежный и удобный пользовательский интерфейс. Это рекомендуемый подход. Подробнее о намерениях в Календаре…
- Адаптеры синхронизации. Адаптер синхронизации синхронизирует данные календаря на устройстве пользователя с другим сервером или источником данных. В таблицах
CalendarContract.CalendarsиCalendarContract.Eventsесть столбцы, зарезервированные для адаптеров синхронизации. Поставщик и приложения не должны их изменять. Фактически они не видны, если к ним не обращаются как к адаптеру синхронизации. Подробнее об адаптерах синхронизации…
Разрешения пользователей
Чтобы читать данные календаря, приложение должно включать разрешение READ_CALENDAR в файле манифеста. Она должна включать разрешение WRITE_CALENDAR на удаление, вставку и обновление данных календаря:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android"...> <uses-sdk android:minSdkVersion="14" /> <uses-permission android:name="android.permission.READ_CALENDAR" /> <uses-permission android:name="android.permission.WRITE_CALENDAR" /> ... </manifest>
Таблица календарей
Таблица CalendarContract.Calendars содержит сведения об отдельных календарях. В следующие столбцы таблицы Calendars могут записывать данные как приложение, так и адаптер синхронизации.
Полный список поддерживаемых полей приведен в справочнике по CalendarContract.Calendars.
| Константа | Описание |
|---|---|
NAME |
Название календаря. |
CALENDAR_DISPLAY_NAME |
Название календаря, которое видит пользователь. |
VISIBLE |
Логическое значение, указывающее, выбран ли календарь для показа. Значение 0 указывает, что мероприятия, связанные с этим календарем, не должны показываться. Значение 1 указывает, что события, связанные с этим календарем, должны показываться. Это значение влияет на создание строк в таблице CalendarContract.Instances. |
SYNC_EVENTS |
Логическое значение, указывающее, должен ли календарь синхронизироваться и должны ли его мероприятия храниться на устройстве. Значение 0 означает, что календарь не нужно синхронизировать или хранить его мероприятия на устройстве. Значение 1 означает, что мероприятия из этого календаря синхронизируются и хранятся на устройстве. |
Укажите тип аккаунта для всех операций.
Если вы запрашиваете Calendars.ACCOUNT_NAME, то в выборку также необходимо включить Calendars.ACCOUNT_TYPE. Это связано с тем, что аккаунт считается уникальным только при наличии как ACCOUNT_NAME, так и ACCOUNT_TYPE. ACCOUNT_TYPE – это строка, соответствующая аутентификатору аккаунта, который использовался при регистрации аккаунта в AccountManager. Также существует специальный тип аккаунта ACCOUNT_TYPE_LOCAL для календарей, не связанных с аккаунтом устройства.
Аккаунты ACCOUNT_TYPE_LOCAL не синхронизируются.
Запрос к календарю
Ниже приведен пример, показывающий, как получить календари, принадлежащие определенному пользователю. Для простоты в этом примере операция запроса показана в потоке пользовательского интерфейса ("основном потоке"). На практике это следует делать в асинхронном потоке, а не в основном. Подробнее о загрузчиках… Если вы не только читаете данные, но и изменяете их, ознакомьтесь с разделом AsyncQueryHandler.
Kotlin
// Projection array. Creating indices for this array instead of doing // dynamic lookups improves performance. private val EVENT_PROJECTION: Array<String> = arrayOf( CalendarContract.Calendars._ID, // 0 CalendarContract.Calendars.ACCOUNT_NAME, // 1 CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, // 2 CalendarContract.Calendars.OWNER_ACCOUNT // 3 ) // The indices for the projection array above. private const val PROJECTION_ID_INDEX: Int = 0 private const val PROJECTION_ACCOUNT_NAME_INDEX: Int = 1 private const val PROJECTION_DISPLAY_NAME_INDEX: Int = 2 private const val PROJECTION_OWNER_ACCOUNT_INDEX: Int = 3
Java
// Projection array. Creating indices for this array instead of doing // dynamic lookups improves performance. public static final String[] EVENT_PROJECTION = new String[] { Calendars._ID, // 0 Calendars.ACCOUNT_NAME, // 1 Calendars.CALENDAR_DISPLAY_NAME, // 2 Calendars.OWNER_ACCOUNT // 3 }; // The indices for the projection array above. private static final int PROJECTION_ID_INDEX = 0; private static final int PROJECTION_ACCOUNT_NAME_INDEX = 1; private static final int PROJECTION_DISPLAY_NAME_INDEX = 2; private static final int PROJECTION_OWNER_ACCOUNT_INDEX = 3;
В следующей части примера вы создаете запрос. Выборка определяет критерии для запроса. В этом примере запрос ищет календари, у которых ACCOUNT_NAME – hera@example.com, ACCOUNT_TYPE – com.example, а OWNER_ACCOUNT – hera@example.com. Если вы хотите посмотреть все календари, которые просматривал пользователь, а не только те, которыми он владеет, не указывайте OWNER_ACCOUNT.
Запрос возвращает объект Cursor, который можно использовать для обхода набора результатов, возвращенного запросом к базе данных. Подробнее об использовании запросов в поставщиках контента…
Kotlin
// Run query val uri: Uri = CalendarContract.Calendars.CONTENT_URI val selection: String = "((${CalendarContract.Calendars.ACCOUNT_NAME} = ?) AND (" + "${CalendarContract.Calendars.ACCOUNT_TYPE} = ?) AND (" + "${CalendarContract.Calendars.OWNER_ACCOUNT} = ?))" val selectionArgs: Array<String> = arrayOf("hera@example.com", "com.example", "hera@example.com") val cur: Cursor = contentResolver.query(uri, EVENT_PROJECTION, selection, selectionArgs, null)
Java
// Run query Cursor cur = null; ContentResolver cr = getContentResolver(); Uri uri = Calendars.CONTENT_URI; String selection = "((" + Calendars.ACCOUNT_NAME + " = ?) AND (" + Calendars.ACCOUNT_TYPE + " = ?) AND (" + Calendars.OWNER_ACCOUNT + " = ?))"; String[] selectionArgs = new String[] {"hera@example.com", "com.example", "hera@example.com"}; // Submit the query and get a Cursor object back. cur = cr.query(uri, EVENT_PROJECTION, selection, selectionArgs, null);
В следующем разделе курсор используется для пошагового просмотра набора результатов. Для возврата значений каждого поля используются константы, заданные в начале примера.
Kotlin
// Use the cursor to step through the returned records while (cur.moveToNext()) { // Get the field values val calID: Long = cur.getLong(PROJECTION_ID_INDEX) val displayName: String = cur.getString(PROJECTION_DISPLAY_NAME_INDEX) val accountName: String = cur.getString(PROJECTION_ACCOUNT_NAME_INDEX) val ownerName: String = cur.getString(PROJECTION_OWNER_ACCOUNT_INDEX) // Do something with the values... }
Java
// Use the cursor to step through the returned records while (cur.moveToNext()) { long calID = 0; String displayName = null; String accountName = null; String ownerName = null; // Get the field values calID = cur.getLong(PROJECTION_ID_INDEX); displayName = cur.getString(PROJECTION_DISPLAY_NAME_INDEX); accountName = cur.getString(PROJECTION_ACCOUNT_NAME_INDEX); ownerName = cur.getString(PROJECTION_OWNER_ACCOUNT_INDEX); // Do something with the values... ... }
Изменить календарь
Чтобы обновить календарь, укажите его _ID в URI (withAppendedId()) или выберите его в качестве первого элемента. Выбор должен начинаться с "_id=?", а первый selectionArg должен быть _ID календаря.
Вы также можете выполнять обновления, закодировав идентификатор в URI. В примере ниже показано, как изменить отображаемое название календаря с помощью метода withAppendedId():
Kotlin
const val DEBUG_TAG: String = "MyActivity" ... val calID: Long = 2 val values = ContentValues().apply { // The new display name for the calendar put(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, "Trevor's Calendar") } val updateUri: Uri = ContentUris.withAppendedId(CalendarContract.Calendars.CONTENT_URI, calID) val rows: Int = contentResolver.update(updateUri, values, null, null) Log.i(DEBUG_TAG, "Rows updated: $rows")
Java
private static final String DEBUG_TAG = "MyActivity"; ... long calID = 2; ContentValues values = new ContentValues(); // The new display name for the calendar values.put(Calendars.CALENDAR_DISPLAY_NAME, "Trevor's Calendar"); Uri updateUri = ContentUris.withAppendedId(Calendars.CONTENT_URI, calID); int rows = getContentResolver().update(updateUri, values, null, null); Log.i(DEBUG_TAG, "Rows updated: " + rows);
Как вставить календарь
Календари предназначены для управления в основном с помощью адаптера синхронизации, поэтому вы должны вставлять новые календари только в качестве адаптера синхронизации. В большинстве случаев приложения могут вносить в календари только поверхностные изменения, например менять отображаемое название. Если приложению нужно создать локальный календарь, оно может сделать это, выполнив вставку календаря в качестве адаптера синхронизации, используя ACCOUNT_TYPE из ACCOUNT_TYPE_LOCAL.
ACCOUNT_TYPE_LOCAL
– это специальный тип аккаунта для календарей, которые не связаны с аккаунтом устройства. Календари этого типа не синхронизируются с сервером. Подробнее об адаптерах синхронизации…
Таблица событий
Таблица CalendarContract.Events содержит подробные сведения об отдельных событиях. Чтобы добавлять, обновлять или удалять мероприятия, приложение должно включать разрешение WRITE_CALENDAR в файле манифеста.
Следующие столбцы таблицы Events доступны для записи как приложению, так и адаптеру синхронизации. Полный список поддерживаемых полей приведен в справочнике по CalendarContract.Events.
| Константа | Описание |
|---|---|
CALENDAR_ID |
_ID календаря, к которому относится мероприятие. |
ORGANIZER |
Адрес электронной почты организатора (владельца) мероприятия. |
TITLE |
Название мероприятия. |
EVENT_LOCATION |
Место проведения мероприятия. |
DESCRIPTION |
Описание мероприятия. |
DTSTART |
Время начала события в миллисекундах UTC с начала эпохи. |
DTEND |
Время окончания мероприятия в миллисекундах UTC с начала эпохи. |
EVENT_TIMEZONE |
Часовой пояс события. |
EVENT_END_TIMEZONE |
Часовой пояс времени окончания мероприятия. |
DURATION |
Продолжительность мероприятия в формате RFC5545.
Например, значение "PT1H" указывает, что мероприятие должно длиться один час, а значение "P2W" – две недели. |
ALL_DAY |
Значение 1 указывает, что мероприятие занимает весь день, как определено местным часовым поясом. Значение 0 указывает на то, что это обычное мероприятие, которое может начаться и закончиться в любое время в течение дня. |
RRULE |
Правило повторения для формата мероприятия. Пример: "FREQ=WEEKLY;COUNT=10;WKST=SU". Подробнее… |
RDATE |
Даты повторения мероприятия.
Обычно RDATE используется вместе с RRULE, чтобы определить набор повторяющихся событий. Дополнительную информацию можно найти в спецификации RFC5545. |
AVAILABILITY |
Указывает, считается ли это мероприятие занятым временем или свободным временем, которое можно запланировать. |
GUESTS_CAN_MODIFY |
Могут ли гости изменять мероприятие. |
GUESTS_CAN_INVITE_OTHERS |
Могут ли гости приглашать других гостей. |
GUESTS_CAN_SEE_GUESTS |
Могут ли гости видеть список участников. |
Добавить события
Когда ваше приложение добавляет новое мероприятие, мы рекомендуем использовать INSERT Intent, как описано в разделе Использование Intent для добавления мероприятия. Однако при необходимости вы можете вставлять события напрямую. В этом разделе описывается, как это сделать.
Вот правила вставки нового события:
- Необходимо добавить свойства
CALENDAR_IDиDTSTART. - Необходимо добавить
EVENT_TIMEZONE. Чтобы получить список идентификаторов часовых поясов, установленных в системе, используйтеgetAvailableIDs(). Обратите внимание, что это правило не применяется, если вы вставляете мероприятие с помощью намеренияINSERT, описанного в разделе Использование намерения для вставки мероприятия. В этом случае используется часовой пояс по умолчанию. - Для неповторяющихся мероприятий необходимо указать
DTEND. - Для повторяющихся мероприятий необходимо указать
DURATIONв дополнение кRRULEилиRDATE. Обратите внимание, что это правило не применяется, если вы вставляете мероприятие с помощью намеренияINSERT, описанного в разделе Использование намерения для вставки мероприятия. В этом случае вы можете использоватьRRULEв сочетании сDTSTARTиDTEND, и приложение "Календарь" автоматически преобразует его в продолжительность.
Ниже приведен пример вставки мероприятия. Для простоты это выполняется в потоке UI. На практике вставки и обновления должны выполняться в асинхронном потоке, чтобы перенести действие в фоновый поток. Дополнительную информацию можно найти по адресу AsyncQueryHandler.
Kotlin
val calID: Long = 3 val startMillis: Long = Calendar.getInstance().run { set(2012, 9, 14, 7, 30) timeInMillis } val endMillis: Long = Calendar.getInstance().run { set(2012, 9, 14, 8, 45) timeInMillis } ... val values = ContentValues().apply { put(CalendarContract.Events.DTSTART, startMillis) put(CalendarContract.Events.DTEND, endMillis) put(CalendarContract.Events.TITLE, "Jazzercise") put(CalendarContract.Events.DESCRIPTION, "Group workout") put(CalendarContract.Events.CALENDAR_ID, calID) put(CalendarContract.Events.EVENT_TIMEZONE, "America/Los_Angeles") } val uri: Uri = contentResolver.insert(CalendarContract.Events.CONTENT_URI, values) // get the event ID that is the last element in the Uri val eventID: Long = uri.lastPathSegment.toLong() // // ... do something with event ID // //
Java
long calID = 3; long startMillis = 0; long endMillis = 0; Calendar beginTime = Calendar.getInstance(); beginTime.set(2012, 9, 14, 7, 30); startMillis = beginTime.getTimeInMillis(); Calendar endTime = Calendar.getInstance(); endTime.set(2012, 9, 14, 8, 45); endMillis = endTime.getTimeInMillis(); ... ContentResolver cr = getContentResolver(); ContentValues values = new ContentValues(); values.put(Events.DTSTART, startMillis); values.put(Events.DTEND, endMillis); values.put(Events.TITLE, "Jazzercise"); values.put(Events.DESCRIPTION, "Group workout"); values.put(Events.CALENDAR_ID, calID); values.put(Events.EVENT_TIMEZONE, "America/Los_Angeles"); Uri uri = cr.insert(Events.CONTENT_URI, values); // get the event ID that is the last element in the Uri long eventID = Long.parseLong(uri.getLastPathSegment()); // // ... do something with event ID // //
Примечание. Обратите внимание, как в этом примере идентификатор события фиксируется после его создания. Это самый простой способ получить идентификатор мероприятия. Идентификатор мероприятия часто требуется для выполнения других операций в календаре, например для добавления участников или напоминаний.
Обновления
Если вы хотите разрешить пользователям редактировать мероприятия, рекомендуем использовать намерение EDIT, как описано в разделе Использование намерения для редактирования мероприятия.
Однако при необходимости вы можете редактировать события напрямую. Чтобы обновить событие, укажите его _ID в URI (withAppendedId()) или в качестве первого элемента выбора.
Выделение должно начинаться с "_id=?", а первым selectionArg должно быть _ID события. Вы также можете вносить изменения, используя выборку без идентификатора. Вот пример обновления
события. Оно меняет название мероприятия, используя подход withAppendedId():
Kotlin
val DEBUG_TAG = "MyActivity" ... val eventID: Long = 188 ... val values = ContentValues().apply { // The new title for the event put(CalendarContract.Events.TITLE, "Kickboxing") } val updateUri: Uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventID) val rows: Int = contentResolver.update(updateUri, values, null, null) Log.i(DEBUG_TAG, "Rows updated: $rows")
Java
private static final String DEBUG_TAG = "MyActivity"; ... long eventID = 188; ... ContentResolver cr = getContentResolver(); ContentValues values = new ContentValues(); Uri updateUri = null; // The new title for the event values.put(Events.TITLE, "Kickboxing"); updateUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID); int rows = cr.update(updateUri, values, null, null); Log.i(DEBUG_TAG, "Rows updated: " + rows);
Удалить мероприятия
Вы можете удалить событие, указав его _ID в качестве добавленного идентификатора в URI или используя стандартный выбор. Если вы используете добавленный идентификатор, то не сможете сделать выбор.
Существует две версии удаления: как приложение и как адаптер синхронизации. При удалении приложения в столбце deleted устанавливается значение 1. Этот флаг сообщает адаптеру синхронизации, что строка была удалена и это удаление должно быть распространено на сервер. При удалении адаптера синхронизации мероприятие удаляется из базы данных вместе со всеми связанными с ним данными. Ниже приведен пример того, как приложение удаляет мероприятие с помощью _ID:
Kotlin
val DEBUG_TAG = "MyActivity" ... val eventID: Long = 201 ... val deleteUri: Uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventID) val rows: Int = contentResolver.delete(deleteUri, null, null) Log.i(DEBUG_TAG, "Rows deleted: $rows")
Java
private static final String DEBUG_TAG = "MyActivity"; ... long eventID = 201; ... ContentResolver cr = getContentResolver(); Uri deleteUri = null; deleteUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID); int rows = cr.delete(deleteUri, null, null); Log.i(DEBUG_TAG, "Rows deleted: " + rows);
Таблица участников
Каждая строка таблицы CalendarContract.Attendees представляет одного участника или гостя мероприятия. Вызов query() возвращает список участников мероприятия с заданным EVENT_ID.
Значение EVENT_ID должно соответствовать значению _ID определенного события.
В таблице ниже перечислены поля, доступные для записи. При добавлении нового участника необходимо указать все поля, кроме ATTENDEE_NAME.
| Константа | Описание |
|---|---|
EVENT_ID |
Идентификатор мероприятия. |
ATTENDEE_NAME |
Имя участника. |
ATTENDEE_EMAIL |
Адрес электронной почты участника. |
ATTENDEE_RELATIONSHIP |
Отношение участника к мероприятию. Один из следующих вариантов: |
ATTENDEE_TYPE |
Тип участника. Один из следующих вариантов: |
ATTENDEE_STATUS |
Статус участия участника. Один из следующих вариантов: |
Добавить участников
Ниже приведен пример добавления одного участника в мероприятие. Обратите внимание, что параметр EVENT_ID является обязательным.
Kotlin
val eventID: Long = 202 ... val values = ContentValues().apply { put(CalendarContract.Attendees.ATTENDEE_NAME, "Trevor") put(CalendarContract.Attendees.ATTENDEE_EMAIL, "trevor@example.com") put( CalendarContract.Attendees.ATTENDEE_RELATIONSHIP, CalendarContract.Attendees.RELATIONSHIP_ATTENDEE ) put(CalendarContract.Attendees.ATTENDEE_TYPE, CalendarContract.Attendees.TYPE_OPTIONAL) put( CalendarContract.Attendees.ATTENDEE_STATUS, CalendarContract.Attendees.ATTENDEE_STATUS_INVITED ) put(CalendarContract.Attendees.EVENT_ID, eventID) } val uri: Uri = contentResolver.insert(CalendarContract.Attendees.CONTENT_URI, values)
Java
long eventID = 202; ... ContentResolver cr = getContentResolver(); ContentValues values = new ContentValues(); values.put(Attendees.ATTENDEE_NAME, "Trevor"); values.put(Attendees.ATTENDEE_EMAIL, "trevor@example.com"); values.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE); values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_OPTIONAL); values.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_INVITED); values.put(Attendees.EVENT_ID, eventID); Uri uri = cr.insert(Attendees.CONTENT_URI, values);
Таблица напоминаний
Каждая строка таблицы CalendarContract.Reminders представляет собой одно напоминание о мероприятии. Вызов query() возвращает список напоминаний для мероприятия с заданным EVENT_ID.
В таблице ниже перечислены поля напоминаний, доступные для записи. Все они должны быть включены при вставке нового напоминания. Обратите внимание, что адаптеры синхронизации указывают типы поддерживаемых напоминаний в таблице CalendarContract.Calendars. Подробности: ALLOWED_REMINDERS.
| Константа | Описание |
|---|---|
EVENT_ID |
Идентификатор мероприятия. |
MINUTES |
За сколько минут до мероприятия должно сработать напоминание. |
METHOD |
Способ оповещения, заданный на сервере. Один из следующих вариантов: |
Напоминания
В этом примере в мероприятие добавляется напоминание. Напоминание срабатывает за 15 минут до мероприятия.
Kotlin
val eventID: Long = 221 ... val values = ContentValues().apply { put(CalendarContract.Reminders.MINUTES, 15) put(CalendarContract.Reminders.EVENT_ID, eventID) put(CalendarContract.Reminders.METHOD, CalendarContract.Reminders.METHOD_ALERT) } val uri: Uri = contentResolver.insert(CalendarContract.Reminders.CONTENT_URI, values)
Java
long eventID = 221; ... ContentResolver cr = getContentResolver(); ContentValues values = new ContentValues(); values.put(Reminders.MINUTES, 15); values.put(Reminders.EVENT_ID, eventID); values.put(Reminders.METHOD, Reminders.METHOD_ALERT); Uri uri = cr.insert(Reminders.CONTENT_URI, values);
Таблица экземпляров
Таблица CalendarContract.Instances содержит время начала и окончания событий. Каждая строка в этой таблице
представляет собой одно событие. Таблица экземпляров доступна только для чтения и позволяет запрашивать информацию о событиях.
В таблице ниже перечислены некоторые поля, по которым можно выполнять запросы для экземпляра. Обратите внимание, что часовой пояс определяется параметрами KEY_TIMEZONE_TYPE и KEY_TIMEZONE_INSTANCES.
| Константа | Описание |
|---|---|
BEGIN |
Время начала экземпляра в миллисекундах UTC. |
END |
Время окончания экземпляра в миллисекундах UTC. |
END_DAY |
Юлианский день окончания экземпляра относительно часового пояса календаря. |
END_MINUTE |
Минута окончания экземпляра, отсчитываемая от полуночи в часовом поясе календаря. |
EVENT_ID |
_ID события для этого экземпляра. |
START_DAY |
Юлианский день начала экземпляра относительно часового пояса календаря. |
START_MINUTE |
Минута начала экземпляра, отсчитываемая от полуночи, относительно часового пояса календаря. |
Как отправить запрос к таблице экземпляров
Чтобы отправить запрос к таблице Instances, необходимо указать в URI диапазон времени. В этом примере CalendarContract.Instances получает доступ к полю TITLE через реализацию интерфейса CalendarContract.EventsColumns.
Другими словами, TITLE возвращается через представление базы данных, а не через запрос к необработанной таблице CalendarContract.Instances.
Kotlin
const val DEBUG_TAG: String = "MyActivity" val INSTANCE_PROJECTION: Array<String> = arrayOf( CalendarContract.Instances.EVENT_ID, // 0 CalendarContract.Instances.BEGIN, // 1 CalendarContract.Instances.TITLE // 2 ) // The indices for the projection array above. const val PROJECTION_ID_INDEX: Int = 0 const val PROJECTION_BEGIN_INDEX: Int = 1 const val PROJECTION_TITLE_INDEX: Int = 2 // Specify the date range you want to search for recurring // event instances val startMillis: Long = Calendar.getInstance().run { set(2011, 9, 23, 8, 0) timeInMillis } val endMillis: Long = Calendar.getInstance().run { set(2011, 10, 24, 8, 0) timeInMillis } // The ID of the recurring event whose instances you are searching // for in the Instances table val selection: String = "${CalendarContract.Instances.EVENT_ID} = ?" val selectionArgs: Array<String> = arrayOf("207") // Construct the query with the desired date range. val builder: Uri.Builder = CalendarContract.Instances.CONTENT_URI.buildUpon() ContentUris.appendId(builder, startMillis) ContentUris.appendId(builder, endMillis) // Submit the query val cur: Cursor = contentResolver.query( builder.build(), INSTANCE_PROJECTION, selection, selectionArgs, null ) while (cur.moveToNext()) { // Get the field values val eventID: Long = cur.getLong(PROJECTION_ID_INDEX) val beginVal: Long = cur.getLong(PROJECTION_BEGIN_INDEX) val title: String = cur.getString(PROJECTION_TITLE_INDEX) // Do something with the values. Log.i(DEBUG_TAG, "Event: $title") val calendar = Calendar.getInstance().apply { timeInMillis = beginVal } val formatter = SimpleDateFormat("MM/dd/yyyy") Log.i(DEBUG_TAG, "Date: ${formatter.format(calendar.time)}") }
Java
private static final String DEBUG_TAG = "MyActivity"; public static final String[] INSTANCE_PROJECTION = new String[] { Instances.EVENT_ID, // 0 Instances.BEGIN, // 1 Instances.TITLE // 2 }; // The indices for the projection array above. private static final int PROJECTION_ID_INDEX = 0; private static final int PROJECTION_BEGIN_INDEX = 1; private static final int PROJECTION_TITLE_INDEX = 2; ... // Specify the date range you want to search for recurring // event instances Calendar beginTime = Calendar.getInstance(); beginTime.set(2011, 9, 23, 8, 0); long startMillis = beginTime.getTimeInMillis(); Calendar endTime = Calendar.getInstance(); endTime.set(2011, 10, 24, 8, 0); long endMillis = endTime.getTimeInMillis(); Cursor cur = null; ContentResolver cr = getContentResolver(); // The ID of the recurring event whose instances you are searching // for in the Instances table String selection = Instances.EVENT_ID + " = ?"; String[] selectionArgs = new String[] {"207"}; // Construct the query with the desired date range. Uri.Builder builder = Instances.CONTENT_URI.buildUpon(); ContentUris.appendId(builder, startMillis); ContentUris.appendId(builder, endMillis); // Submit the query cur = cr.query(builder.build(), INSTANCE_PROJECTION, selection, selectionArgs, null); while (cur.moveToNext()) { String title = null; long eventID = 0; long beginVal = 0; // Get the field values eventID = cur.getLong(PROJECTION_ID_INDEX); beginVal = cur.getLong(PROJECTION_BEGIN_INDEX); title = cur.getString(PROJECTION_TITLE_INDEX); // Do something with the values. Log.i(DEBUG_TAG, "Event: " + title); Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(beginVal); DateFormat formatter = new SimpleDateFormat("MM/dd/yyyy"); Log.i(DEBUG_TAG, "Date: " + formatter.format(calendar.getTime())); } }
Намерения в Календаре
Вашему приложению не нужны разрешения на чтение и запись данных календаря. Вместо этого оно может использовать намерения, поддерживаемые приложением "Календарь" для Android, чтобы передавать ему операции чтения и записи. В таблице ниже перечислены намерения, поддерживаемые поставщиком календаря.
| Действие | URI | Описание | Дополнительный контент |
|---|---|---|---|
VIEW |
CalendarContract.CONTENT_URI.
Пример использования этого намерения приведен в разделе Использование намерений для просмотра данных календаря.
|
Открыть календарь на времени, указанном в <ms_since_epoch>. |
Нет. |
Events.CONTENT_URI.
Пример использования этого намерения приведен в разделе Использование намерений для просмотра данных календаря.
|
Посмотреть мероприятие, указанное в <event_id>. |
CalendarContract.EXTRA_EVENT_BEGIN_TIMECalendarContract.EXTRA_EVENT_END_TIME |
|
EDIT |
Events.CONTENT_URI.
Пример использования этого намерения приведен в разделе Использование намерения для изменения мероприятия.
|
Изменить мероприятие, указанное в <event_id>. |
CalendarContract.EXTRA_EVENT_BEGIN_TIMECalendarContract.EXTRA_EVENT_END_TIME |
EDIT INSERT |
Events.CONTENT_URI.
Пример использования намерения приведен в разделе Использование намерения для вставки мероприятия.
|
Создайте мероприятие. | Любые дополнительные услуги, перечисленные в таблице ниже. |
В таблице ниже перечислены дополнительные параметры намерений, поддерживаемые поставщиком календаря.
| Intent Extra | Описание |
|---|---|
Events.TITLE |
Название события. |
CalendarContract.EXTRA_EVENT_BEGIN_TIME |
Время начала события в миллисекундах с начала эпохи. |
CalendarContract.EXTRA_EVENT_END_TIME |
Время окончания события в миллисекундах с начала эпохи. |
CalendarContract.EXTRA_EVENT_ALL_DAY |
Логическое значение, указывающее, что мероприятие длится весь день. Возможные значения: true или false. |
Events.EVENT_LOCATION |
Место проведения. |
Events.DESCRIPTION |
Описание мероприятия. |
Intent.EXTRA_EMAIL |
Адреса электронной почты пользователей, которых нужно пригласить, в виде списка, разделенного запятыми. |
Events.RRULE |
Правило повторения для мероприятия. |
Events.ACCESS_LEVEL |
Является ли мероприятие личным или общедоступным. |
Events.AVAILABILITY |
Указывает, считается ли это мероприятие занятым временем или свободным временем, которое можно запланировать. |
В разделах ниже описано, как использовать эти намерения.
Как использовать намерение для вставки события
Используя намерение INSERT, ваше приложение может передать задачу по добавлению мероприятия самому Календарю.
При таком подходе в файле манифеста приложения даже не нужно указывать разрешение WRITE_CALENDAR.
Когда пользователи запускают приложение, использующее этот подход, оно перенаправляет их в Календарь, чтобы завершить добавление мероприятия. Намерение INSERT использует дополнительные поля, чтобы автоматически заполнить форму сведениями о мероприятии из Календаря. Пользователи могут отменить мероприятие, изменить форму или сохранить мероприятие в календаре.
Ниже приведен фрагмент кода, который планирует мероприятие на 19 января 2012 года с 7:30 до 8:30. Обратите внимание на следующее:
- В качестве URI указывается
Events.CONTENT_URI. - Для автоматического заполнения формы временем события используются дополнительные поля
CalendarContract.EXTRA_EVENT_BEGIN_TIMEиCalendarContract.EXTRA_EVENT_END_TIME. Значения времени должны быть указаны в миллисекундах UTC с начала эпохи. - В нем используется дополнительное поле
Intent.EXTRA_EMAIL, в котором содержится список приглашенных, разделенный запятыми и заданный адресами электронной почты.
Kotlin
val startMillis: Long = Calendar.getInstance().run { set(2012, 0, 19, 7, 30) timeInMillis } val endMillis: Long = Calendar.getInstance().run { set(2012, 0, 19, 8, 30) timeInMillis } val intent = Intent(Intent.ACTION_INSERT) .setData(CalendarContract.Events.CONTENT_URI) .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, startMillis) .putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endMillis) .putExtra(CalendarContract.Events.TITLE, "Yoga") .putExtra(CalendarContract.Events.DESCRIPTION, "Group class") .putExtra(CalendarContract.Events.EVENT_LOCATION, "The gym") .putExtra(CalendarContract.Events.AVAILABILITY, CalendarContract.Events.AVAILABILITY_BUSY) .putExtra(Intent.EXTRA_EMAIL, "rowan@example.com,trevor@example.com") startActivity(intent)
Java
Calendar beginTime = Calendar.getInstance(); beginTime.set(2012, 0, 19, 7, 30); Calendar endTime = Calendar.getInstance(); endTime.set(2012, 0, 19, 8, 30); Intent intent = new Intent(Intent.ACTION_INSERT) .setData(Events.CONTENT_URI) .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, beginTime.getTimeInMillis()) .putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endTime.getTimeInMillis()) .putExtra(Events.TITLE, "Yoga") .putExtra(Events.DESCRIPTION, "Group class") .putExtra(Events.EVENT_LOCATION, "The gym") .putExtra(Events.AVAILABILITY, Events.AVAILABILITY_BUSY) .putExtra(Intent.EXTRA_EMAIL, "rowan@example.com,trevor@example.com"); startActivity(intent);
Как изменить событие с помощью намерения
Вы можете изменить мероприятие напрямую, как описано в разделе Изменение мероприятий. Но использование намерения EDIT позволяет приложению, у которого нет разрешения, передавать редактирование мероприятий приложению "Календарь".
После того как пользователь закончит редактировать мероприятие в Календаре, он вернется в исходное приложение.
Ниже приведен пример намерения, которое задает новое название для указанного мероприятия и позволяет пользователям редактировать его в Календаре.
Kotlin
val eventID: Long = 208 val uri: Uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventID) val intent = Intent(Intent.ACTION_EDIT) .setData(uri) .putExtra(CalendarContract.Events.TITLE, "My New Title") startActivity(intent)
Java
long eventID = 208; Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID); Intent intent = new Intent(Intent.ACTION_EDIT) .setData(uri) .putExtra(Events.TITLE, "My New Title"); startActivity(intent);
Как использовать намерения для просмотра данных календаря
Поставщик календаря предлагает два способа использования намерения VIEW:
- Чтобы открыть Календарь на определенной дате.
- Просмотр мероприятия.
Вот пример того, как открыть Календарь на определенной дате:
Kotlin
val startMillis: Long ... val builder: Uri.Builder = CalendarContract.CONTENT_URI.buildUpon() .appendPath("time") ContentUris.appendId(builder, startMillis) val intent = Intent(Intent.ACTION_VIEW) .setData(builder.build()) startActivity(intent)
Java
// A date-time specified in milliseconds since the epoch. long startMillis; ... Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon(); builder.appendPath("time"); ContentUris.appendId(builder, startMillis); Intent intent = new Intent(Intent.ACTION_VIEW) .setData(builder.build()); startActivity(intent);
Ниже показано, как открыть мероприятие для просмотра.
Kotlin
val eventID: Long = 208 ... val uri: Uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventID) val intent = Intent(Intent.ACTION_VIEW).setData(uri) startActivity(intent)
Java
long eventID = 208; ... Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID); Intent intent = new Intent(Intent.ACTION_VIEW) .setData(uri); startActivity(intent);
Адаптеры синхронизации
Существуют лишь незначительные различия в том, как приложение и адаптер синхронизации получают доступ к поставщику календаря:
- Адаптер синхронизации должен указывать, что он является адаптером синхронизации, устанавливая для
CALLER_IS_SYNCADAPTERзначениеtrue. - Адаптер синхронизации должен предоставлять
ACCOUNT_NAMEиACCOUNT_TYPEв качестве параметров запроса в URI. - У адаптера синхронизации есть доступ на запись к большему количеству столбцов, чем у приложения или виджета.
Например, приложение может изменить только несколько характеристик календаря, таких как его название, отображаемое название, настройка видимости и синхронизация календаря. Для сравнения, адаптер синхронизации может получить доступ не только к этим столбцам, но и ко многим другим, таким как цвет календаря, часовой пояс, уровень доступа, местоположение и т. д.
Однако адаптер синхронизации ограничен указанными им
ACCOUNT_NAMEиACCOUNT_TYPE.
Ниже приведен вспомогательный метод, который можно использовать для возврата URI, используемого с адаптером синхронизации.
Kotlin
fun asSyncAdapter(uri: Uri, account: String, accountType: String): Uri { return uri.buildUpon() .appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true") .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_NAME, account) .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_TYPE, accountType).build() }
Java
static Uri asSyncAdapter(Uri uri, String account, String accountType) { return uri.buildUpon() .appendQueryParameter(android.provider.CalendarContract.CALLER_IS_SYNCADAPTER,"true") .appendQueryParameter(Calendars.ACCOUNT_NAME, account) .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build(); }