日历提供程序是用户日历事件的存储区。借助 Calendar Provider API,您可以对日历、活动、参加者和提醒等内容执行查询、插入、更新和删除操作。
Calendar Provider API 可供应用和同步适配器使用。规则因发出调用的程序类型而异。本文档主要侧重于将 Calendar Provider API 用作应用。如需了解同步适配器的不同之处,请参阅同步适配器。
通常,若要读取或写入日历数据,应用的清单必须包含用户权限中所述的适当权限。为了更轻松地执行常见操作,日历提供程序提供了一组 intent,如日历 intent 中所述。这些 intent 会将用户转到日历应用,以便插入、查看和修改活动。用户与日历应用互动,然后返回到原始应用。因此,您的应用无需请求权限,也无需提供用于查看或创建活动的用户界面。
基本信息
Content Provider 用于存储数据并使其可供应用访问。Android 平台提供的内容提供程序(包括日历提供程序)通常会以一组基于关系数据库模型的表格形式公开数据,其中每一行都是一条记录,每一列都是特定类型和含义的数据。通过 Calendar Provider API,应用和同步适配器可以获得对存储用户日历数据的数据库表的读写权限。
每个 content provider 都会公开一个公共 URI(封装为 Uri 对象),用于唯一标识其数据集。控制多个数据集(多个表)的 content provider 会为每个数据集公开一个单独的 URI。提供程序的所有 URI 都以字符串“content://”开头。这会将数据标识为由 content provider 控制。日历提供程序为其每个类(表)的 URI 定义了常量。这些 URI 的格式为 <class>.CONTENT_URI。例如 Events.CONTENT_URI。
图 1 显示了日历提供程序数据模型的图形表示。它显示了主表以及将这些表相互关联的字段。
用户可以拥有多个日历,不同的日历可以与不同类型的账号(Google 日历、Exchange 等)相关联。
CalendarContract 定义了日历和活动相关信息的数据模型。这些数据存储在多个表中,如下所示。
| 表(类) | 说明 |
|---|---|
| 此表包含特定于日历的信息。此表中的每一行都包含单个日历的详细信息,例如名称、颜色和同步信息。 | |
CalendarContract.Events |
此表包含事件特定信息。此表中的每一行都包含单个活动的信息,例如活动标题、地点、开始时间、结束时间。活动可以是一次性的,也可以是多次重复的。参加者、提醒和扩展属性存储在单独的表中。
它们各自都有一个 EVENT_ID,用于引用 Events 表中的 _ID。 |
CalendarContract.Instances |
此表包含每个事件发生实例的开始时间和结束时间。此表中的每一行都代表一个事件发生。对于一次性活动,实例与活动之间存在 1:1 的映射关系。对于周期性活动,系统会自动生成多行,分别对应于该活动的多次发生。 |
CalendarContract.Attendees |
此表包含活动参加者(邀请对象)信息。每一行代表活动的一位邀请对象。它指定了邀请对象的类型以及邀请对象对活动的回复。 |
CalendarContract.Reminders |
此表包含提醒/通知数据。每一行代表一个事件的单个提醒。一个活动可以有多个提醒。每个活动的最大提醒数量在 MAX_REMINDERS 中指定,由拥有给定日历的同步适配器设置。提醒在活动开始前几分钟指定,并具有确定如何提醒用户的方法。 |
Calendar Provider 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。
Kotlin
// Projection array. Creating indices for this array instead of doing // dynamic lookups improves performance. private val EVENT_PROJECTION: Array<String> = arrayOf( CalendarContract.Calendars._ID, // 0 CalendarContract.Calendars.ACCOUNT_NAME, // 1 CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, // 2 CalendarContract.Calendars.OWNER_ACCOUNT // 3 ) // The indices for the projection array above. private const val PROJECTION_ID_INDEX: Int = 0 private const val PROJECTION_ACCOUNT_NAME_INDEX: Int = 1 private const val PROJECTION_DISPLAY_NAME_INDEX: Int = 2 private const val PROJECTION_OWNER_ACCOUNT_INDEX: Int = 3
Java
// Projection array. Creating indices for this array instead of doing // dynamic lookups improves performance. public static final String[] EVENT_PROJECTION = new String[] { Calendars._ID, // 0 Calendars.ACCOUNT_NAME, // 1 Calendars.CALENDAR_DISPLAY_NAME, // 2 Calendars.OWNER_ACCOUNT // 3 }; // The indices for the projection array above. private static final int PROJECTION_ID_INDEX = 0; private static final int PROJECTION_ACCOUNT_NAME_INDEX = 1; private static final int PROJECTION_DISPLAY_NAME_INDEX = 2; private static final int PROJECTION_OWNER_ACCOUNT_INDEX = 3;
在示例的下一部分中,您将构建查询。选择内容指定了查询的条件。在此示例中,查询会查找具有 ACCOUNT_NAME“hera@example.com”、ACCOUNT_TYPE“com.example”和 OWNER_ACCOUNT“hera@example.com”的日历。如果您想查看用户查看过的所有日历,而不仅仅是用户拥有的日历,请省略 OWNER_ACCOUNT。该查询会返回一个 Cursor 对象,您可以使用该对象遍历数据库查询返回的结果集。如需详细了解如何在 content provider 中使用查询,请参阅 Content Provider。
Kotlin
// Run query val uri: Uri = CalendarContract.Calendars.CONTENT_URI val selection: String = "((${CalendarContract.Calendars.ACCOUNT_NAME} = ?) AND (" + "${CalendarContract.Calendars.ACCOUNT_TYPE} = ?) AND (" + "${CalendarContract.Calendars.OWNER_ACCOUNT} = ?))" val selectionArgs: Array<String> = arrayOf("hera@example.com", "com.example", "hera@example.com") val cur: Cursor = contentResolver.query(uri, EVENT_PROJECTION, selection, selectionArgs, null)
Java
// Run query Cursor cur = null; ContentResolver cr = getContentResolver(); Uri uri = Calendars.CONTENT_URI; String selection = "((" + Calendars.ACCOUNT_NAME + " = ?) AND (" + Calendars.ACCOUNT_TYPE + " = ?) AND (" + Calendars.OWNER_ACCOUNT + " = ?))"; String[] selectionArgs = new String[] {"hera@example.com", "com.example", "hera@example.com"}; // Submit the query and get a Cursor object back. cur = cr.query(uri, EVENT_PROJECTION, selection, selectionArgs, null);
下一部分将使用游标逐步遍历结果集。它使用示例开头设置的常量来返回每个字段的值。
Kotlin
// Use the cursor to step through the returned records while (cur.moveToNext()) { // Get the field values val calID: Long = cur.getLong(PROJECTION_ID_INDEX) val displayName: String = cur.getString(PROJECTION_DISPLAY_NAME_INDEX) val accountName: String = cur.getString(PROJECTION_ACCOUNT_NAME_INDEX) val ownerName: String = cur.getString(PROJECTION_OWNER_ACCOUNT_INDEX) // Do something with the values... }
Java
// Use the cursor to step through the returned records while (cur.moveToNext()) { long calID = 0; String displayName = null; String accountName = null; String ownerName = null; // Get the field values calID = cur.getLong(PROJECTION_ID_INDEX); displayName = cur.getString(PROJECTION_DISPLAY_NAME_INDEX); accountName = cur.getString(PROJECTION_ACCOUNT_NAME_INDEX); ownerName = cur.getString(PROJECTION_OWNER_ACCOUNT_INDEX); // Do something with the values... ... }
修改日历
如需更新日历,您可以将日历的 _ID 作为附加 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_TYPE_LOCAL 的 ACCOUNT_TYPE。
ACCOUNT_TYPE_LOCAL 是一种特殊账号类型,适用于未与设备账号关联的日历。此类日历不会同步到服务器。如需了解同步适配器,请参阅同步适配器。
事件表格
CalendarContract.Events 表包含各个事件的详细信息。如需添加、更新或删除活动,应用必须在其清单文件中包含 WRITE_CALENDAR 权限。
以下 Events 列可由应用和同步适配器写入。如需查看受支持字段的完整列表,请参阅 CalendarContract.Events 参考文档。
| 常量 | 说明 |
|---|---|
CALENDAR_ID |
活动所属日历的 _ID。 |
ORGANIZER |
活动组织者(所有者)的邮箱。 |
TITLE |
活动标题。 |
EVENT_LOCATION |
活动举办地点。 |
DESCRIPTION |
事件的说明。 |
DTSTART |
事件开始时间(以自纪元以来的世界协调时间 [UTC] 毫秒数表示)。 |
DTEND |
事件结束时间(采用世界协调时间 [UTC],以自纪元以来的毫秒数表示)。 |
EVENT_TIMEZONE |
活动的时区。 |
EVENT_END_TIMEZONE |
活动结束时间所在的时区。 |
DURATION |
活动时长,采用 RFC5545 格式。
例如,值为 "PT1H" 表示事件应持续 1 小时,值为 "P2W" 表示持续时间为 2 周。 |
ALL_DAY |
值为 1 表示此活动持续一整天(由本地时区定义)。值为 0 表示这是一个常规活动,可能在一天中的任何时间开始和结束。 |
RRULE |
活动格式的重复规则。例如:"FREQ=WEEKLY;COUNT=10;WKST=SU"如需查看更多示例,请参阅 RFC5545 规范。 |
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()。请注意,如果您通过INSERTintent 插入活动(如使用 intent 插入活动中所述),则此规则不适用,因为在这种情况下,系统会提供默认时区。 - 对于非周期性活动,您必须添加
DTEND。 - 对于周期性活动,除了
RRULE或RDATE之外,您还必须添加DURATION。请注意,如果您通过INSERTintent 插入活动(如使用 intent 插入活动中所述),则此规则不适用。在这种情况下,您可以将RRULE与DTSTART和DTEND结合使用,日历应用会自动将其转换为时长。
以下是插入事件的示例。为简单起见,此操作在界面线程中执行。在实践中,应在异步线程中执行插入和更新操作,以便将操作移至后台线程。如需了解详情,请参阅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 intent,如使用 intent 修改事件中所述。不过,如有需要,您也可以直接修改事件。如需更新活动,您可以将活动的 _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, "trevor@example.com") put( CalendarContract.Attendees.ATTENDEE_RELATIONSHIP, CalendarContract.Attendees.RELATIONSHIP_ATTENDEE ) put(CalendarContract.Attendees.ATTENDEE_TYPE, CalendarContract.Attendees.TYPE_OPTIONAL) put( CalendarContract.Attendees.ATTENDEE_STATUS, CalendarContract.Attendees.ATTENDEE_STATUS_INVITED ) put(CalendarContract.Attendees.EVENT_ID, eventID) } val uri: Uri = contentResolver.insert(CalendarContract.Attendees.CONTENT_URI, values)
Java
long eventID = 202; ... ContentResolver cr = getContentResolver(); ContentValues values = new ContentValues(); values.put(Attendees.ATTENDEE_NAME, "Trevor"); values.put(Attendees.ATTENDEE_EMAIL, "trevor@example.com"); values.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE); values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_OPTIONAL); values.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_INVITED); values.put(Attendees.EVENT_ID, eventID); Uri uri = cr.insert(Attendees.CONTENT_URI, values);
提醒表
CalendarContract.Reminders 表的每一行都表示一个事件的单个提醒。调用 query() 会返回具有给定 EVENT_ID 的活动的提醒列表。
下表列出了提醒的可写字段。插入新提醒时,必须包含所有这些字段。请注意,同步适配器会在 CalendarContract.Calendars 表中指定其支持的提醒类型。如需了解详情,请参阅 ALLOWED_REMINDERS。
| 常量 | 说明 |
|---|---|
EVENT_ID |
事件的 ID。 |
MINUTES |
活动开始前多少分钟触发提醒。 |
METHOD |
在服务器上设置的警报方法。以下之一: |
添加提醒
此示例会向活动添加提醒。提醒会在活动开始前 15 分钟触发。
Kotlin
val eventID: Long = 221 ... val values = ContentValues().apply { put(CalendarContract.Reminders.MINUTES, 15) put(CalendarContract.Reminders.EVENT_ID, eventID) put(CalendarContract.Reminders.METHOD, CalendarContract.Reminders.METHOD_ALERT) } val uri: Uri = contentResolver.insert(CalendarContract.Reminders.CONTENT_URI, values)
Java
long eventID = 221; ... ContentResolver cr = getContentResolver(); ContentValues values = new ContentValues(); values.put(Reminders.MINUTES, 15); values.put(Reminders.EVENT_ID, eventID); values.put(Reminders.METHOD, Reminders.METHOD_ALERT); Uri uri = cr.insert(Reminders.CONTENT_URI, values);
实例表格
CalendarContract.Instances 表包含事件发生的开始时间和结束时间。此表中的每一行都代表一个事件发生。实例表不可写入,仅提供一种查询事件发生情况的方法。
下表列出了您可以针对实例查询的一些字段。请注意,时区由 KEY_TIMEZONE_TYPE 和 KEY_TIMEZONE_INSTANCES 定义。
| 常量 | 说明 |
|---|---|
BEGIN |
实例的开始时间(以世界协调时间毫秒为单位)。 |
END |
实例的结束时间(以世界协调时间毫秒为单位)。 |
END_DAY |
实例的儒略历结束日期(相对于日历的时区)。 |
END_MINUTE |
实例的结束分钟数,从日历时区的午夜开始测量。 |
EVENT_ID |
此实例的事件 _ID。 |
START_DAY |
实例的儒略历开始日期,相对于日历的时区。 |
START_MINUTE |
实例的开始分钟数(从午夜开始测量,相对于日历的时区)。 |
查询实例表
如需查询 Instances 表,您需要在 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())); } }
日历 intent
您的应用无需权限即可读取和写入日历数据。而是可以使用 Android 日历应用支持的 intent 将读写操作移交给该应用。下表列出了日历提供程序支持的 intent:
| 操作 | URI | 说明 | Extra |
|---|---|---|---|
VIEW |
CalendarContract.CONTENT_URI 引用 URI。
如需查看使用此 intent 的示例,请参阅使用 intent 查看日历数据。
|
打开日历,并跳转到 <ms_since_epoch> 指定的时间。 |
无。 |
Events.CONTENT_URI 引用 URI。
如需查看使用此 intent 的示例,请参阅使用 intent 查看日历数据。
|
查看 <event_id> 指定的事件。 |
CalendarContract.EXTRA_EVENT_BEGIN_TIMECalendarContract.EXTRA_EVENT_END_TIME |
|
EDIT |
Events.CONTENT_URI 引用 URI。
如需查看使用此 intent 的示例,请参阅使用 intent 修改活动。
|
修改 <event_id> 指定的活动。 |
CalendarContract.EXTRA_EVENT_BEGIN_TIMECalendarContract.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 权限。
当用户运行使用此方法的应用时,应用会将用户转到 Google 日历,以完成活动添加。INSERT Intent 使用 extra 字段,根据日历中的活动详情预先填充表单。然后,用户可以取消活动、根据需要修改表单,或将活动保存到其日历中。
以下代码段用于安排在 2012 年 1 月 19 日上午 7:30 至 8:30 举行的活动。请注意有关此代码段的以下事项:
- 它将
Events.CONTENT_URI指定为 URI。 - 它使用
CalendarContract.EXTRA_EVENT_BEGIN_TIME和CalendarContract.EXTRA_EVENT_END_TIMEextra 字段,以事件时间预先填充表单。这些时间的值必须采用自纪元以来的世界协调时间 (UTC) 毫秒数。 - 它使用
Intent.EXTRA_EMAILextra 字段提供以逗号分隔的受邀者列表(通过电子邮件地址指定)。
Kotlin
val startMillis: Long = Calendar.getInstance().run { set(2012, 0, 19, 7, 30) timeInMillis } val endMillis: Long = Calendar.getInstance().run { set(2012, 0, 19, 8, 30) timeInMillis } val intent = Intent(Intent.ACTION_INSERT) .setData(CalendarContract.Events.CONTENT_URI) .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, startMillis) .putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endMillis) .putExtra(CalendarContract.Events.TITLE, "Yoga") .putExtra(CalendarContract.Events.DESCRIPTION, "Group class") .putExtra(CalendarContract.Events.EVENT_LOCATION, "The gym") .putExtra(CalendarContract.Events.AVAILABILITY, CalendarContract.Events.AVAILABILITY_BUSY) .putExtra(Intent.EXTRA_EMAIL, "rowan@example.com,trevor@example.com") startActivity(intent)
Java
Calendar beginTime = Calendar.getInstance(); beginTime.set(2012, 0, 19, 7, 30); Calendar endTime = Calendar.getInstance(); endTime.set(2012, 0, 19, 8, 30); Intent intent = new Intent(Intent.ACTION_INSERT) .setData(Events.CONTENT_URI) .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, beginTime.getTimeInMillis()) .putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endTime.getTimeInMillis()) .putExtra(Events.TITLE, "Yoga") .putExtra(Events.DESCRIPTION, "Group class") .putExtra(Events.EVENT_LOCATION, "The gym") .putExtra(Events.AVAILABILITY, Events.AVAILABILITY_BUSY) .putExtra(Intent.EXTRA_EMAIL, "rowan@example.com,trevor@example.com"); startActivity(intent);
使用 intent 修改活动
您可以直接更新活动,如更新活动中所述。但使用 EDIT intent 允许没有权限的应用将活动编辑工作移交给日历应用。
用户在 Google 日历中完成活动修改后,会返回到原始应用。
以下示例展示了一个 intent,该 intent 可为指定活动设置新标题,并允许用户在 Google 日历中修改该活动。
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_NAME和ACCOUNT_TYPE作为查询参数。 - 同步适配器比应用或 widget 拥有更多列的写入权限。
例如,应用只能修改日历的少数特征,如名称、显示名称、公开范围设置以及日历是否同步。相比之下,同步适配器不仅可以访问这些列,还可以访问许多其他列,例如日历颜色、时区、访问权限级别和位置。
不过,同步适配器仅限于其指定的
ACCOUNT_NAME和ACCOUNT_TYPE。
以下是一个辅助方法,可用于返回与同步适配器搭配使用的 URI:
Kotlin
fun asSyncAdapter(uri: Uri, account: String, accountType: String): Uri { return uri.buildUpon() .appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true") .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_NAME, account) .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_TYPE, accountType).build() }
Java
static Uri asSyncAdapter(Uri uri, String account, String accountType) { return uri.buildUpon() .appendQueryParameter(android.provider.CalendarContract.CALLER_IS_SYNCADAPTER,"true") .appendQueryParameter(Calendars.ACCOUNT_NAME, account) .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build(); }