Обзор поставщика календарей

Поставщик календаря — это хранилище событий календаря пользователя. API поставщика календаря позволяет выполнять операции запроса, вставки, обновления и удаления с календарями, событиями, участниками, напоминаниями и т. д.

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

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

Основы

Поставщики контента хранят данные и делают их доступными для приложений. Поставщики контента, предлагаемые платформой Android (включая поставщика календаря), обычно предоставляют данные в виде набора таблиц на основе модели реляционной базы данных, где каждая строка является записью, а каждый столбец — данными определенного типа и значения. Через 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 , который задается адаптером синхронизации, которому принадлежит данный календарь. Напоминания указываются за несколько минут до события и имеют метод, определяющий способ оповещения пользователя.

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 .
  • Для повторяющихся событий в дополнение к RRULE или RDATE необходимо указать DURATION . Обратите внимание, что это правило не применяется, если вы вставляете событие через намерение 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

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 . Пример использования этого намерения см. в разделе Использование намерения для вставки события .
Создайте событие. Любое из дополнений, перечисленных в таблице ниже.

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

Намерение Экстра Описание
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();
 }