Поставщик календаря — это хранилище событий календаря пользователя. API поставщика календаря позволяет выполнять операции запроса, вставки, обновления и удаления с календарями, событиями, участниками, напоминаниями и т. д.
API поставщика календаря может использоваться приложениями и адаптерами синхронизации. Правила различаются в зависимости от того, какой тип программы осуществляет вызовы. В этом документе основное внимание уделяется использованию API поставщика календаря в качестве приложения. Подробнее о различиях адаптеров синхронизации см. в разделе Адаптеры синхронизации .
Обычно для чтения или записи данных календаря манифест приложения должен включать соответствующие разрешения, описанные в разделе «Разрешения пользователя» . Чтобы упростить выполнение общих операций, поставщик календаря предлагает набор намерений, как описано в разделе «Намерения календаря» . Эти намерения перенаправляют пользователей в приложение «Календарь» для вставки, просмотра и редактирования событий. Пользователь взаимодействует с приложением «Календарь», а затем возвращается к исходному приложению. Таким образом, вашему приложению не нужно запрашивать разрешения и предоставлять пользовательский интерфейс для просмотра или создания событий.
Основы
Поставщики контента хранят данные и делают их доступными для приложений. Поставщики контента, предлагаемые платформой Android (включая поставщика календаря), обычно предоставляют данные в виде набора таблиц на основе модели реляционной базы данных, где каждая строка является записью, а каждый столбец — данными определенного типа и значения. Через API поставщика календаря приложения и адаптеры синхронизации могут получать доступ для чтения и записи к таблицам базы данных, в которых хранятся данные календаря пользователя.
Каждый поставщик контента предоставляет общедоступный URI (завернутый в объект Uri
), который однозначно идентифицирует его набор данных. Поставщик контента, который управляет несколькими наборами данных (несколько таблиц), предоставляет отдельный URI для каждого из них. Все URI поставщиков начинаются со строки «content://». Это идентифицирует данные как контролируемые поставщиком контента. Поставщик календаря определяет константы для URI для каждого из своих классов (таблиц). Эти URI имеют формат <class> .CONTENT_URI
. Например, Events.CONTENT_URI
.
На рис. 1 показано графическое представление модели данных поставщика календаря. Здесь показаны основные таблицы и поля, которые связывают их друг с другом.
У пользователя может быть несколько календарей, и разные календари могут быть связаны с разными типами учетных записей (Календарь Google, Exchange и т. д.).
CalendarContract
определяет модель данных календаря и информации, связанной с событиями. Эти данные хранятся в ряде таблиц, перечисленных ниже.
Таблица (Класс) | Описание |
---|---|
Эта таблица содержит информацию, относящуюся к календарю. Каждая строка в этой таблице содержит сведения об одном календаре, такие как имя, цвет, информация о синхронизации и т. д. | |
CalendarContract.Events | Эта таблица содержит информацию, относящуюся к событию. Каждая строка в этой таблице содержит информацию об одном событии, например название события, место, время начала, время окончания и т. д. Событие может произойти однократно или повторяться несколько раз. Участники, напоминания и расширенные свойства хранятся в отдельных таблицах. У каждого из них есть EVENT_ID , который ссылается на _ID в таблице Events. |
CalendarContract.Instances | В этой таблице хранится время начала и окончания каждого события. Каждая строка в этой таблице представляет одно событие. Для одноразовых событий существует сопоставление экземпляров с событиями 1:1. Для повторяющихся событий автоматически генерируется несколько строк, соответствующих множественным повторениям этого события. |
CalendarContract.Attendees | В этой таблице хранится информация об участниках (гостях) мероприятия. Каждая строка представляет одного гостя мероприятия. Он определяет тип гостя и реакцию гостя на событие. |
CalendarContract.Reminders | Эта таблица содержит данные предупреждений/уведомлений. Каждая строка представляет одно оповещение о событии. Событие может иметь несколько напоминаний. Максимальное количество напоминаний на одно событие указано в MAX_REMINDERS , который задается адаптером синхронизации, которому принадлежит данный календарь. Напоминания указываются за несколько минут до события и имеют метод, определяющий способ оповещения пользователя. |
API поставщика календаря разработан как гибкий и мощный. В то же время важно обеспечить удобство работы конечных пользователей и защитить целостность календаря и его данных. Вот несколько вещей, которые следует учитывать при использовании API:
- Вставка, обновление и просмотр событий календаря. Чтобы напрямую вставлять, изменять и читать события из поставщика календаря, вам необходимы соответствующие разрешения . Однако если вы не создаете полноценное приложение календаря или адаптер синхронизации, запрашивать эти разрешения не обязательно. Вместо этого вы можете использовать намерения, поддерживаемые приложением Android Calendar, для передачи операций чтения и записи этому приложению. Когда вы используете намерения, ваше приложение отправляет пользователей в приложение «Календарь» для выполнения нужной операции в заранее заполненной форме. После завершения они возвращаются в ваше приложение. Разрабатывая свое приложение для выполнения общих операций через Календарь, вы предоставляете пользователям согласованный и надежный пользовательский интерфейс. Это рекомендуемый подход. Дополнительные сведения см. в разделе Назначение календаря .
- Адаптеры синхронизации. Адаптер синхронизации синхронизирует данные календаря на устройстве пользователя с другим сервером или источником данных. В таблицах
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
содержит сведения об отдельных календарях. Следующие столбцы календарей доступны для записи как приложением, так и адаптером синхронизации . Полный список поддерживаемых полей см. в справочнике 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
.
Котлин
// 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
Ява
// 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
, который можно использовать для перемещения по набору результатов, возвращаемому запросом к базе данных. Дополнительные сведения об использовании запросов в поставщиках контента см. в разделе Поставщики контента .
Котлин
// 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)
Ява
// 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);
В следующем разделе курсор используется для перемещения по набору результатов. Он использует константы, заданные в начале примера, для возврата значений для каждого поля.
Котлин
// 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... }
Ява
// 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()
):
Котлин
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")
Ява
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
в свой файл манифеста .
Следующие столбцы «События» доступны для записи как приложением, так и адаптером синхронизации. Полный список поддерживаемых полей см. в справочнике CalendarContract.Events
.
Постоянный | Описание |
---|---|
CALENDAR_ID | _ID календаря, которому принадлежит событие. |
ORGANIZER | Электронная почта организатора (владельца) мероприятия. |
TITLE | Название мероприятия. |
EVENT_LOCATION | Где происходит мероприятие. |
DESCRIPTION | Описание события. |
DTSTART | Время начала события в миллисекундах по всемирному координированному времени с начала эпохи. |
DTEND | Время окончания события в миллисекундах по всемирному координированному времени с начала эпохи. |
EVENT_TIMEZONE | Часовой пояс для мероприятия. |
EVENT_END_TIMEZONE | Часовой пояс времени окончания события. |
DURATION | Продолжительность события в формате RFC5545 . Например, значение "PT1H" указывает, что событие должно длиться один час, а значение "P2W" указывает на продолжительность 2 недели. |
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
, как описано в разделе Использование намерения для вставки события . Однако при необходимости вы можете вставлять события напрямую. В этом разделе описывается, как это сделать.
Вот правила вставки нового события:
- Вы должны указать
CALENDAR_ID
иDTSTART
. - Вы должны включить
EVENT_TIMEZONE
. Чтобы получить список установленных в системе идентификаторов часовых поясов, используйтеgetAvailableIDs()
. Обратите внимание, что это правило не применяется, если вы вставляете событие через намерениеINSERT
, описанное в разделе Использование намерения для вставки события — в этом сценарии предоставляется часовой пояс по умолчанию. - Для неповторяющихся событий необходимо включить
DTEND
. - Для повторяющихся событий необходимо добавить
DURATION
в дополнение кRRULE
илиRDATE
. Обратите внимание, что это правило не применяется, если вы вставляете событие через намерениеINSERT
, описанное в разделе «Использование намерения для вставки события» . В этом сценарии вы можете использоватьRRULE
в сочетании сDTSTART
иDTEND
, и приложение «Календарь» преобразует автоматически установить продолжительность.
Вот пример вставки события. Для простоты это выполняется в потоке пользовательского интерфейса. На практике вставки и обновления должны выполняться в асинхронном потоке, чтобы перенести действие в фоновый поток. Дополнительные сведения см. в разделе AsyncQueryHandler
.
Котлин
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 // //
Ява
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()
:
Котлин
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")
Ява
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, либо с помощью стандартного выбора. Если вы используете добавленный идентификатор, вы также не можете сделать выбор. Есть две версии удаления: как приложение и как адаптер синхронизации. При удалении приложения для удаленного столбца устанавливается значение 1. Этот флаг сообщает адаптеру синхронизации, что строка была удалена и что это удаление должно быть передано на сервер. Удаление адаптера синхронизации удаляет событие из базы данных вместе со всеми связанными с ним данными. Вот пример приложения, удаляющего событие через его _ID
:
Котлин
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")
Ява
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
является обязательным:
Котлин
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)
Ява
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 минут до мероприятия.
Котлин
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)
Ява
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 | Начальная минута экземпляра, отсчитываемая от полуночи относительно часового пояса календаря. |
Запросить таблицу экземпляров
Чтобы запросить таблицу экземпляров, вам необходимо указать диапазон времени для запроса в URI. В этом примере CalendarContract.Instances
получает доступ к полю TITLE
посредством реализации интерфейса CalendarContract.EventsColumns
. Другими словами, TITLE
возвращается через представление базы данных, а не через запрос к необработанной таблице CalendarContract.Instances
.
Котлин
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)}") }
Ява
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 Calendar, для передачи операций чтения и записи этому приложению. В следующей таблице перечислены намерения, поддерживаемые поставщиком календаря:
Действие | URI | Описание | Дополнительно |
---|---|---|---|
VIEW | CalendarContract.CONTENT_URI . Пример использования этого намерения см. в разделе Использование намерений для просмотра данных календаря . | Открыть календарь на время, указанное в <ms_since_epoch> . | Никто. |
Events.CONTENT_URI . Пример использования этого намерения см. в разделе Использование намерений для просмотра данных календаря . | Просмотрите событие, указанное <event_id> . | CalendarContract.EXTRA_EVENT_BEGIN_TIME CalendarContract.EXTRA_EVENT_END_TIME | |
EDIT | Events.CONTENT_URI . Пример использования этого намерения см. в разделе Использование намерения для редактирования события . | Отредактируйте событие, указанное в <event_id> . | CalendarContract.EXTRA_EVENT_BEGIN_TIME CalendarContract.EXTRA_EVENT_END_TIME |
EDIT INSERT | Events.CONTENT_URI . Пример использования этого намерения см. в разделе Использование намерения для вставки события . | Создайте событие. | Любые из дополнительных услуг, перечисленных в таблице ниже. |
В следующей таблице перечислены дополнительные возможности намерений, поддерживаемые поставщиком календаря:
Намерение Экстра | Описание |
---|---|
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
для предоставления списка приглашенных, разделенных запятыми, с указанием адреса электронной почты.
Котлин
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)
Ява
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
позволяет приложению, у которого нет разрешения, передавать редактирование событий приложению «Календарь». Когда пользователи завершают редактирование своего мероприятия в Календаре, они возвращаются в исходное приложение.
Вот пример намерения, которое устанавливает новое название для указанного события и позволяет пользователям редактировать это событие в календаре.
Котлин
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)
Ява
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
:
- Чтобы открыть Календарь на определенную дату.
- Чтобы просмотреть событие.
Вот пример, показывающий, как открыть Календарь на определенную дату:
Котлин
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)
Ява
// 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);
Вот пример, показывающий, как открыть событие для просмотра:
Котлин
val eventID: Long = 208 ... val uri: Uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventID) val intent = Intent(Intent.ACTION_VIEW).setData(uri) startActivity(intent)
Ява
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 для использования с адаптером синхронизации:
Котлин
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() }
Ява
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(); }