日曆供應程式總覽

日曆提供者是使用者日曆活動的存放區。您可以使用 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 會定義日曆和活動相關資訊的資料模型。這項資料會儲存在下列多個資料表中:

Table (類別) 說明

CalendarContract.Calendars

此表含有日曆專屬資訊。這個資料表的每個資料列都包含單一日曆的詳細資料,例如名稱、顏色、同步處理資訊等。
CalendarContract.Events 這個表格會儲存事件專屬資訊。這個表格中的每一列都包含單一活動的資訊,例如活動標題、地點、開始時間、結束時間等等。事件可以發生一次,也可以重複發生多次。參與者、提醒和擴充屬性會儲存在不同的資料表中。每個事件都包含一個 EVENT_ID,可參照「事件」表格中的 _ID
CalendarContract.Instances 這個資料表會保存每個事件發生的開始和結束時間。這個表格中的每一列都代表單一事件發生情形。對於一次性事件,會將例項與事件進行 1:1 對應。如果是週期性事件,系統會自動產生多個資料列,這些資料列會對應至該事件的多次。
CalendarContract.Attendees 這個資料表會儲存活動與會者 (嘉賓) 的資訊。每列都代表單一事件的一位邀請對象。這項資訊會指定邀請對象的類型,以及該邀請對象對活動的出席回覆。
CalendarContract.Reminders 這個資料表會儲存警示/通知資料。每一列都代表一項事件的單一警示。一個活動可以有多個提醒。每個活動的提醒次數上限會在 MAX_REMINDERS 中指定,由擁有指定日曆的同步處理器設定。提醒時間會以事件發生前幾分鐘為單位指定,並提供方法來決定使用者收到的通知方式。

Calendar Provider API 的設計旨在提供靈活且強大的功能。同時,請務必提供良好的使用者體驗,並保護日曆及其資料的完整性。因此,使用 API 時,請注意下列事項:

  • 插入、更新及查看日曆活動。如要直接在日曆供應器中插入、修改及讀取事件,您必須具備適當的權限。不過,如果您並未建構完整的日曆應用程式或同步處理轉接器,則不必要求這些權限。您可以改用 Android 日曆應用程式支援的意圖,將讀取和寫入作業交給該應用程式。使用意圖時,應用程式會將使用者傳送至日曆應用程式,在預先填妥的表單中執行所需作業。完成後,系統會將他們帶回您的應用程式。您可以設計應用程式,透過日曆執行常見作業,為使用者提供一致且健全的使用者介面。此為建議做法。詳情請參閱「日曆意圖」。
  • 同步轉換介面同步轉換介面會將使用者裝置上的日曆資料與其他伺服器或資料來源同步處理。在 CalendarContract.CalendarsCalendarContract.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 參考資料。

常數 說明
NAME 日曆的名稱。
CALENDAR_DISPLAY_NAME 向使用者顯示的這個日曆名稱。
VISIBLE 布林值,表示是否要顯示該日曆。值為 0 表示不應顯示與此日曆相關聯的活動。如果值為 1,表示系統應顯示與此日曆相關聯的活動。這個值會影響 CalendarContract.Instances 資料表中資料列的產生方式。
SYNC_EVENTS 布林值,表示是否要同步處理日曆,並將日曆的事件儲存在裝置上。值為 0 表示不要同步這份日曆或在裝置上儲存其活動。值為 1 表示要為這個日曆同步事件,並將事件儲存在裝置上。

為所有作業納入帳戶類型

如果您對 Calendars.ACCOUNT_NAME 進行查詢,則必須在選取範圍中加入 Calendars.ACCOUNT_TYPE。這是因為系統會根據 ACCOUNT_NAMEACCOUNT_TYPE 判斷帳戶是否唯一。ACCOUNT_TYPE 是與帳戶驗證器相對應的字串,該驗證器是在帳戶註冊至 AccountManager 時使用。另外,如果日曆未與裝置帳戶建立關聯,也會有一種稱為 ACCOUNT_TYPE_LOCAL 的特殊帳戶。ACCOUNT_TYPE_LOCAL 帳戶不會同步處理。

查詢日曆

以下範例說明如何取得特定使用者擁有的 Google 日曆。為了簡化說明,本例會在使用者介面執行緒 (「主執行緒」) 中顯示查詢作業。實際上,這項操作應在非同步執行緒中執行,而不是在主執行緒中執行。如需進一步討論,請參閱「載入器」。如果不只是讀取資料,而是要修改資料,請參閱 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」的 Google 日曆、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_LOCALACCOUNT_TYPE,以同步轉換介面執行日曆插入作業。ACCOUNT_TYPE_LOCAL 是一種特殊帳戶類型,用於未與裝置帳戶建立關聯的日曆。這類型日曆不會同步處理至伺服器。如要瞭解同步轉換介面,請參閱「同步轉換介面」。

事件資料表

CalendarContract.Events 表格包含個別事件的詳細資料。如要新增、更新或刪除事件,應用程式必須在資訊清單檔案中加入 WRITE_CALENDAR 權限。

下列「事件」欄可供應用程式和同步處理轉接器寫入。如需支援欄位的完整清單,請參閱 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"。如需更多範例,請參閱這篇文章
RDATE 活動的週期日期。 您通常會將 RDATERRULE 搭配使用,定義一組重複出現的項目。如需進一步討論,請參閱 RFC5545 規格
AVAILABILITY 如果這個活動顯示為忙碌的時間或可預約的有空時間,
GUESTS_CAN_MODIFY 邀請對象是否可以修改活動。
GUESTS_CAN_INVITE_OTHERS 邀請對象是否可以邀請其他人。
GUESTS_CAN_SEE_GUESTS 邀請對象是否可查看與會者名單。

新增事件

應用程式插入新事件時,建議您使用 INSERT 意圖,如「使用意圖插入事件」所述。不過,您可以視需要直接插入事件。本節將說明如何執行這項操作。

插入新事件的規則如下:

  • 您必須加入 CALENDAR_IDDTSTART
  • 您必須加入 EVENT_TIMEZONE。如要取得系統已安裝的時區 ID 清單,請使用 getAvailableIDs()。請注意,如果您是透過 INSERT 意圖插入事件,則此規則不適用。如需相關說明,請參閱「使用意圖插入事件」一文。在這種情況下,系統會提供預設時區。
  • 如為非週期性事件,則必須加入 DTEND
  • 如果是週期性活動,除了 RRULERDATE 以外,您還必須加入 DURATION。請注意,如果您是透過 INSERT 意圖插入事件 (請參閱「使用意圖插入事件」),則不適用這項規則。在這種情況下,您可以將 RRULEDTSTARTDTEND 搭配使用,日曆應用程式會自動將其轉換為時間長度。

以下是插入事件的範例。為求簡單起見,這項操作會在 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,做為 URI (withAppendedId()) 的附加 ID,或做為第一個選取項目。這個選項應以 "_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,您也無法進行選取。刪除有兩種版本:分別是應用程式和同步處理轉換器。應用程式刪除作業會將「已刪除」欄設為 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_TYPEKEY_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。如需使用此意圖的範例,請參閱「使用意圖插入事件」。
建立活動。 下表列出的任何額外內容。

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

意圖額外資料 說明
Events.TITLE 事件的名稱。
CalendarContract.EXTRA_EVENT_BEGIN_TIME 事件開始時間,以 Epoch 紀元時間起算的毫秒數為單位。
CalendarContract.EXTRA_EVENT_END_TIME 事件結束時間,以 Epoch 紀元時間起算的毫秒數。
CalendarContract.EXTRA_EVENT_ALL_DAY 布林值,指出活動是否為全天活動。值可以是 truefalse
Events.EVENT_LOCATION 活動的地點。
Events.DESCRIPTION 活動說明。
Intent.EXTRA_EMAIL 邀請對象的電子郵件地址,以半形逗號分隔。
Events.RRULE 事件週期性規則。
Events.ACCESS_LEVEL 活動是否為私人或公開活動。
Events.AVAILABILITY 這項活動是否會計入忙碌時間,或是可預約的空閒時間。

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

使用意圖插入事件

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

當使用者執行採用這種做法的應用程式時,應用程式會將使用者傳送至日曆,以便完成新增活動。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 意圖可讓沒有權限的應用程式將活動編輯權限交給日曆應用程式。使用者完成日曆活動編輯作業後,就會返回原本的應用程式。

以下的意圖範例會設定指定活動的新標題,並讓使用者在日曆中編輯活動。

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 意圖的方式:

  • 如要開啟日曆並前往特定日期。
  • 查看事件。

以下範例說明如何開啟日曆,並將日期設為特定日期:

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