关闭

Android官方文档之Calendar Provider

标签: providerCalendar
4256人阅读 评论(0) 收藏 举报
分类:

Calendar Provider是一个用于提供用户标记在日历上事件的数据仓库。Calendar Provider 的API提供了包括增删改查在内的一系列操作日历事件的方法。

您可以在自己的应用程序或Adapter中使用Calendar Provider 的API。Adapter的相关用法将在本文的最后介绍。


通常来说,要访问日历应用程序中的数据,需要您在manifest中声明权限;为了简化操作, Calendar Provider 提供了一系列的Intents,使用这些Intents可以方便添加、查看、编辑 日历应用程序中的事件(take users to the Calendar application to insert, view, and edit events),这样一来,您可以与日历应用程序交互以获取日历应用的数据信息,而且不需要您声明权限,也不需要自己建立日历事件的视图。


本文将介绍Calendar Provider的相关内容,如需访问官方原文,您可以点击这个链接:《Calendar Provider》


基础概要(Basics)


Content providers存储了数据,并提供了API以便应用程序可以访问Content providers管理的数据。Content providers管理的数据通常是关系型数据库中的一系列表结构,表中的每一行表示一条记录(record),每一列表示该记录的一个特定属性。通过Calendar Provider API,您可以方便地读写日历应用程序中由Calendar Provider管理的一系列表中的记录。


每一个content provider都向外界暴露了一个独一无二的Uri地址,通过这个地址,您可以访问到这个content provider,以读写它所管辖的数据。所有的Uri都以content://开头。为了能访问到Calendar Provider中表的数据,应当采用 <class>.CONTENT_URI 的Uri格式,其中class就表示表名,如Events.CONTENT_URI


下图展示了Calendar Provider管辖的数据模型,该模型由一个叫做Events的表作为主表,通过该表可以链接至其他表:
这里写图片描述


CalendarContract类中定义了Calendar Provider中各种表所对应的模型,并存储相关信息,如下表所示:


表名(模型名) Tables(class) 描述(Description)
CalendarContract.Calendars 该表中包含了具体的日历信息(calendar-specific information),每一行表示一个日期中的具体内容,如名字,颜色,同步信息 等
CalendarContract.Events 该表中包含了具体的事件信息(event-specific information),每一行表示一个独立的事件,如,事件标题、发生地点、起始时间、结束时间 等(event title, location, start time, end time, and so on)。可以将事件设置为一次性事件或重复事件,而参加者(Attendees)、提醒信息(reminders)、其他属性信息(extended properties)都被存储于相应的表中,这些表中都各自包含一个EVENT_ID 字段,作为Events表的外键(一对多:一个事件可以有多个提醒信息 等。但一个提醒信息只能针对一个事件)
CalendarContract.Instances 该表记录了事件的起始和结束时间。每一行表示一个事件的发生情况。对于一次性事件,该表和Events表是一对一关系(一对一:一个事件只有一对起讫时间,而一对起讫时间也只对应一个事件) ;对于重复事件,该表和Events表是多对多关系(多对多:一个事件可以有多对起讫时间,而一对起讫时间也可以应用于多个事件)
CalendarContract.Attendees 该表记录了事件的参加者信息(holds the event attendee (guest) information),每一行表示了一个事件中的一个参加者信息。
CalendarContract.Reminders 该表记录了警告和通知信息(alert/notification data),每一行表示一个事件的警告,一个事件可以有多个警告,但有最大警告数的限制,用MAX_REMINDERS 指定。提醒信息会在设定提醒时间之前的若干分钟进行预提醒,以通知用户做好准备

使用Calendar Provider API需要注意的事:

  • 插入、更新、查看日历事件(Inserting, updating, and viewing calendar events):为了读写Calendar Provider中的数据,需要您添加读写权限。若您并不打算开发一款功能齐全的日历应用程序或者同步adapter,那么您无需申请权限(, if you’re not building a full-fledged calendar application or sync adapter, requesting these permissions isn’t necessary),您可以使用Intents访问系统自带的日历应用程序,并返回结果。有关Calendar Intents的内容,我将在后续翻译。

  • 同步adapter(Sync adapters):同步adapter可以从用户的设备中同步日历数据(A sync adapter synchronizes the calendar data on a user’s device with another server or data source)。CalendarContract.CalendarsCalendarContract.Events表中都提供了同步adapter的字段。Calendar Provider和其他应用程序不可修改这个字段中的内容。事实上,这对于外部应用是隐藏的。


用户权限(User Permissions)


为了查询Calendar Provider中的数据,您应在manifest文件中添加READ_CALENDAR权限,为了增、删、改 Calendar Provider中的数据,您应在manifest文件中添加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>

日历表(Calendars Table)


CalendarContract.Calendars表中包含了日历的详细信息。下面介绍了表中的字段信息。

字段(Constant) 描述(Description)
NAME 日历的名字
CALENDAR_DISPLAY_NAME 向用户展示的日历名字(The name of this calendar that is displayed to the user)
VISIBLE boolean类型,用于决定是否将日历展示给用户。若值为0表示与该日历绑定的事件不予显示。值为1则相反。该值直接影响了CalendarContract.Instances表中的内容
SYNC_EVENTS boolean类型,表示是否同步设备中的日历数据。

查询日历表(Querying a calendar)


下面演示了查询的示例,需要注意的是,您需要在异步线程中执行查询操作:


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

// 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[] {"sampleuser@gmail.com", "com.google",
        "sampleuser@gmail.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()) {
    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...

   ...
}

修改日历表(Modifying a calendar)


修改日历表实际上是修改表中的某一行或若干行数据,您可以直接在Uri中追加需要修改的行(调用withAppendedId()),或使用selection 筛选行,selection 应为_id=?形式,而selectionArg 是calendar表的_ID,示例如下:

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

事件表(Events Table)


CalendarContract.Events表记录了事件的具体信息,为了操作该表,您同样需要添加相应权限。

下面是Events 表中的字段信息:

字段(Constant) 描述(Description)
CALENDAR_ID Calender表的外键
ORGANIZER 事件的Email
TITLE 事件的标题
EVENT_LOCATION 事件的发生地点
DESCRIPTION 事件描述
DTSTART 事件起始时间
DTEND 事件结束时间
EVENT_TIMEZONE 事件发生的时区
EVENT_END_TIMEZONE 结束时间的时区
DURATION 事件持续时间,使用RFC5545 格式,如PT1H表示事件将持续一小时,P2W表示事件将持续两周
ALL_DAY boolean类型,表示是否为全天候事件,0表示“是全天候事件”,1表示“不是全天候事件”
RRULE 循环触发事件的条件,如:FREQ=WEEKLY;COUNT=10;WKST=SU,您可以参考这个例子
RDATE 事件循环的时间,您可以与RRULE一并使用
AVAILABILITY 将此事件视为忙碌时间还是可调度的空闲时间
GUESTS_CAN_MODIFY 来宾是否可修改事件
GUESTS_CAN_INVITE_OTHERS 是否可以邀请其他来宾
GUESTS_CAN_SEE_GUESTS 来宾是否可查看参加者列表

添加事件(Adding Events)


当您打算插入一条新的事件时,推荐使用INSERT Intent,当然您也可以直接插入事件,需要注意的事项如下:

  • 必须包含CALENDAR_IDDTSTART字段;

  • 必须包含EVENT_TIMEZONE字段,调用getAvailableIDs()方法获取时区的ID。如使用Intent插入事件,则系统会自动包含时区信息;

  • 若是一次性事件,则必须包含DTEND字段;

  • 对于一次性事件,应包含DURATION字段,而不应包含RRULE 或 RDATE字段,如使用Intent插入事件,则系统会自动包含事件的持续时间;


下面通过一个例子演示插入事件,您同样需要把操作放在异步线程中执行:

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执行其他日历操作,如在实践中加入参加者或提醒(to add attendees or reminders to an event)。


修改事件(Updating Events)


推荐使用EDIT Intent修改事件,但也可以手动修改,示例如下:

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

删除事件(Deleting Events)


示例如下:

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


CalendarContract.Attendees表的一行表示一个事件的参加者信息。调用query()方法将返回某个事件(表中的EVENT_ID字段为Event表的外键,该字段的值决定了参加者所属的事件)的参加者列表。
下面介绍了参加者表的字段信息:

字段(Constant) 描述(Description)
EVENT_ID Event表的外键
ATTENDEE_NAME 参加者的姓名
ATTENDEE_EMAIL 参加者的Email
ATTENDEE_RELATIONSHIP 参加者的职责,下列值之一:RELATIONSHIP_ATTENDEERELATIONSHIP_NONERELATIONSHIP_ORGANIZERRELATIONSHIP_PERFORMERRELATIONSHIP_SPEAKER
ATTENDEE_TYPE 参加者的重要程度,下列值之一:TYPE_REQUIREDTYPE_OPTIONAL
ATTENDEE_STATUS 参加者的状态,下列值之一:ATTENDEE_STATUS_ACCEPTEDATTENDEE_STATUS_DECLINEDATTENDEE_STATUS_INVITEDATTENDEE_STATUS_NONEATTENDEE_STATUS_TENTATIVE

添加参加者(Adding Attendees)


下面是一个添加参加者的示例,需要注意的是必须包含EVENT_ID字段:

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

提醒事项表(Reminders Table)


CalendarContract.Reminders表中的一行表示了某个事件的提醒事项,下面介绍该表的字段信息:

字段(Constant) 描述(Description)
EVENT_ID Event表的外键
MINUTES 事件发生前的分钟数,应在达到该时间时发出提醒
METHOD 服务器上设置的提醒方法。下列值之一:METHOD_ALERTMETHOD_DEFAULTMETHOD_EMAILMETHOD_SMS

添加提醒事项(Adding Reminders)


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


CalendarContract.Instances表中的每一行记录了一个事件的起讫时间,该表中的内容不可修改,只能查询:

字段(Constant) 描述(Description)
BEGIN 实例的开始时间,以协调世界时毫秒数表示
END 实例的结束时间,以协调世界时毫秒数表示
END_DAY 与日历时区相应的实例儒略历结束日(The Julian end day of the instance, relative to the Calendar’s time zone)
END_MINUTE 从日历时区午夜开始计算的实例结束时间(分钟)
EVENT_ID Event表的主键
START_DAY 与日历时区相应的实例儒略历开始日
START_MINUTE 从日历时区午夜开始计算的实例开始时间(分钟)

查询实例表(Querying the Instances table)


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(Calendar Intents)


使用Calendar Intents,您无需添加权限,就能访问Calendar Provider管理的表数据。
下面介绍了由Calendar Provider支持的Intent:

Action URI Description Extras
VIEW content://com.android.calendar/time/<ms_since_epoch> 打开日历后定位到 <ms_since_epoch> 指定的时间
VIEW content://com.android.calendar/events/<event_id> 查看 <event_id> 指定的事件 CalendarContract.EXTRA_EVENT_BEGIN_TIME CalendarContract.EXTRA_EVENT_END_TIME
EDIT content://com.android.calendar/events/<event_id> 编辑 <event_id> 指定的事件 CalendarContract.EXTRA_EVENT_BEGIN_TIME CalendarContract.EXTRA_EVENT_END_TIME
EDIT INSERT content://com.android.calendar/events 创建事件 下表列出的任一 Extra

Intent Extra Description
Events.TITLE 事件的名字
CalendarContract.EXTRA_EVENT_BEGIN_TIME 事件开始时间,以从公元纪年开始计算的毫秒数表示
CalendarContract.EXTRA_EVENT_END_TIME 事件结束时间,以从公元纪年开始计算的毫秒数表示
CalendarContract.EXTRA_EVENT_ALL_DAY boolean类型,0表示全天候事件,1表示非全天候事件
Events.EVENT_LOCATION 事件的地点
Events.DESCRIPTION 事件描述
Intent.EXTRA_EMAIL 受邀者Email地址列表,以逗号分隔
Events.RRULE 事件的重复发生规则
Events.ACCESS_LEVEL 事件是私人性质还是公共性质
Events.AVAILABILITY 将此事件视为忙碌时间还是可调度的空闲时间

使用Intent添加事件(Using an intent to insert an event)


当您在自己的应用中使用Intent启动日历应用程序时,应用会转到日历来完成事件添加操作,可以将预填充日历事件的详细信息附加在INSERT Intent中的extra 字段里,待程序切换至日历应用后,这些信息会自动填充至相应的栏目中,用户只需选择保存、编辑、取消 等选项即可。

下面通过一段代码演示了添加一条发生于2012年1月19日的待办事件,该事件的起始时间为上午7:30 ,结束时间为上午8:30:

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编辑事件(Using an intent to edit an event)


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查看日历信息(Using intents to view calendar data)


查看日历信息,有两种查看结果:

  • 查询指定日期的日历信息;

  • 查询指定日期的事件。


查看指定日期的日历信息如下:

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

同步Adapters(Sync Adapters)


使用同步Adapters访问Calendar Provider中数据的方式 与上述方式差别不大:

  • 同步Adapter需要通过将 CALLER_IS_SYNCADAPTER 设置为 true 来表明它是同步适配器;

  • 需提供 ACCOUNT_NAMEACCOUNT_TYPE 作为 URI 中的查询参数;

  • 与应用或小工具相比,同步适配器拥有写入权限的列更多。 例如,应用只能修改日历的少数几种特性, 例如其名称、显示名称、能见度设置以及是否同步日历。 相比之下,同步适配器不仅可以访问这些列,还能访问许多其他列, 例如日历颜色、时区、访问级别、地点等等。不过,同步适配器受限于它指定的 ACCOUNT_NAMEACCOUNT_TYPE


同步Adapter的示例如下:

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

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:97765次
    • 积分:2166
    • 等级:
    • 排名:第18066名
    • 原创:65篇
    • 转载:17篇
    • 译文:23篇
    • 评论:41条
    博客专栏
    最新评论