Android NDK开发详解用户数据和身份之日历提供程序概览

日历提供程序是用户日历事件的存储区。您可以使用 Calendar Provider API,对日历、事件、参加者、提醒等执行查询、插入、更新和删除操作。

Calendar Provider API 可供应用和同步适配器使用。规则视进行调用的程序类型而异。本文主要侧重于介绍如何将 Calendar Provider API 用作应用。如需了解对各类同步适配器差异的介绍,请参阅同步适配器。

正常情况下,如要读取或写入日历数据,则应用的清单文件必须包含用户权限中所述的适当权限。为简化常见操作的执行,日历提供程序提供了一组 Intent(如日历 Intent 中所述)。这些 Intent 会将用户带入日历应用,以便执行插入事件、查看事件和编辑事件。用户会与日历应用交互,然后返回原来的应用。因此,您的应用无需请求权限,也无需提供用于查看事件或创建事件的用户界面。

基础知识

内容提供程序用于存储数据,并使其可供应用访问。Android 平台提供的内容提供程序(包括日历提供程序)通常以一组基于关系型数据库模型的表格形式公开数据,表格内的每一行都是一条记录,每一列都是特定类型和含义的数据。应用和同步适配器可通过 Calendar Provider API 获得对储存用户日历数据的数据库表进行读取/写入的权限。

每个内容提供程序都会公开一个公共 URI(包装成 Uri 对象),从而对其数据集进行唯一标识。控制多个数据集(多个表)的内容提供程序会为每个数据集公开单独的 URI。所有提供程序 URI 都以字符串“content://”开头。这表示数据会受内容提供程序的控制。日历提供程序会为其每个类(表)定义 URI 常量。这些 URI 的格式为 .CONTENT_URI。例如,Events.CONTENT_URI。

图 1 是对日历提供程序数据模型的图形化表示。该图展示将彼此链接在一起的主要表和字段。
日历提供程序数据模型
在这里插入图片描述

图 1. 日历提供程序数据模型。

用户可拥有多个日历,并且可将不同日历与不同类型的帐户(Google 日历、Exchange 等)进行关联。

CalendarContract 定义了日历和事件相关信息的数据模型。这些数据存储在以下所列的若干表中。
在这里插入图片描述

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 参考资料。
在这里插入图片描述

包括适用于所有操作的帐户类型

如果您查询 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: In = 1
private const val PROJECTION_DISPLAY_NAME_INDEX: In = 2
private const val PROJECTION_OWNER_ACCOUNT_INDEX: In = 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 对象,以遍历数据库查询返回的结果集。如需查看有关在内容提供程序中使用查询的详细介绍,请参阅内容提供程序。
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:向 URI 追加的 ID (withAppendedId()),或第一个选定项。选定范围应以 “_id=?” 开头,并且第一个 selectionArg 应为日历的 _ID。您还可在 URI 中对 ID 进行编码,从而执行更新。下例使用 (withAppendedId()) 方法更改日历的显示名称:
Kotlin

kconst 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 权限。

应用和同步适配器均可写入以下事件列。如需查看所支持字段的完整列表,请参阅 CalendarContract.Events 参考资料。
在这里插入图片描述

添加事件

当应用插入新事件时,我们建议您按使用 Intent 插入事件中所述使用 INSERT Intent。不过,如有需要,您也可直接插入事件。本部分描述如何执行此操作。

以下是插入新事件的规则:

您必须加入 CALENDAR_ID 和 DTSTART。
您必须加入 EVENT_TIMEZONE。如需获取系统中已安装时区 ID 的列表,请使用 getAvailableIDs()。请注意,如果您按使用 Intent 插入事件中所述通过 INSERT Intent 插入事件,则此规则不适用 — 在该情形下,系统会提供默认时区。
对于非重复事件,您必须加入 DTEND。
对于重复事件,您必须加入 DURATION,以及 RRULE 或 RDATE。请注意,如果您按使用 Intent 插入事件中所述通过 INSERT 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 来执行其他日历操作 — 例如,向事件添加参加者或提醒。

更新事件

当应用想允许用户编辑事件时,我们建议您按使用 Intent 编辑事件中所述使用 EDIT Intent。不过,如有需要,您也可直接编辑事件。如要执行事件更新,您可以下方形式提供事件的 _ID:向 URI 追加的 ID (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 不可或缺:
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,了解详细信息。
在这里插入图片描述

添加提醒

下例展示如何为事件添加提醒。提醒会在事件发生前 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 定义。
在这里插入图片描述

查询实例表

如要查询实例表,您需在 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:
在这里插入图片描述

下表列出了日历提供程序支持的 Intent Extra:
在这里插入图片描述

下文描述如何使用这些 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 字段为表单预填充事件的时间。这些时间的值必须以从公元纪年开始计算的毫秒数(协调世界时)表示。
它使用 Intent.EXTRA_EMAIL extra 字段提供以逗号分隔的受邀者电子邮件地址列表。

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,则没有事件编辑权限的应用便可将事件编辑操作转由日历应用执行。在日历中完成事件编辑后,用户便会返回原来的应用。

以下是一个 Intent 示例,该 Intent 为指定事件设置新名称,并允许用户在日历中编辑事件。
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,以表明它是同步适配器。
同步适配器需提供 ACCOUNT_NAME 和 ACCOUNT_TYPE,作为 URI 中的查询参数。
与应用或微件相比,同步适配器拥有写入权限的列更多。例如,应用只能修改日历的少数几种特性,例如其名称、显示名称、可见性设置以及是否同步日历。相比之下,同步适配器不仅可访问这些列,还能访问许多其他列,例如日历颜色、时区、访问级别、地点等。不过,同步适配器受限于其指定的 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();
 }

如需查看同步适配器的实现示例(并非仅限与日历有关的实现),请参阅 SampleSyncAdapter。

本页面上的内容和代码示例受内容许可部分所述许可的限制。Java 和 OpenJDK 是 Oracle 和/或其关联公司的注册商标。

最后更新时间 (UTC):2021-10-27。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

五一编程

程序之路有我与你同行

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值