日历提供程序是用户日历活动的存储区。借助 Calendar Provider API,您可以对日历、事件、参加者、提醒等执行查询、插入、更新和删除操作。
Calendar Provider API 可供应用和同步适配器使用。这些规则因进行调用的程序类型而异。本文档主要重点介绍如何将 Calendar Provider API 用作应用。如需了解各种同步适配器差异的讨论,请参阅同步适配器。
通常,如需读取或写入日历数据,应用的清单必须包含用户权限中所述的适当权限。为简化常见操作,日历提供程序提供了一组 intent,如日历 intent 中所述。这些 intent 可将用户转到日历应用,以便插入、查看和修改事件。用户与日历应用交互,然后返回原始应用。因此,您的应用不需要请求权限,也不需要提供用于查看或创建事件的界面。
基础知识
内容提供程序会存储数据,并使其可供应用访问。Android 平台提供的内容提供程序(包括日历提供程序)通常以一组基于关系型数据库模型的表格形式公开数据,其中每一行都是一条记录,每一列都是特定类型和含义的数据。应用和同步适配器可以通过日历提供程序 API 获得对保存用户日历数据的数据库表的读写权限。
每个 content provider 都会公开一个公共 URI(封装为 Uri
对象),用于唯一标识其数据集。控制多个数据集(多个表)的 content provider 会为每个数据集公开单独的 URI。所有提供程序 URI 都以字符串“content://”开头。这表示数据受 content provider 的控制。日历提供程序会为其每个类(表)定义 URI 常量。这些 URI 的格式为 <class>.CONTENT_URI
。例如 Events.CONTENT_URI
。
图 1 显示了日历提供程序数据模型的图形表示。其中显示了将彼此链接在一起的主要表和字段。

图 1. 日历提供程序数据模型。
用户可拥有多个日历,并且可将不同日历与不同类型的账户(Google 日历、Exchange 等)进行关联。
CalendarContract
定义了日历和活动相关信息的数据模型。这些数据存储在以下所列的若干表中。
表(类) | 说明 |
---|---|
此表包含特定于日历的信息。此表中的每一行都包含一个日历的详细信息,例如名称、颜色、同步信息等。 | |
CalendarContract.Events |
此表包含事件特定信息。此表中的每一行都包含单个事件的信息,例如活动标题、地点、开始时间、结束时间等。事件可以发生一次性,也可以多次重复。参加者、提醒和扩展属性存储在单独的表中。它们各自都有一个 EVENT_ID ,用于引用“事件”表中的 _ID 。 |
CalendarContract.Instances |
此表包含每个事件实例的开始时间和结束时间。此表中的每一行代表一个事件发生实例。对于一次性事件,实例与事件为 1 对 1 的映射。对于周期性事件,系统会自动生成多个行,分别对应多个事件实例。 |
CalendarContract.Attendees |
此表包含事件参加者(来宾)信息。每一行代表事件的一位邀请对象。它指定邀请对象的类型以及活动的邀请对象出席响应。 |
CalendarContract.Reminders |
此表包含提醒/通知数据。每一行代表事件的一个提醒。一个活动可以有多个提醒。每个事件的最大提醒数量在 MAX_REMINDERS 中指定,后者由拥有给定日历的同步适配器设置。提醒以事件发生前的分钟数形式指定,其具有一个可决定用户提醒方式的方法。 |
日历提供程序 API 设计灵活且功能强大。同时,提供良好的最终用户体验并保护日历及其数据的完整性也非常重要。因此,请在使用该 API 时注意以下几点:
- 插入、更新和查看日历活动。如要直接插入、修改和读取日历提供程序中的活动,您需要拥有适当的权限。但是,如果您并未构建成熟的日历应用或同步适配器,则无需请求这些权限。您可以改用 Android 的日历应用支持的 intent 将读取和写入操作转给该应用。当您使用 intent 时,您的应用会将用户转到日历应用,以便通过预填充的表单执行所需操作。完成后,系统会返回您的应用。通过将应用设计为通过日历执行常见操作,您可以为用户提供一致且可靠的界面。我们建议您使用此方法。如需了解详情,请参阅日历 intent。
- 同步适配器 -同步适配器会将用户设备上的日历数据与其他服务器或数据源同步。在
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
对象,您可以使用该对象遍历数据库查询返回的结果集。如需详细了解如何在 content provider 中使用查询,请参阅 Content Provider。
// 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 或作为第一个所选项的形式提供。所选内容应以 "_id=?"
开头,并且第一个 selectionArg
应为日历的 _ID
。您还可以通过在 URI 中对 ID 进行编码来执行更新。以下示例使用 (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_LOCAL
的 ACCOUNT_TYPE
作为同步适配器执行日历插入操作。ACCOUNT_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 |
事件的重复发生日期。
通常将 RDATE 与 RRULE 结合使用来定义一组聚合重复实例。如需查看更详细的介绍,请参阅 RFC5545 规范。 |
AVAILABILITY |
此事件是计为忙碌时间还是可调度的空闲时间。 |
GUESTS_CAN_MODIFY |
邀请对象是否可以修改活动。 |
GUESTS_CAN_INVITE_OTHERS |
邀请对象是否可以邀请其他邀请对象。 |
GUESTS_CAN_SEE_GUESTS |
来宾是否可查看参加者列表。 |
添加事件
当应用插入新事件时,我们建议您使用 INSERT
intent,如使用 intent 插入事件中所述。不过,您可以根据需要直接插入事件。本部分介绍如何执行此操作。
以下是插入新事件的规则:
- 您必须添加
CALENDAR_ID
和DTSTART
。 - 您必须添加
EVENT_TIMEZONE
。如需获取系统中已安装时区 ID 的列表,请使用getAvailableIDs()
。请注意,如果您按使用 intent 插入事件中所述通过INSERT
intent 插入事件,则此规则不适用,在这种情况下,系统会提供默认时区。 - 对于非周期性活动,您必须添加
DTEND
。 - 对于周期性活动,除了
RRULE
或RDATE
之外,您还必须添加DURATION
。请注意,如果您通过INSERT
intent 插入事件(如使用 intent 插入事件中所述),则此规则不适用。在这种情况下,您可以将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
//
//
注意:请查看此示例如何在事件创建后捕获事件 ID。这是获取事件 ID 的最简单方法。您通常需要使用活动 ID 来执行其他日历操作,例如,为活动添加参加者或提醒。
更新事件
当应用想要允许用户修改事件时,我们建议您使用 EDIT
intent,如使用 intent 修改事件中所述。不过,您可以根据需要直接修改事件。如需执行事件更新,您可以提供事件的 _ID
:作为 URI 的附加 ID (withAppendedId()
),或作为第一个选择项。所选内容应以 "_id=?"
开头,并且第一个 selectionArg
应为事件的 _ID
。您还可以使用不含 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 的附加 ID,或使用标准选择。如果您使用附加 ID,则无法同时选择。共有两个版本的删除:应用删除和同步适配器删除。应用删除操作会将 deleted 列设置为 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 |
事件的 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
。
添加提醒
下例展示了如何为活动添加提醒。提醒在事件发生前 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 |
实例的开始时间,以协调世界时毫秒数表示。 |
END |
实例的结束时间,以协调世界时毫秒数表示。 |
END_DAY |
相对于日历时区的实例的朱利安结束日期。 |
END_MINUTE |
从日历时区午夜开始计算的实例结束时间(分钟)。 |
EVENT_ID |
此实例的事件的 _ID 。 |
START_DAY |
相对于日历时区的实例的 Julian 开始日。 |
START_MINUTE |
从日历时区午夜开始计算的实例开始时间(分钟)。 |
查询实例表
如需查询实例表,您需要在 URI 中指定查询的时间范围。在此示例中,CalendarContract.Instances
通过其 CalendarContract.EventsColumns
接口实现获得对 TITLE
字段的访问权限。换句话说,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()));
}
}
日历 Intent
您的应用不需要读取和写入日历数据的权限。它可以改用 Android 的日历应用支持的 intent 将读取和写入操作移交给该应用。下表列出了日历提供程序支持的 Intent:
操作 | URI | 说明 | 花絮 |
---|---|---|---|
VIEW |
CalendarContract.CONTENT_URI 引用 URI。如需查看使用此 intent 的示例,请参阅使用 intent 查看日历数据。
|
打开日历并定位到 <ms_since_epoch> 指定的时间。 |
无。 |
Events.CONTENT_URI 引用 URI。如需查看使用此 intent 的示例,请参阅使用 intent 查看日历数据。
|
查看 <event_id> 指定的事件。 |
CalendarContract.EXTRA_EVENT_BEGIN_TIME CalendarContract.EXTRA_EVENT_END_TIME |
|
EDIT |
Events.CONTENT_URI 引用 URI。如需查看使用此 intent 的示例,请参阅使用 intent 修改事件。
|
修改 <event_id> 指定的事件。 |
CalendarContract.EXTRA_EVENT_BEGIN_TIME CalendarContract.EXTRA_EVENT_END_TIME |
EDIT INSERT |
Events.CONTENT_URI 引用 URI。如需查看使用此 intent 的示例,请参阅使用 intent 插入事件。
|
创建活动。 | 下表列出的任一 Extra。 |
下表列出了日历提供程序支持的 intent extra:
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 |
将此事件视为忙碌时间还是可调度的空闲时间。 |
下文描述如何使用这些 Intent。
使用 Intent 插入事件
借助 INSERT
intent,您的应用可以将事件插入任务交给日历本身执行。如果使用此方法,您的应用甚至不需要在其清单文件中添加 WRITE_CALENDAR
权限。
当用户运行使用此方法的应用时,该应用会将用户转到日历以完成事件添加。INSERT
intent 使用 extra 字段为表单预填充日历中的活动详细信息。然后,用户可以取消事件、根据需要修改表单,或将事件保存到日历中。
以下代码段用于安排一个在 2012 年 1 月 19 日上午 7:30 到上午 8:30 发生的事件。请注意有关此代码段的以下内容:
- 它将
Events.CONTENT_URI
指定为 URI。 - 它使用
CalendarContract.EXTRA_EVENT_BEGIN_TIME
和CalendarContract.EXTRA_EVENT_END_TIME
extra 字段为表单预填充事件的时间。这些时间的值必须采用从公元纪年开始计算的世界协调时间 (UTC) 毫秒数。 - 它使用
Intent.EXTRA_EMAIL
extra 字段提供以英文逗号分隔的邀请对象列表(由电子邮件地址指定)。
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);
使用 Intent 编辑事件
您可以直接更新活动,如更新事件中所述。但使用 EDIT
intent 可让没有事件修改权限的应用将事件修改操作转由日历应用执行。用户在 Google 日历中完成活动修改后,将会返回原始应用。
以下是一个 intent 示例,它为指定事件设置新标题,并允许用户在日历中修改事件。
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);
使用 Intent 查看日历数据
日历提供程序提供了两种不同的 VIEW
intent 使用方式:
- 打开日历并定位到特定日期。
- 查看事件。
下例展示如何打开日历并定位到特定日期:
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();
}