日曆供應程式總覽

日曆提供者是使用者日曆活動的存放區。您可以透過 Calendar Provider API 來查詢、插入、更新及刪除日曆、活動、參與者、提醒等事項。

應用程式和同步轉換介面都能使用 Calendar Provider API。規則會因呼叫的程式類型而異。本文主要著重於使用 Calendar Provider API 做為應用程式。如需同步轉換介面差異的討論,請參閱同步轉換介面

一般而言,如要讀取或寫入日曆資料,應用程式的資訊清單必須包含適當的權限,如使用者權限中所述。為方便您執行常見作業,日曆供應程式提供一組意圖,如日曆意圖中所述。這些意圖會將使用者導向 Google 日曆應用程式以插入、檢視和編輯事件。使用者與日曆應用程式互動,然後返回原始應用程式。因此,應用程式不需要要求權限,也不需要提供使用者介面來查看或建立事件。

基本資訊

內容供應器會儲存資料,並讓應用程式存取。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 這個資料表含有事件專屬資訊。這個資料表中的每一列都有單一事件的資訊,例如活動標題、地點、開始時間、結束時間等。活動可以一次性出現,也可以重複發生。參與者、提醒和擴充屬性會儲存在不同的資料表中。而且每個項目都有參照「事件」資料表中 _IDEVENT_ID
CalendarContract.Instances 此資料表保存了每次事件的開始和結束時間。這個表格中的每一列都代表一次事件發生。針對一次性事件,系統會對執行個體與事件進行 1:1 的對應關係。如為週期性活動,系統會自動產生多個資料列,以便對應至該事件的多次發生。
CalendarContract.Attendees 這個資料表保存了活動參與者 (訪客) 資訊。每一列都代表一個事件的單一邀請對象。用於指定邀請對象類型,以及活動的出席狀況回應。
CalendarContract.Reminders 這個資料表會保留快訊/通知資料。每一列都代表特定事件的單一快訊。一個活動可以設定多個提醒。MAX_REMINDERS 中指定的每個事件提醒數量上限,其由擁有指定日曆的同步轉換介面設定。提醒是在事件前幾分鐘的指定時間,且有一種方法可決定使用者的提醒方式。

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

  • 插入、更新及查看日曆活動。如要直接插入、修改及讀取來自日曆供應者的活動,您必須具備合適的權限。不過,如果你沒有建構完善的日曆應用程式或同步轉換介面,則不必要求這些權限。您可以改用 Android 日曆應用程式支援的意圖,將讀取和寫入作業傳送至該應用程式。使用意圖時,應用程式會將使用者傳送至日曆應用程式,在預先填入的表單中執行所需作業。完成後,就會傳回您的應用程式。藉由設計應用程式來執行 Google 日曆的一般作業,您就能為使用者提供一致、穩固的使用者介面。這是建議做法。詳情請參閱日曆意圖
  • 同步處理轉換介面。同步轉換介面會將使用者裝置上的日曆資料與其他伺服器或資料來源同步處理。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 帳戶不會同步。

查詢日曆

以下範例說明如何取得特定使用者擁有的日曆。為求簡單起見,在此範例中,查詢作業會顯示在使用者介面執行緒 (「主執行緒」) 中。實際上,這項操作應該在非同步執行緒 (而非主執行緒) 中完成。詳情請參閱「載入器」。如果您不只是讀取資料而要修改資料,請參閱 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 做為 URI 的附加 ID (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 意圖,如「使用意圖插入事件」一文所述。不過,如有需要,您可以直接插入事件。本節說明如何執行這項操作。

以下是插入新事件的規則:

以下示範如何插入事件。為了方便起見,系統會在 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 的附加 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,則無法同時選取其他 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_TYPEKEY_TIMEZONE_INSTANCES 定義。

常數 說明
BEGIN 執行個體的開始時間,以世界標準時間為準。
END 執行個體的結束時間,以世界標準時間為準。
END_DAY 以日曆時區為準的執行個體七月結束日。
END_MINUTE 從日曆時區的午夜算起,執行個體的結束時間 (分鐘)。
EVENT_ID 此例項的事件 _ID
START_DAY 執行個體的凱利安起始日 (以日曆的時區為準)。
START_MINUTE 執行個體開始時間 (以午夜為準) (相對於日曆的時區)。

查詢執行個體資料表

如要查詢執行個體資料表,您需要在 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 權限。

當使用者執行使用此方法的應用程式時,應用程式會將應用程式傳送至 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,表明這是同步轉換介面。
  • 同步轉換介面必須提供 ACCOUNT_NAMEACCOUNT_TYPE 做為 URI 中的查詢參數。
  • 同步轉換介面的寫入權限比應用程式或小工具多了更多欄。舉例來說,應用程式只能修改日曆的某些特性,例如名稱、顯示名稱、瀏覽權限設定,以及日曆是否保持同步。相較之下,同步轉接器不僅可存取這些資料欄,還能存取日曆顏色、時區、存取層級、位置等眾多資料欄。不過,同步轉換介面受限於指定的 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();
 }