日曆供應程式總覽

日曆供應器是使用者日曆活動的存放區。Calendar Provider API 可讓您對日曆、活動、與會者和提醒等項目執行查詢、插入、更新和刪除作業。

應用程式和同步轉換介面可以使用 Calendar Provider API。規則會因發出呼叫的程式類型而異。本文主要著重於以應用程式的形式使用 Calendar Provider API。如要瞭解同步轉換介面的差異,請參閱「同步轉換介面」。

一般來說,如要讀取或寫入日曆資料,應用程式的資訊清單必須包含適當權限,詳情請參閱「使用者授權」。為了讓您更輕鬆地執行常見作業,日曆供應器提供了一組意圖,如「日曆意圖」一節所述。這些意圖會將使用者導向日曆應用程式,以便插入、查看及編輯活動。使用者與日曆應用程式互動,然後返回原始應用程式。因此,應用程式不需要要求權限,也不需要提供使用者介面來查看或建立事件。

基本資訊

內容供應器會儲存資料,並開放應用程式存取。Android 平台提供的內容供應器 (包括日曆供應器) 通常會根據關聯式資料庫模型,以一組資料表的形式公開資料,其中每個資料列都是一筆記錄,每個資料欄都是特定類型和含義的資料。透過 Calendar Provider API,應用程式和同步轉換介面可以取得資料庫資料表的讀取/寫入權限,其中包含使用者的日曆資料。

每個內容供應器都會公開一個公開 URI (包裝為 Uri 物件),用於唯一識別其資料集。如果內容供應器控制多個資料集 (多個資料表),則會為每個資料集公開不同的 URI。供應器的所有 URI 都以「content://」字串開頭。這會將資料識別為由內容供應器控管。日曆供應器會為每個類別 (資料表) 的 URI 定義常數。這些 URI 的格式為 <class>.CONTENT_URI。例如:Events.CONTENT_URI。

圖 1 顯示日曆供應器資料模型的圖形表示法。其中顯示主要資料表,以及連結這些資料表的欄位。

日曆供應程式資料模型
圖 1. 日曆供應程式資料模型。

使用者可以有多個日曆,不同的日曆可以與不同類型的帳戶 (Google 日曆、Exchange 等) 建立關聯。

CalendarContract 定義日曆和活動相關資訊的資料模型。這類資料會儲存在多個資料表中,如下所示。

資料表 (類別) 說明

CalendarContract.Calendars

這個資料表會保存日曆專屬資訊。這個資料表的每一列都包含單一日曆的詳細資料,例如名稱、顏色和同步資訊。
CalendarContract.Events 這個資料表會保存事件專屬資訊。這個表格中的每一列都包含單一事件的資訊,例如事件標題、地點、開始時間、結束時間。事件可以是一次性或重複發生多次。與會者、提醒和擴充屬性會儲存在不同的資料表中。兩者都有一個 EVENT_ID,參照 Events 資料表中的 _ID。
CalendarContract.Instances 這個資料表會記錄每個事件的開始和結束時間。這個表格中的每一列都代表一個事件。對於一次性事件,執行個體與事件之間存在 1:1 的對應關係。如果是週期性活動,系統會自動產生多個資料列,對應到該活動的多個發生時間。
CalendarContract.Attendees 這個資料表會保存活動與會者 (邀請對象) 資訊。每一列代表活動的一位邀請對象。指定邀請對象類型和邀請對象對活動的回覆。
CalendarContract.Reminders 這個資料表會保存快訊/通知資料。每一列代表一個事件的單一警告。一個活動可以有多個提醒。每個活動的提醒數量上限由 MAX_REMINDERS 指定,並由擁有指定日曆的同步轉換介面設定。提醒會以活動開始前的分鐘數指定,並提供決定使用者收到提醒方式的方法。

Calendar Provider API 具有彈性且功能強大。同時,也必須提供良好的使用者體驗,並保護日曆和資料的完整性。因此,使用 API 時請留意下列事項:

  • 插入、更新及查看日曆活動。如要直接從日曆供應器插入、修改及讀取事件,您必須具備適當的權限。不過,如果您並非建構完整的日曆應用程式或同步轉換介面,就不需要要求這些權限。您可以改用 Android 日曆應用程式支援的意圖,將讀取和寫入作業交給該應用程式。使用意圖時,應用程式會將使用者導向 Google 日曆應用程式,在預填表單中執行所選操作。完成後,系統會將他們導回您的應用程式。只要將應用程式設計為透過日曆執行常見作業,就能為使用者提供一致、穩定的使用者介面。這是建議做法。詳情請參閱「日曆意圖」。
  • 同步轉換介面:同步轉換介面會將使用者裝置上的日曆資料,與其他伺服器或資料來源同步。在 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 資料表包含個別日曆的詳細資料。應用程式和同步轉換介面皆可寫入下列 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 物件,可用來遍歷資料庫查詢傳回的結果集。如要進一步瞭解如何在內容供應器中使用查詢,請參閱「內容供應器」。

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 事件開始時間,以毫秒為單位,自 Epoch 紀元時間起算 (採用世界標準時間)。
DTEND 事件結束時間,以毫秒為單位,自 Epoch 紀元時間起算 (採用世界標準時間)。
EVENT_TIMEZONE 活動的時區。
EVENT_END_TIMEZONE 活動結束時間的時區。
DURATION 活動時間長度,採用 RFC5545 格式。舉例來說,"PT1H" 值表示事件應持續一小時,"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 意圖,如「使用意圖插入活動」一節所述。不過,如有需要,您也可以直接插入事件。本節將說明如何執行這項操作。

插入新事件的規則如下:

  • 您必須加入 CALENDAR_ID 和 DTSTART。
  • 您必須加入 EVENT_TIMEZONE。如要取得系統安裝的時區 ID 清單,請使用 getAvailableIDs()。請注意,如果您是透過 INSERT 意圖插入活動 (如「使用意圖插入活動」一節所述),則不適用這項規則,因為在這種情況下,系統會提供預設時區。
  • 如果是非週期性事件,則必須包含 DTEND。
  • 如果是週期性活動,除了 RRULE 或 RDATE 之外,還必須加入 DURATION。請注意,如果您是透過 INSERT 意圖插入活動 (如「使用意圖插入活動」一節所述),則不適用這項規則。在這種情況下,您可以將 RRULE 與 DTSTART 和 DTEND 搭配使用,日曆應用程式會自動將其轉換為持續時間。

以下是插入活動的範例。為求簡單,這項作業會在 UI 執行緒中執行。在實際操作中,插入和更新作業應在非同步執行緒中完成,以便將動作移至背景執行緒。詳情請參閱 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 意圖,如「使用意圖編輯事件」一節所述。不過,您也可以視需要直接編輯活動。如要更新事件,您可以將事件的 _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);

刪除活動

您可以透過 URI 附加 ID 的 _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()));
    }
 }

日曆意圖

應用程式不需要權限即可讀取及寫入日曆資料。而是使用 Android 日曆應用程式支援的意圖,將讀取和寫入作業交給該應用程式。下表列出日曆供應器支援的意圖:

動作 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
EDIT

INSERT

content://com.android.calendar/events

您也可以使用 Events.CONTENT_URI 參照 URI。如需使用這個意圖的範例,請參閱「使用意圖插入事件」。
建立活動。 下表列出的任何額外項目。

下表列出日曆供應器支援的意圖額外項目:

Intent Extra 說明
Events.TITLE 事件名稱。
CalendarContract.EXTRA_EVENT_BEGIN_TIME 事件開始時間 (以從 Epoch 紀元時間算起的毫秒數表示)。
CalendarContract.EXTRA_EVENT_END_TIME 事件結束時間 (以從 Epoch 紀元時間算起的毫秒數表示)。
CalendarContract.EXTRA_EVENT_ALL_DAY 布林值,表示事件是否為全天活動。值可以是 true 或 false。
Events.EVENT_LOCATION 活動的地點。
Events.DESCRIPTION 事件說明。
Intent.EXTRA_EMAIL 邀請對象的電子郵件地址,以逗號分隔的清單。
Events.RRULE 活動的重複規則。
Events.ACCESS_LEVEL 活動是私人或公開。
Events.AVAILABILITY 這項活動是否計為忙碌時間,或是否為可安排其他活動的空閒時間。

以下各節將說明如何使用這些意圖。

使用意圖插入活動

使用 INSERT 意圖可讓應用程式將事件插入工作交給日曆本身。採用這種做法時,應用程式甚至不需要在資訊清單檔案中加入 WRITE_CALENDAR 權限。

使用者執行採用這種方法的應用程式時,應用程式會將他們導向 Google 日曆,完成新增活動的程序。INSERT 意圖會使用額外欄位,在表單中預先填入日曆中的活動詳細資料。使用者可以取消活動、視需要編輯表單,或將活動儲存到日曆。

以下程式碼片段會安排在 2012 年 1 月 19 日上午 7:30 到 8:30 執行事件。請注意,這段程式碼片段:

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

使用意圖編輯事件

您可以直接更新事件,詳情請參閱「更新事件」。但使用 EDIT 意圖,可讓沒有權限的應用程式將活動編輯作業交給日曆應用程式。使用者在 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);

使用意圖查看日曆資料

日曆供應器提供兩種使用 VIEW 意圖的方式:

  • 開啟 Google 日曆並前往特定日期。
  • 查看活動。

以下範例說明如何開啟日曆並跳到特定日期:

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 做為查詢參數。
  • 同步轉換介面可寫入的資料欄比應用程式或小工具多。舉例來說,應用程式只能修改日曆的少數特徵,例如名稱、顯示名稱、瀏覽權限設定,以及日曆是否同步。相較之下,同步轉換介面不僅能存取這些資料欄,還能存取許多其他資料欄,例如日曆顏色、時區、存取層級和位置。不過,同步轉換介面僅限於指定的 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();
 }