<?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>Calendar Provider是用于用户的日历事务的仓库。Calendar Provider API允许你在日历,事务,参与者,提醒等,执行查询,插入,更新,和删除操作。
Calendar Provder API能通过应用程序和同步Adapter被使用。基于程序的类型的规则变化执行调用。这个文档主要集中于和一个应用一样使用Calendar Provider API。查看同步Adapter的差异讨论,查阅Sync Adapters。
通常地,读或者写日历数据,一个应用程序的清单文件必须包含相应的权限,在User Permission中被描述。为了更简单的执行共同的操作,Calendar Provider支持一系列的Intent,在Calendar Intent中被描述。这些Intent使用户在日历应用程序中插入,查看,和编辑事务。用户和日历应用程序相互作用,然后返回到原始的应用程序。尽管你的应用程序不需要请求权限,或者它需要给用户提供一个查看或创建事务的接口。
基础
———————————————————————————————————————————————————————————————
Content Provider存储数据并使它能被应用程序访问。content provider支持Android平台(包含Calendar Provider)典型暴露数据,作为一系列基于关系数据库模型的表,它每行是一个记录并且没列是一个特数据的数据和意思。通过Calendar Provider API,应用程序和同步Adapter能获取拥有一个用户日历数据的数据库表的读/写访问。
任何content provider暴露一个公共的URI( 作为一个Uri对象包装),它是它的数据集合的唯一标识。一个控制多个数据集合(多个表)的content provider给每个暴露一个单独的URI。所有provider的URI起始于字符串”content://”。它标识这个数据被一个content provider控制。Calendar Provider给每个它的类(表)定义URI常量。这些URI有<class>.CONTENT_URI格式。例如,Events.CONTENT_URI。
CalendarContract定义了日历的数据模型和事务相关的信息。这些数据被存放在许多表中,被列举如下。
Table(Class) | Description |
CalendarContract.Calendars | 这个表格保存了日历特指的信息。在表中的每行包含一个单独的日历的详细信息,例如名字,颜色,同步信息,等等。 |
CalendarContract.Events | 这个表格保存了事务特指的信息。在表中的每行包含一个单独事务的信息—例如,事务标题,地点,开始时间,结束时间,等等。这个事务能在某个时间发生,或者能多次重复发生。参与者,提醒,和扩展的属性被保存在一个分离的表。他们有一个EVENT_ID来映射在事务表中的_ID。 |
CalendarContract.Instances | 这个表格保存了每个发生事务的开始和终止时间。在表中的没行代表一个单独的事务发生。对于一次性事务这里有一个1:1的实例到事务的映射。对于重复事务,多行自动被生成,它这个事务的多次发生想对应。 |
lendarContract.Attendees | 这个表格保存事务出席者(宾客)信息。没啊和那个代表一个事务的一个单独宾客。如果指定这个事务的宾客的类型和宾客的出席响应。 |
CalendarContract.Reminders | 这个表格保存了警告/通知数据。每行代表了一个事务的一个单独警告。一个事务能有多个提醒。每个事务的提醒的最大数目在MAX_REMINDERS中指定,它通过拥有这个被给予的日历的同步Adapter设置。提醒使用事务发生分钟指定,并且有一个方法决定用户将要如何被警告。 |
Calendar Provider API被设计的灵活并且强大。同时,它对于提供一个好的用户体验和保护日历和它的数据的完整性很重要。这儿是一些当你使用API时候记住的事情:
插入,更新,和查看日历事务。从Calendar Provider直接插入,修改,和读取事务,你需要相应的权限。然而,如果你没有创建一个完全日历应用程序或者同步Adapter,请求这些权限是没有必要的。你能通过Android的日历应用程序支持的intent,给那个应用程序切换读和写操作。当你使用Intent的时候,你的应用程序发送用户到日历应用程序,来执行渴望的操作。在他们执行完,他们被返回到你的应用程序。通过设计应用程序使用日历来执行相同的操作,你提供给用户一个助手,强健的用户接口。这是被推荐的方式。更多信息,请查阅Calender Intents。
同步Adapter。同步Adapter在一个用户设备和其它服务器或者数据源同步日历数据。在CalendarContract.Calendars和CalendarContract.Events表,这里的列被保留用户与同步Adapter使用。Provider和应用不能修改它们。事实上,它们是不可用的,除非作为一个同步Adapter被访问。更多关于同步Adapter信息,请查阅Sync Adapters。
用户权限
————————————————————————————————————————————————————————————————————————————
为了读取日历数据,应用程序必须早它的清单文件中包含READ_CALENDAR权限。它必须包含WRITE_CALENDAR权限来删除,插入或者更新日历数据:
日历表
—————————————————————————————————————————————————————————————— ——————————————
CalendarContract.Calendars表包含个人日历的详情。下面日历列对于一个应用程序和一个同步Adapter是可写的。查看支持的区域的完整列表,请查阅CalendarContract.Calendars参考。
Constant | Description |
NAME | 日历名称 |
CALENDAR_DISPLAY_NAMW | 显示给用户的日历名称 |
VISIBLE | 一个布尔值,表明日历是否被选择来显示。一个0值表明和这个事务相关的日历没有被显示。一个1值表明和这个事务相关的日历被显示。这个值影响在CalendarContract.Instance表的生成的行。 |
SYNC_EVENTS | 一个布尔值,表明这个日历是否能被同步和在设备中存放它的事务。一个0值说明不会同步这个日历或者在设备上存储它的事务。一个1值说明同步日历的事务和在设备上存储事务。 |
查询一个日历
这里是显示如何获取一个特殊用户拥有日历的例子。为避免繁琐,在这个例子中查询操作在用户接口线程(“主线程”)中被显示。实际上,这个必须在一个异步线程中执行来代替在主线程中。更多的讨论,请查阅Loaders。如果你不仅仅读取数据且修改数据,查看AsyncQueryHandler。
// 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.google”,[email protected]??想查看用户查看的所有日历,二不仅仅是用户所拥有的日历,省略OWNER_ACOOUNT。查询返回一个Cursor对象,你能用户横穿通过数据库查询返回的结果集。更多在content provider使用查询,查阅Content Provider。
// 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.google", "[email protected]"}; // Submit the query and get a Cursor object back. cur = cr.query(uri, EVENT_PROJECTION, selection, selectionArgs, null);
下面的章节使用cursor来逐步通过结果集。它使用在例子开始的的时候设置的常量来返回每个区域的值。
// 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追加的ID(withAppendId())或者如同第一个选择项。这个选着应该以“_id=?”开始,并且第一个选着参数应该是这个日历的_ID。你也能通过编码在URI中的ID。这个例子使用这种方式(withAppendId())改变了一个日历的显示名称:
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);
插入日历
日历被设计通过一个同步Adapter进行主要管理,所以你仅仅插入新的日历作为一个同步Adapter。对于大部分的部分,应用程序仅仅能给日历做特定的改变,例如改变显示名称。如果一个应用程序需要创建一个本地的日历,它能通过执行日历插入作为一个同步Adapter来实现,使用一个ACCOUNT_TYPE_LOCAL的ACCOUNT_TYPE。ACCOUNT_TYPE_LOCAL是一个日历特殊的账户类型,它没有和设备账目想关联。这种类型的日历不和服务器同步。查找同步Adapter的讨论,请查阅Sync Adapter。
事务表
———————————————————————————————————————————————————————————————————————————
CalendarContract.Events表包含个别事务的详情。为了添加,更新,或者删除事务,应用程序必须包在它的清单文件中包含WRITE_CALENDAR权限。
下面的事务列允许应用程序和同步Adapter写入。查看支持域的完整列表,请查阅CalendarContract.Evenets参考。
Constant | Description |
CALENDAR_ID | 事务属于的日历_ID。 |
ORGANIZER | 事务组织者(拥有者)的email。 |
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和RULE结合定义一个重复发生合计集合。更多的讨论,请查阅RFC5545 spec。 |
AVAIABILITY | 如果事务计数作为忙碌时间或者是清闲时间,可以被安排。 |
GUESTS_CAN_MODIFY | 客户是否能修改事务。 |
GUESTS_GAN_INVITE_OTHERS | 客户是否能邀请其他客户。 |
GUESTS_GAN_SEE_GUESTS | 客户是否能查看参与者列表。 |
添加事务
当你的应用程序插入一个新的事务,我们要求你使用一个INSERT Intent,如同在an intent to insert an event被描述一样。然而,如果你需要做,你能直接插入事务。这个章节描述了如何这样做。
下面是用于插入一个新的事务的规则:
你必须包含CALENDAR_ID和DTSTART。
你必须包含一个EVENT_TIMEZONE。为了获取系统的被安装时区的ID列表,使用getAvailableIDs()方法。注意如果你使用在Using an intent to insert an event中被描述的NSERT Intent插入一个事务,这个规则没有被使用。在那个场景,一个默认的时区被使用。
对于没有重复的事务,你必须包含DTEND。
对于重复的事务,你必须包含DURATION,除RRULE或者RDATE之外。注意如果是通过在Using an intent to insert an event中被描述的INSERT Intent插入一个事务,这规则没有被使用。在那个场景,你能使用一个RRULE同DTSTART和DTEND一起,并且日历自动转换它成为一个持续时间。
这里是插入一个事务的例子。为简单起见,这个在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,在Using an Intent to edit an event中被描述。然而,如果你需要,你能直接编辑事务。为了执行一个事务的更新,你能提供事务的_ID或者被追加ID的Uri(withAppendId()方法)或者第一个选择项。选着应该以”_id=?”开始,并且第一个seletionArg应该是事务的_ID。你也能使用没有ID的选择执行更新。下面是一个更新事务示例。它使用withAppendId()方法的途径更改了事务的标题:
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);
删除事务
你也通过一个追加它的_ID的URI删除事务,或者通过使用标准的选择。如果你使用一个追加的ID,你被追加的ID,你也不能执行一个选择。这里有两个版本的删除:作为一个应用程序和作为一个同步Adapter。删除应用程序设置被删除的列为1。这个标识告诉同步Adapter,被删除行和这个删除应该同步给服务器。同步Adapter从数据库中删除事务和它所有相关的数据。这是一个应用程序通过它的_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);
出席者列表
———————————————————————————————————————————————————————————————
CalendarContract.Attendees表的每行一个事务单独的出席者或者宾客。使用给予的EVENT_ID调用query()方法返回事务的一个出席者列表。这个EVENT_ID必须匹配一个特定事务的_ID。
下面的表格列举了可写字段。当插入一个新的出席者,你必须全部包含它们除了ATTENDEE_NAME。
Constant | Description |
EVENT_ID | 事务的ID。 |
ATTENDEE_NAME | 出席者的名称。 |
ATTENDEE_EMAIL | 出席者的email。 |
ATTENDEE_RELATIONSHIP | 出席者和和事务的关系,其中一个:
|
日历Intent
—————————————————————————————————————————————————————————————————————————————
你的应用程序不需要权限来读和写日历数据。它能被替代,使用Andoird日历程序支持的Intent来来处理那个应用程序的读和写操作。下面的表列举了日历Provider支持的Intent:
Action | URI | Description | Extras |
VIEW | content://com.android.calendar/time/<ms_since_epoch> 你也能使用CalendarContract.CONTENT_URI引用这个URI。查看使用这个Intent的例子,请查阅Using intents to view calendar data。 | 在通过<ms_since_epoch>被指定的时间打开日历 | None。 |
VIEW | content://com.android.calendar/events/<event_id> 你也能通过Events.CONTENT_URI引用这个URI,查看使用这个Intent的例子,请查阅Using intent to view Calendar data。 | 查看通过<event_id>被指定的事务 | CalendarContract.EXTRA_EVENT_BEGIN_TIME CalendarContract.EXTRA_EVENT_END_TIME |
EDIT | content://com.android.calendar/events/<event_id> 你也能使用Events.CONTENT_URI引用这个URI。查看使用这个Intent的例子,请查阅Using an intint to edit an event。 | 编辑通过<event_id>被的事务 | CalendarContract.EXTRA_EVENT_BEGIN_TIME CalendarContract.EXTRA_EVENT_END_TIME |
EDIT INSERT | content://com.android.calendar/events 你也可以通过Events.CONTENT_URI引用这个URI。查看使用这个Intent的例子,请查阅Using an intent to insert a event。 | 创建一个事务 | 所有在下面表中被列举的额外部分。 |
下面的表格列举了日历Provider额外支持的Intent:
Intent Extra | Description |
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 | 当作一个逗号分隔列表邀请的email地址。 |
Events.RRULE | 事务的重复规则。 |
Events.ACCESS_LEVEL | 事务是否是私有的或者公共的。 |
Events.ACAILABILITYT | 被合计为忙碌时间或者休闲时间能被安排的事务。 |
下面的章节描述了如何使用这些Intent。
使用一个Intent插入一个事务
使用INSERT Intent让你的应用程序处理向自己的日历中插入事务任务。使用这种方式,你的应用程序不需要在它的清单文件中包含WRITE_CALENDAR权限。
当用户运行一个使用这种方式的应用程序,这个应用程序向日历发送Intent来完成添加事务。INSERT Inent使用在日历中,使用附加的属性预装一个含有事务详情的表格。用户然后能取消事务,根据自己的需要编辑表格,或者向它们的日立中保存事务。
这里是一个代码片段,它安排一个在2012年1月19日的事务,从上午7:30到8:30运行。注意下面关于这个代码片段的事项:
它指定Events.CONTENT_URI作为Uri。
它使用CalendarContract.EXTRA_EVENT_BEGIN_TIME和CalendarContract.EXTRA_EVENT_END_TIME附加区域原装一个事务时间的表格。这个时间的值必须从某个时间开始的毫秒数。
它使用Intent.EXTRA_EMAL附加区域来提供一个逗号分隔的参与者列表,通过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);
使用一个Intent编辑事务
你能像Updating events中被描述的,直接更新一个事务。但是使用EDIT Intent允许没有权限的应用程序切换到日历应用程序来编辑事务。当用户完成在日历中编辑它们的事务,它们返回原来的应用程序。
下面是一个例子,一个设置指定事务的标题的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查看日历数据
日历Provider支持两种不同的方式来使用VIEW 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);
下面是一个示例,它显示了如何查阅一个事务:
long eventID = 208; ... Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID); Intent intent = new Intent(Intent.ACTION_VIEW) .setData(uri); startActivity(intent);
同步Adapter
———————————————————————————————————————————————————————————————
在一个应用程序和一个同步Adapter如何访问日历Provider之间,只有小小的不一样:
同步Adapter需要通过设置CALLER_IS_SYNCADAPTER为true来执行它是一个同步Adapter。
同步Adapter需要在URI中提供一个ACCOUNT_NAME和一个ACCOUNT_TYPE作为查询参数。
同步Adapter比应用程序或者小部件可以写更多列。例如一个应用程序只能修改日历的很少特性,例如它的名称,同步Adapter能访问不仅仅这些列,且许多其它列,例如日历颜色,时间区域,访问级别,地点等等。然而,同步Adapter是被它指定的ACCOUNT_NAME和ACCOUNT_TYPE限制。
这里是一个帮助方法,你能返回一个和同步Adapter使用的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(); }
查看同步Adapter的实现例子(没有和日历相关),请查阅SampleSyncAdapter。