日历提供程序概述

日历提供程序是用户日历事件的存储库。日历提供程序 API 允许您对日历、事件、参与者、提醒等执行查询、插入、更新和删除操作。

应用程序和同步适配器可以使用日历提供程序 API。规则因执行调用的程序类型而异。本文档主要侧重于将日历提供程序 API 用作应用程序。有关同步适配器如何不同的讨论,请参阅同步适配器

通常,要读取或写入日历数据,应用程序的清单必须包含适当的权限,如用户权限中所述。为了使执行常见操作更轻松,日历提供程序提供了一组意图,如日历意图中所述。这些意图将用户带到日历应用程序以插入、查看和编辑事件。用户与日历应用程序交互,然后返回到原始应用程序。因此,您的应用程序不需要请求权限,也不需要提供用户界面来查看或创建事件。

基础知识

内容提供程序存储数据并使其可供应用程序访问。Android 平台提供的內容提供程序(包括日历提供程序)通常会将数据公开为一组基于关系数据库模型的表,其中每行都是一条记录,每列都是特定类型和含义的数据。通过日历提供程序 API,应用程序和同步适配器可以获取对存储用户日历数据的数据库表的读写访问权限。

每个内容提供程序都公开了一个公共 URI(封装为一个Uri对象),该 URI 唯一标识其数据集。控制多个数据集(多个表)的内容提供程序会为每个数据集公开一个单独的 URI。所有提供程序的 URI 都以字符串“content://”开头。这将数据标识为受内容提供程序控制。日历提供程序为其每个类(表)定义 URI 的常量。这些 URI 的格式为<class>.CONTENT_URI。例如,Events.CONTENT_URI

图 1 显示了日历提供程序数据模型的图形表示。它显示了主表以及将它们相互链接的字段。

Calendar Provider Data Model

图 1. 日历提供程序数据模型。

用户可以拥有多个日历,不同的日历可以与不同类型的帐户(Google 日历、Exchange 等)关联。

CalendarContract定义了日历和事件相关信息的數據模型。这些数据存储在许多表中,列出如下。

表(类) 描述

CalendarContract.Calendars

此表保存特定于日历的信息。此表中的每一行都包含单个日历的详细信息,例如名称、颜色、同步信息等。
CalendarContract.Events 此表保存特定于事件的信息。此表中的每一行都包含单个事件的信息 - 例如,事件标题、位置、开始时间、结束时间等。该事件可以是一次性事件,也可以多次重复发生。参与者、提醒和扩展属性存储在单独的表中。它们都具有一个EVENT_ID,该 ID 引用 Events 表中的_ID
CalendarContract.Instances 此表保存事件每次发生时的开始时间和结束时间。此表中的每一行都代表单个事件发生。对于一次性事件,实例与事件之间存在一对一的映射关系。对于重复发生的事件,会自动生成多行,对应于该事件的多次发生。
CalendarContract.Attendees 此表格保存了活动参与者(嘉宾)的信息。每行代表一个活动的单个嘉宾。它指定了嘉宾的类型和嘉宾对该活动的出席回复。
CalendarContract.Reminders 此表格保存了警报/通知数据。每行代表活动的一个警报。一个活动可以有多个提醒。每个活动的最大提醒数量在 MAX_REMINDERS 中指定,该值由拥有给定日历的同步适配器设置。提醒是在事件开始之前几分钟指定的,并且有一个方法来确定将如何提醒用户。

Calendar Provider API 旨在灵活且功能强大。同时,为用户提供良好的体验并保护日历及其数据的完整性也很重要。为此,在使用 API 时请牢记以下几点。

  • 插入、更新和查看日历事件。 要直接从 Calendar Provider 插入、修改和读取事件,您需要相应的 权限。但是,如果您没有构建完整的日历应用程序或同步适配器,则不需要请求这些权限。您可以改为使用 Android 日历应用程序支持的意图,将读写操作转交给该应用程序。当您使用意图时,您的应用程序会将用户发送到日历应用程序以在预先填充的表单中执行所需的操作。完成后,他们将被返回到您的应用程序。通过设计您的应用程序以通过日历执行常见操作,您可以为用户提供一致、强大的用户界面。这是推荐的方法。有关更多信息,请参阅 日历意图
  • 同步适配器。 同步适配器将用户设备上的日历数据与另一个服务器或数据源同步。在 CalendarContract.CalendarsCalendarContract.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_NAMEACCOUNT_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 为“[email protected]”,ACCOUNT_TYPE 为“com.example”,以及 OWNER_ACCOUNT 为“[email protected]” 的日历。如果您想查看用户查看过的所有日历,而不只是用户拥有的日历,请省略 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("[email protected]", "com.example", "[email protected]")
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[] {"[email protected]", "com.example",
        "[email protected]"};
// 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 作为附加的 ID 添加到 Uri (withAppendedId()) 中,或者作为第一个选择项。选择应该以 "_id=?" 开头,并且第一个 selectionArg 应该是日历的 _ID。您也可以通过在 URI 中编码 ID 来进行更新。此示例使用 (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_TYPEACCOUNT_TYPE_LOCALACCOUNT_TYPE_LOCAL 是一种用于与设备帐户无关的日历的特殊帐户类型。此类型的日历不会同步到服务器。有关同步适配器的讨论,请参阅 同步适配器

事件表

CalendarContract.Events 表格包含单个事件的详细信息。要添加、更新或删除事件,应用程序必须在其 清单文件 中包含 WRITE_CALENDAR 权限。

应用程序和同步适配器都可以写入以下事件列。有关受支持字段的完整列表,请参阅 CalendarContract.Events 参考。

常量 描述
CALENDAR_ID 事件所属的日历的 _ID
ORGANIZER 事件的组织者(所有者)的电子邮件。
TITLE 事件的标题。
EVENT_LOCATION 事件发生的地点。
DESCRIPTION 事件的描述。
DTSTART 事件在 UTC 时间开始的时间,以自纪元以来的毫秒数表示。
DTEND 事件在 UTC 时间结束的时间,以自纪元以来的毫秒数表示。
EVENT_TIMEZONE 事件的时区。
EVENT_END_TIMEZONE 事件结束时间的时区。
DURATION 事件的持续时间,以 RFC5545 格式表示。例如,值 "PT1H" 表示事件应持续一个小时,值 "P2W" 表示持续时间为 2 周。
ALL_DAY 值为 1 表示此事件占用了整个日期,如当地时区定义。值为 0 表示它是一个普通的事件,可能在一天中的任何时间开始和结束。
RRULE 事件的重复规则格式。例如,"FREQ=WEEKLY;COUNT=10;WKST=SU"。您可以在 此处 找到更多示例。
RDATE 事件的重复日期。您通常将 RDATERRULE 结合使用以定义一组重复出现的聚合。有关更多讨论,请参阅 RFC5545 规范
AVAILABILITY 如果此事件算作忙碌时间,或者可以安排在上面的空闲时间。
GUESTS_CAN_MODIFY 嘉宾是否可以修改事件。
GUESTS_CAN_INVITE_OTHERS 嘉宾是否可以邀请其他嘉宾。
GUESTS_CAN_SEE_GUESTS 嘉宾是否可以看到出席者列表。

添加事件

当您的应用程序插入一个新事件时,我们建议您使用 INSERT 意图,如 使用意图插入事件 中所述。但是,如果您需要,您可以直接插入事件。本节介绍如何执行此操作。

以下是插入新事件的规则。

以下是如何插入事件的示例。为了简单起见,它在 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
//
//

注意:请参见此示例如何在创建事件后捕获事件 ID。这是获取事件 ID 的最简单方法。您通常需要事件 ID 来执行其他日历操作,例如向事件添加参与者或提醒。

更新事件

当您的应用程序想要允许用户编辑事件时,我们建议您使用 EDIT 意图(如 使用意图编辑事件 中所述)。但是,如果需要,您可以直接编辑事件。要执行事件更新,您可以将事件的 _ID 作为附加的 ID 附加到 URI (withAppendedId()),或者作为第一个选择项。选择应该以 "_id=?" 开头,并且第一个 selectionArg 应该是事件的 _ID。您也可以使用没有 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 的 ID)或使用标准选择来删除事件。如果您使用附加的 ID,则不能同时进行选择。删除有两个版本:应用程序和同步适配器。应用程序删除将 *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 事件的 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, "[email protected]")
    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, "[email protected]");
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 事件的 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_TYPEKEY_TIMEZONE_INSTANCES 定义。

常量 描述
BEGIN 实例的开始时间,以 UTC 毫秒为单位。
END 实例的结束时间,以 UTC 毫秒为单位。
END_DAY 实例的儒略结束日,相对于日历的时区。
END_MINUTE 实例的结束分钟数,从日历时区的午夜开始计算。
EVENT_ID 此实例所属事件的 _ID
START_DAY 实例的儒略开始日,相对于日历的时区。
START_MINUTE 实例的开始分钟数,从午夜开始计算,相对于日历的时区。

查询实例表

要查询实例表,您需要在 URI 中指定查询的时间范围。在此示例中,CalendarContract.Instances 通过其对 CalendarContract.EventsColumns 接口的实现来访问 TITLE 字段。换句话说,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>

您也可以使用 CalendarContract.CONTENT_URI 来引用 URI。有关使用此意图的示例,请参见 使用意图查看日历数据
打开日历,显示由 <ms_since_epoch> 指定的时间。 无。

VIEW

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

您也可以使用 Events.CONTENT_URI 来引用 URI。有关使用此意图的示例,请参见 使用意图查看日历数据
查看由 <event_id> 指定的事件。 CalendarContract.EXTRA_EVENT_BEGIN_TIME


CalendarContract.EXTRA_EVENT_END_TIME
EDIT

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

您也可以使用 Events.CONTENT_URI 来引用 URI。有关使用此意图的示例,请参见 使用意图编辑事件
编辑由 <event_id> 指定的事件。 CalendarContract.EXTRA_EVENT_BEGIN_TIME


CalendarContract.EXTRA_EVENT_END_TIME
EDIT

INSERT

content://com.android.calendar/events

您也可以使用 Events.CONTENT_URI 来引用 URI。有关使用此意图的示例,请参见 使用意图插入事件
创建事件。 下表中列出的任何附加信息。

下表列出了日历提供程序支持的意图附加信息。

意图附加信息 描述
Events.TITLE 事件的名称。
CalendarContract.EXTRA_EVENT_BEGIN_TIME 事件的开始时间,以自纪元以来的毫秒数表示。
CalendarContract.EXTRA_EVENT_END_TIME 事件的结束时间,以自纪元以来的毫秒数表示。
CalendarContract.EXTRA_EVENT_ALL_DAY 一个布尔值,指示事件是否为全天事件。值可以是 truefalse
Events.EVENT_LOCATION 事件的位置。
Events.DESCRIPTION 事件的描述。
Intent.EXTRA_EMAIL 要邀请的电子邮件地址,以逗号分隔的列表形式表示。
Events.RRULE 事件的重复规则。
Events.ACCESS_LEVEL 事件是私有的还是公开的。
Events.AVAILABILITY 如果此事件算作忙碌时间,或者可以安排在上面的空闲时间。

以下部分介绍了如何使用这些意图。

使用意图插入事件

使用 INSERT 意图可以让您的应用程序将事件插入任务委托给日历本身。使用这种方法,您的应用程序甚至不需要在其 清单文件 中包含 WRITE_CALENDAR 权限。

当用户运行使用此方法的应用程序时,该应用程序会将他们发送到日历以完成添加事件的操作。 INSERT 意图使用附加字段来预先填充表单,其中包含日历中事件的详细信息。然后,用户可以取消事件、根据需要编辑表单或将事件保存到他们的日历中。

以下代码片段会在 2012 年 1 月 19 日安排一个从上午 7:30 到 8:30 持续的事件。请注意以下有关此代码片段的信息。

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, "[email protected],[email protected]")
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, "[email protected],[email protected]");
startActivity(intent);

使用 Intent 编辑事件

您可以直接更新事件,如 更新事件 中所述。但是使用 EDIT Intent 允许没有权限的应用程序将事件编辑转交给日历应用程序。当用户在日历中完成事件编辑后,他们将返回到原始应用程序。

以下是一个 Intent 示例,它为指定的事件设置一个新标题并允许用户在日历中编辑该事件。

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);

使用 Intent 查看日历数据

日历提供程序提供两种使用 VIEW Intent 的方法

  • 打开日历到特定日期。
  • 查看事件。

以下示例展示了如何打开日历到特定日期

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 来指定它是同步适配器。
  • 同步适配器需要在 URI 中提供 ACCOUNT_NAMEACCOUNT_TYPE 作为查询参数。
  • 同步适配器对列的写入权限比应用程序或小部件更多。例如,应用程序只能修改日历的一些特性,例如名称、显示名称、可见性设置以及日历是否同步。相比之下,同步适配器不仅可以访问这些列,还可以访问许多其他列,例如日历颜色、时区、访问级别、位置等等。但是,同步适配器仅限于它指定的 ACCOUNT_NAMEACCOUNT_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();
 }