Android 日历CalendarProvider

注:本文为转载,是官网的一篇翻译。

日历提供器是(CalendarProvider)针对用户日历事件的一个资源库。日历提供器API允许你执行有关日历、、事件、会议、提醒等内容的查询、插入、更新、删除操作。

日历提供器API能够用于应用程序和同步适配器,规则的变化依赖于什么类型的程序制造了这个调用。本文重点关注应用程序怎样使用日历提供API。对于与同步适配器的不同的讨论,请看同步适配器。

通常,要读或写日历数据,应用程序的清单文件中必须包含“用户权限”一节中描述的适当的权限,为了让执行普通的的操作更加容易,日历提供器提供了一组在“日历Intent对象”一节中介绍的Intent对象。这些Intent对象携带了用户要日历提供器来插入、查看和编辑的事件。用户跟日历应用程序交互,然后返回到初始应用程序。这样你的应用程序不需要申请权限,也不需要提供查看或创建事件用的的用户界面。

基础

内容提供器保存数据并且使它对应用程序是可访问的。通常,通过Android平台(包括日历提供器)内容提供器以基于关系性数据库模型的表的集合的形式来暴露数据,每一行是一个记录,每一列是一种特定类型的数据。通过日历提供器API,应用程序和同步适配器能够获得对持有用户日历数据的数据库表的读写访问权限。

每个内容提供器都会公开一个公共的唯一标识它的数据集的URI(被封装成Uri对象)。一个内容提供器控制着多个数据集(多个表),给每个表都公开一个独立的URI。提供器的所有URI都以“content//”开头。这是作为由内容提供器控制的数据的标识。日历提供器给它的每个类(表)都定义了URI常量。这些URI的格式是.CONTENT_URI。例如:EVENTS.CONTENT_URI.

图1显示了日历提供器的数据模型,它显示主表和把它们彼此联系到一起的字段。

/uploads/allimg/130311/2142454603-0.gif

图1.日历提供器数据模型

一个用户能够有多个日历,并且不同的日历能够跟不同的账号类型进行关联。

CalendarContract类定义了日历和事件相关信息的数据模型。这种数据被保存在以下列出的多个表中。

表(类)

描述

CalendarContract.Calendars

这个表保存指定日历的信息,在这个表中每一行都包含一个单一日历的详细信息,如名字、颜色、同步信息等。

CalendarContract.Events

这个表保存了特定的事件信息。在这个表中每一行都有单一事件的信息,如事件的标题、位置、开始时间、结束时间等。这个事件能够发生一次或重复发生多次。会议、提醒和扩展的属性被保存的独立的表中,它们都有一个EVENT_ID跟Events表中的_ID进行关联。

CalendarContract.Instances

这个表保存一个事件每次发生的开始事件和结束时间。这个表中的每一行都代表了一个单一的已经发生了的事件。对于一次性事件这个表与Events表有1对1的映射,对于重复发送的事件,每次发生的结果都会自动的在这个表中生产一行。

CalendarContract.Attendees

这表保存事件的参与者信息。每行代表一个单一的事件参与者。它指定了参与者类型以及参与者对事件的参与响应

CalendarContract.Reminders

这个表保存了警告/提醒数据。每行代表一个事件的一个警告。一个事件能够有多个提醒。每个事件的最大提醒数据在MAX_REMINDERS中指定,它是由给定日历的同步适配器来设定的。在事件之前的几分中内来指定提醒,并且会有一个方法来判断如何对用户进行提醒。

日历提供器API被设计的灵活而且强大。重要的是它提供了良好的终端用户体验并且保护了日历和它的数据的完整性,因此在使用这些API时要记住以下事情:

1.  插入、更新、和查看日历事件:要直接从日历提供器中插入、修改、和读取事件,你需要适当的权限。但是,如果你不是要创建一个完整的日历应用程序或同步适配器,就没有必要申请这些权限。你能够使用由Android的日历应用程序支持的Intent对象来替代这些读写操作。当你使用Intent对象时,你的应用程序会把用户预填的表单发送给日历应用程序,让它执行期望的操作。执行完成后,会返回到你的应用程序。通过设计能够执行日历共同操作的应用程序,就可以给用户提供一致的、强大的用户界面。这是推荐的方法,有关更多的信息,请“看日历Intent对象”。

2.  同步适配器。同步适配器把用户设备上的日历数据跟另外的服务器或数据源同步。在CalendarContract.Calendars和CanlendarContract.Events表中,有一些保留给同步适配器使用的列。提供和应用程序不应该修改它们,直到它们被同步适配器访问时它们才可见。有关同步适配器的更多信息,请看“同步适配器”

用户权限

要读取日历数据,应用程序必须在清单文件中包括READ_CALENDAR权限。如果要删除、插入、或更新日历数据,就必须包含WRITE_CALENDAR权限。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"...>



...

Calendars表

CalendarContract.Calendars表包含了单个日历的详细信息。下表中Calendars表列对应用程序和同步适配器都是可写的。对于这个表支持的完整的字段列表,请看“CalendarContract.Calendars参考”

http://developer.android.com/reference/android/provider/CalendarContract.Calendars.html

常量

描述

NAME

日历的名字

CALENDAR_DISPLAY_NAME

显示给用户的名字

VISIBLE

一个指明被选择的日历是否显示的布尔值。0指明跟这个日历相关联的不应该显示,1指明跟这个日历关联的事件应该显示。这个值会影响CalendarContract.Instances表中行的产生。

SYNC_EVENTS

一个布尔值,指明日历是否应该被同步并在设备上保存其事件。0指明不同步这个日历并在设备上保存事件。1指明同步这个日历并在设备上保存其事件。

查询日历

这是一个显示怎样为特定的用户获取所有日历的例子。为了简单明了,在这个列子中,查询操作被写在了用户界面线程中(“主线程”)。实践中,应该用异步线程来替代主线程做这样的事情。

// 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
};

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

接下来要构造查询。Selection变量指定了查询条件。在这个例子中,要查找所有的ACCOUNT_NAME是“[email protected]”并且ACCOUNT_TYPE是“com.google”的日历。查询会返回一个Cursor对象,你可以用它来遍历数据库查询的结果。

// Run query
Cursor cur = null;
ContentResolver cr = getContentResolver();
Uri uri = Calendars.CONTENT_URI;  

String selection = “((” + Calendars.ACCOUNT_NAME + " = ?) AND ("

                 + Calendars.ACCOUNT_TYPE + " = ?))";

String[] selectionArgs = new String[] {"[email protected]", "com.google"};

// 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()) {
long calID = 0;
String displayName = null;
String accountName = 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);

   // Do something with the values...

  ...
}

修改日历

你能够通过日历的_ID来执行更新处理,这个ID既可以是附加到Uri(用withAppendedId()方法)中的ID,也可以是第一个选择项目的ID。selection变量应用用“_id=?”开头,并且selectionArg数组的第一个参数应该是这个日历的_ID。也可以通过URI中的编码ID来做更新的处理。下例使用withAppendedId()方法来改变日历的显示名称:

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

插入日历

Calendars被设计成以同步适配器为主的方式来管理的表,因此你只应该用同步适配器来插入新的日历。大多数情况,应用程序仅能改变日历的外观,如改变显示名字。如果应用程序需要创建一个本地日历,那么它能使用一个ACCOUNT_TYPE_LOCAL的ACCOUNT_TYPE列,通过执行同步适配器的日历插入处理来完成这件事情。ACCOUNT_TYPE_LOCAL是一个特殊的日历账号类型,它不跟设备账号关联。这种类型的日历不同步到服务器。关于同步适配器的讨论,请看“同步适配器”。

Events 表

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

格式化的事件复发规则(RFC5545)。如“FREQ=WEEKLY;COUNT=10;WKST=SU”。

RDATE

事件的复发日期。通常RDATE要联合RRULE一起使用来定义一个重复发生的事件的合集。

AVAILABILITY

If this event counts as busy time or is free time that can be scheduled over.????

GUESTS_CAN_MODIFY

参与者是否能够修改事件

GUESTS_CAN_INVITE_OTHERS

参与者是否能够邀请其他参与者

GUESTS_CAN_SEE_GUESTS

参与者是否能够看到与会者列表

注:RFC5545地址:http://tools.ietf.org/html/rfc5545#section-3.8.2.5

给Events表添加数据

当你的应用程序要插入一个新的事件时,我们推荐你使用INSERT类型Intent对象(在“使用Intent对象来插入事件”一节中介绍)。但是,如果需要,你能够直接插入事件,本节介绍怎样做这件事情。

以下是针对插入一个新的事件的一些规则:

1.  必须包含CALENDAR_ID和DTSTART字段

2.  必须包含EVENT_TIMEZONE字段。使用getAvailableIDs()方法获得系统已安装的时区ID列表。注意如果通过INSTERT类型Intent对象来插入事件,那么这个规则不适用,因为在INSERT对象的场景中会提供一个默认的时区;

3.  对于非重复发生的事件,必须包含DTEND字段;

4.  对重复发生的事件,必须包含一个附加了RRULE或RDATE字段的DURATIION字段。注意,如果通过INSERT类型的Intent对象来插入一个事件,这个规则不适用。因为在这个Intent对象的应用场景中,你能够把RRULE、DTSTART和DTEND字段联合在一起使用,并且Calendar应用程序能够自动的把它转换成一个持续的时间。

以下是插入一个事件的例子,为了简单,这个例子在UI线程中被执行,实际上,插入和更新处理应该在一个后台的线程中异步的执行。有关更多的信息,请看AsyncQueryHandler类

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对象,但是如果需要,你能够直接编辑事件。你能够提供要编辑的事件的_ID来执行事件的更新处理,这个ID既可以是附加给Uri的ID(用withAppendedId()方法),也可以是第一个选择项。selection变量应该用“_id=?”来开头,并且selectionArg参数的第一个值应该是这个事件的_ID值。你也能使用没有ID的selection变量来做更新处理。下面的例子更新了用withAppendedId()方法指明的事件的标题。

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");
myUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
int rows = getContentResolver().update(updateUri, values, null, null);
Log.i(DEBUG_TAG, "Rows updated: " + rows);  

删除Events表的数据

你能够使用附加在URI上的_ID来删除一个事件,也能够使用标准的选择条件来删除事件。如果使用一个附加的ID,就不能做选择。有两个删除的版本:以应用程序的方式和以同步适配器的方式。应用程序删除时会把“deleted”列设置为1,这个标记告诉同步适配器,这行已经被删除并且这个删除应该传递给服务端。同步适配器会把事件连同它关联的数据一起从数据库中删除。以下是应用程序通过事件_ID来删除事件的例子:

private static final String DEBUG_TAG = "MyActivity";
...
long eventID = 201;
...
ContentResolver cr = getContentResolver();
ContentValues values = new ContentValues();
Uri deleteUri = null;
deleteUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
int rows = getContentResolver().delete(deleteUri, null, null);
Log.i(DEBUG_TAG, "Rows deleted: " + rows);  

Attendees表

CalendarContract.Attendees表的每一行代表一个事件的单一参与者。用给定的EVENT_ID调用query()方法能够这个事件对应的参与者列表。EVENT_ID必须跟一个特殊的事件匹配。

下表列出Attendees表的可写字段,当插入一个新的与会者时,必须包含ATTENDEE_NAME以外的其他所有字段。

常量

描述

EVENT_ID

事件ID

ATTENDEE_NAME

与会者的名字

ATTENDEE_EMAIL

与会者的电子邮件地址

ATTENDEE_RELATIONSHIP

与会者与事件的关系,下列值之一

1.  RELATIONSHIP_ATTENDEE

2.  RELATIONSHIP_NONE

3.  RELATIONSHIP_ORGANIZER

4.  RELATIONSHIP_PERFORMER

5.  RELATIONSHIP_SPEAKER

ATTENDEE_TYPE

与会者的类型。下列值之一

1.  TYPE_REQUIRED

2.  TYPE_OPTIONAL

ATTENDEE_STATUS

与会者的与会状态。下列值之一:

1.  ATTENDEE_STATUS_ACCEPTED

2.  ATTENDEE_STATUS_DECLINED

3.  ATTENDEE_STATUS_INVITED

4.  ATTENDEE_STATUS_NONE

5.  ATTENDEE_STATUS_TENTATIVE

添加与会者

以下是给一个事件添加一个与会者的例子。注意,EVENT_ID是必须的:

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

Reminders表

CalendarContract.Reminders表的每一行代表一个事件的一个提醒。用给定的EVENT_ID调用query()方法会返回这个事件的提醒列表。

下表列出了Reminders表的可写字段。当插入一个新的提醒时,必须包含所有这些字段。注意,同步适配器在CalendarContract.Calendars表中指定了支持的提醒的类型。详细内容请看ALLOWED_REMINDERS

http://developer.android.com/reference/android/provider/CalendarContract.CalendarColumns.html#ALLOWED_REMINDERS

常量

描述

EVENT_ID

事件的ID

MINUTES

提供应该在几分钟之前触发事件。

METHOD

在服务上设置的报警的方法,下列设置之一:

1.  METHOD_ALERT

2.  METHOD_DEFAULT

3.  METHOD_EMAIL

4.  METHOD_SMS

添加提醒

下面的例子给一个事件添加一个提醒,这个体香在事件发生之前15分钟触发

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

Instances表

CalendarContract.Instances表保存一个事件的开始和结束时间。表中每一行代表一个单一发生的事件。Instances表不是可写的,并且只提供一个查询发生事件的方法。

下表列出了你能够查询的一些字段,注意:时区是由KEY_TIMEZONE_TYPE和KEY_TIMEZONE_INSTANCES字段定义的。

常量

描述

BEGIN

这个事件实例的开始时间。UTC毫秒

END

这个事件实例的结束时间。UTC毫秒

END_DAY

这个事件实例的结束日,相对与日历的时区

END_MINUTE

从日历的时区的0时开始计算的事件实例的结束分钟数

EVENT_ID

这个事件实例的事件ID

START_DAY

相对日历时区的事件实例的开始日

START_MINUTE

相对日历时区的从0时开始计算的实例事件的开始分钟数

查询Instances表

要查询Instances表,你需要在URI中给查询指定一个时间范围。在这个例子中,CalendarContract.Instances类通过CalendarContract.EventsColumns接口的实现获得对TITLE字段的访问。换句话说,TITLE字段是通过一个数据库视图来返回的,而不是通过查询CalendarContract.Instances表获得的。

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的Calendar应用程序支持的Intent对象来替代你的应用程序的读写权限。下表列出了Calendar提供器支持的Intent对象:

动作

资源标识(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

INSERT

content://com.android.calendar/events

也能够使用Events.CONTENT_URI来引用这个URI

创建一个事件

在下表中列出的任意附加功能

下表列出了Calendar提供器支持的Intent对象的附加功能:

Intent对象附加功能

描述

Events.TITLE

给事件命名

CalendarContract.EXTRA_EVENT_BEGIN_TIME

从纪元开始用毫秒设定事件的开始时间

CalendarContract.EXTRA_EVENT_END_TIME

从纪元开始用毫秒设定事件的结束时间

CalendarContract.EXTRA_EVENT_ALL_DAY

一个布尔值,指定事件是否是全天的。

Events.EVENT_LOCATION

事件的地点

Events.DESCRIPTION

事件的描述

Intent.EXTRA_EMALL

用逗号分开的受邀者电子邮件地址列表

Events.RRULE

事件的重复规则

Events.ACCESS_LEVEL

事件是私有还是共有的

Events.AVAILABILITY

预定事件是在忙时计数还是在闲时计数

以下介绍如何使用这些Intent对象:

1.  使用插入事件的Intent对象

使用INSERT类型的Intent对象会让你的应用程序把事件的插入的任务交给Calendar应用自己。用这种方法,你的应用程序不需要在清单文件中生命WRITE_CALENDAR权限。

当用户运行使用这种方法的应用程序时,应用程序会借助Intent对象把事件信息发送给Calendar应用程序来完成添加事件的处理。INSERT类型的Intent对象使用附加字段来预装一个带有Calendar应用中事件详细信息的表单。为了编辑表单的需要,用户可以取消这个事件,也可以把事件保存到它们的日历中。

以下代码段规划了一个在2011年1月19日上午7:30到8:30运行的事件。代码说明如下:

A.它指定Events.CONTENT_URI作为Uri;

B.它使用CalendarContract.EXTRA_EVENT_BEGIN_TIME和CalendarContract.EXTRA_EVENT_END_TIME附件字段来预装带有事件时间的表单。这些时间值必须是从纪元开始的UTC毫秒。

C.它使用Intent.EXTRA_EMAIL附加字段来提供一个用逗号分隔的受邀者电子邮件列表。

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

2.  使用编辑事件的Intent对象

你能够像“更新事件”一节中介绍的那样直接更新一个事件。但是使用EDIT类型的Intent对象允许没有权限的应用程序把要编辑的事件交给Calendar应用程序来处理。当用户在Calendar应用程序中完成编辑事件的处理时,就会返回原来的应用程序。

下面这个例子使用Intent对象给特定的事件设置一个新的标题,并且要用户在Calendar应用程序中编辑事件。

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

3.  使用Intent对象来查看日历数据

Calendar提供器提供了两种使用VIEW类型Intent对象的方法

A.打开一个特殊日期的日历

B.查看一个事件

下例子显示怎样打开一个特殊日期的日历:

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

下例显示了怎样打开一个要查看的事件:

long eventID = 208;
...
Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
Intent intent = new Intent(Intent.ACTION_VIEW)
.setData(uri);
startActivity(intent);

同步适配器

应用程序和同步适配器访问Calendar提供器的方式仅有较小的差异:

1.  同步适配器需要通过把CALLER_IS_SYNCADAPTER设置为true来指定它是一个同步适配器;

2.  同步适配器需要提供一个ACCOUNT_NAME和ACCOUNT_TYPE作为URI中的查询参数;

3.  同步适配器有写访问权限的列比应用程序或widget要多。例如,应用程序只能修改一些日历的字符,如名字、显示名、可见设置、以及日历是否被同步。同步适配器不仅能够访问这些列,而且还有一些其他列,如日历的颜色、时区、访问级别、地点等等。但是同步适配器要受到ACCOUNT_NAME和ACCOUNT_TYPE的限制。

你能够使用下面的这个帮助器方法来返回一个给同步适配器使用的URI:

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