Поставщик календаря

Поставщик календаря – это хранилище мероприятий из календаря пользователя. 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.Calendars

В этой таблице содержится информация о календаре. Каждая строка в этой таблице содержит сведения об одном календаре, такие как название, цвет, информация о синхронизации и т. д.
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

content://com.android.calendar/time/<ms_since_epoch>

Вы также можете ссылаться на URI с помощью CalendarContract.CONTENT_URI. Пример использования этого намерения приведен в разделе Использование намерений для просмотра данных календаря.
Открыть календарь на времени, указанном в <ms_since_epoch>. Нет.

VIEW

content://com.android.calendar/events/<event_id>

Вы также можете ссылаться на URI с помощью Events.CONTENT_URI. Пример использования этого намерения приведен в разделе Использование намерений для просмотра данных календаря.
Посмотреть мероприятие, указанное в <event_id>. CalendarContract.EXTRA_EVENT_BEGIN_TIME


CalendarContract.EXTRA_EVENT_END_TIME
EDIT

content://com.android.calendar/events/<event_id>

Вы также можете ссылаться на URI с помощью Events.CONTENT_URI. Пример использования этого намерения приведен в разделе Использование намерения для изменения мероприятия.
Изменить мероприятие, указанное в <event_id>. CalendarContract.EXTRA_EVENT_BEGIN_TIME


CalendarContract.EXTRA_EVENT_END_TIME
EDIT

INSERT

content://com.android.calendar/events

Вы также можете ссылаться на URI с помощью 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();
 }