CalendarProviderを使ってGoogleカレンダーにアクセスする


AndroidアプリからCalendarProviderを使って、Googleカレンダーに登録されているイベントを読み取る方法を紹介します。

# User Permissions

まず、カレンダーの読み込みに必要なパーミッションを設定します。

```xml:AndroidManifets.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.xxx">
    ...
    <uses-permission android:name="android.permission.READ_CALENDAR" />
    ...
</manifest>
```

* Android 6.0 (Marshmallow) 以降もサポートする際には、追加でRuntime Permissionへの対応が必要です。


# カレンダーの一覧取得

## カレンダーから取得可能な情報

個々のカレンダーから取得可能な情報は、下記を参照してください。
- CalendarContract.java
- APIリファレンス: [CalendarContract.Calendars](https://developer.android.com/reference/android/provider/CalendarContract.Calendars.html?hl=ja)

以下に、個々のカレンダーから取得可能な情報のうち、主なものを列挙します。

| Constant | Type | Description |
|:---------|:-----|:------------|
| _ID  |  INTEGER (long)| unique ID|
| NAME | TEXT | カレンダーの名前 |
| ACCOUNT_NAME | TEXT | 同期の際に使用されるアカウント |
| ACCOUNT_TYPE | TEXT | 同期の際に使用されるアカウントの種別 |
| CALENDAR_COLOR | INTEGER (color value) | カレンダーの色<br>(イベントのデフォルトの表示色)|
| CALENDAR_DISPLAY_NAME | TEXT | ユーザーに表示されるカレンダーの名前 |
| CALENDAR_ACCESS_LEVEL | INTEGER <br> (one of the values below) | カレンダーに対するアクセスレベル |
| CALENDAR_TIME_ZONE | TEXT | カレンダーのタイムゾーン|
| VISIBLE | INTEGER (boolean) | カレンダーが表示対象か否か <br> ・ 値 0 の場合、このカレンダーのイベントを表示しない <br> ・値 1 の場合、このカレンダーのイベントを表示する |
| SYNC_EVENTS | INTEGER (boolean) | カレンダーを同期し、イベントを端末に保存するか否か <br> ・ 値 0 の場合、同期せず、イベントを端末に保存しない <br> ・値 1 の場合、同期してイベントを端末に保存する|
| OWNER_ACCOUNT | TEXT | カレンダーの所有者のアカウント |


なお、CALENDAR_ACCESS_LEVEL は以下のいずれかの値をとります。

| Constant  | Value | Description |
|:-----------|------:|:-------------|
| CAL_ACCESS_NONE | 0 | カレンダーにアクセスできない |
| CAL_ACCESS_FREEBUSY | 100 | カレンダー内の空きの有無を確認可能 |
| CAL_ACCESS_READ | 200 | 全てのイベントの詳細が確認可能 |
| CAL_ACCESS_RESPOND | 300 | イベントに対してyes/no/maybeで返信可能 |
| CAL_ACCESS_OVERRIDE | 400 | ※使用されていない |
| CAL_ACCESS_CONTRIBUTOR | 500 | アクセス権以外の全ての編集が可能 |
| CAL_ACCESS_EDITOR | 600 |  アクセス権以外の全ての編集が可能 |
| CAL_ACCESS_OWNER | 700 | カレンダーに対して全ての操作が可能 |
| CAL_ACCESS_ROOT | 800 | ドメインの管理者 |


## カレンダーの一覧取得

まず、"android.provider.CalendarContract.Calendars"を static import しておきます。
"CalendarContract.Calendars.xxx"のままでは長いため、"Calendars.xxx"でアクセスできるようにします。

```java
import static android.provider.CalendarContract.Calendars;
```

次に、個々のカレンダーから取得したいプロパティを列挙します。

```java
    // プロジェクション配列。
    // 取得したいプロパティの一覧を指定する。
    private static final String[] CALENDAR_PROJECTION = new String[] {
            Calendars._ID,
            Calendars.NAME,
            Calendars.ACCOUNT_NAME,
            Calendars.ACCOUNT_TYPE,
            Calendars.CALENDAR_COLOR,
            Calendars.CALENDAR_DISPLAY_NAME,
            Calendars.CALENDAR_ACCESS_LEVEL,
            Calendars.CALENDAR_TIME_ZONE,
            Calendars.VISIBLE,
            Calendars.SYNC_EVENTS,
            Calendars.OWNER_ACCOUNT,
    };
    
    // プロジェクション配列のインデックス。
    // パフォーマンス向上のために、動的に取得せずに、静的に定義しておく。
    private static final int CALENDAR_PROJECTION_IDX_ID = 0;
    private static final int CALENDAR_PROJECTION_IDX_NAME = 1;
    private static final int CALENDAR_PROJECTION_IDX_ACCOUNT_NAME = 2;
    private static final int CALENDAR_PROJECTION_IDX_ACCOUNT_TYPE = 3;
    private static final int CALENDAR_PROJECTION_IDX_CALENDAR_COLOR = 4;
    private static final int CALENDAR_PROJECTION_IDX_CALENDAR_DISPLAY_NAME = 5;
    private static final int CALENDAR_PROJECTION_IDX_CALENDAR_ACCESS_LEVEL = 6;
    private static final int CALENDAR_PROJECTION_IDX_CALENDAR_TIME_ZONE = 7;
    private static final int CALENDAR_PROJECTION_IDX_VISIBLE = 8;
    private static final int CALENDAR_PROJECTION_IDX_SYNC_EVENTS = 9;
    private static final int CALENDAR_PROJECTION_IDX_OWNER_ACCOUNT = 10;
```

最後に、クエリを発行してカーソルを取得し、カーソルから個々のカレンダーの情報を取得します。
以下はCSV形式で、logcatに出力します。

```java
        // クエリ条件を設定する
        final Uri uri = CalendarContract.Calendars.CONTENT_URI;
        final String[] projection = CALENDAR_PROJECTION;
        final String selection = null;
        final String[] selectionArgs = null;
        final String sortOrder = null;

        // クエリを発行してカーソルを取得する
        final ContentResolver cr = getContentResolver();
        final Cursor cur = cr.query(uri, projection, selection, selectionArgs, sortOrder);

        // ログ出力 (Header)
        final StringBuilder sbHeader = new StringBuilder();
        for (final String property : CALENDAR_PROJECTION) {
            sbHeader.append(property).append(',');
        }
        Log.d(TAG, sbHeader.toString());

        while (cur.moveToNext()) {
            // カーソルから各プロパティを取得する
            final long id = cur.getLong(CALENDAR_PROJECTION_IDX_ID);
            final String name = cur.getString(CALENDAR_PROJECTION_IDX_NAME);
            final String accountName = cur.getString(CALENDAR_PROJECTION_IDX_ACCOUNT_NAME);
            final String accountType = cur.getString(CALENDAR_PROJECTION_IDX_ACCOUNT_TYPE);
            final int calendarColor = cur.getInt(CALENDAR_PROJECTION_IDX_CALENDAR_COLOR);
            final String calendarDisplayName = cur.getString(CALENDAR_PROJECTION_IDX_CALENDAR_DISPLAY_NAME);
            final int calendarAccessLevel = cur.getInt(CALENDAR_PROJECTION_IDX_CALENDAR_ACCESS_LEVEL);
            final String calendarTimeZone = cur.getString(CALENDAR_PROJECTION_IDX_CALENDAR_TIME_ZONE);
            final int visible = cur.getInt(CALENDAR_PROJECTION_IDX_VISIBLE);
            final int syncEvents = cur.getInt(CALENDAR_PROJECTION_IDX_SYNC_EVENTS);
            final String ownerAccount = cur.getString(CALENDAR_PROJECTION_IDX_OWNER_ACCOUNT);

            // ログ出力 (Body)
            final StringBuilder sbBody = new StringBuilder();
            sbBody.append(id).append(',');
            sbBody.append(name).append(',');
            sbBody.append(accountName).append(',');
            sbBody.append(accountType).append(',');
            sbBody.append(String.format("0x%08X", calendarColor)).append(',');
            sbBody.append(calendarDisplayName).append(',');
            sbBody.append(calendarAccessLevel).append(',');
            sbBody.append(calendarTimeZone).append(',');
            sbBody.append(visible).append(',');
            sbBody.append(syncEvents).append(',');
            sbBody.append(ownerAccount).append(',');
            Log.d(TAG, sbBody.toString());
        }
```

## カレンダーの一覧の取得結果

```csv
_id,name,account_name,account_type,calendar_color,calendar_displayName,calendar_access_level,calendar_timezone,visible,sync_events,ownerAccount
1,aaa@gmail.com,aaa@gmail.com,com.google,0xFF9A9CFF,aaa@gmail.com,700,Asia/Tokyo,1,1,aaa@gmail.com
2,共有カレンダー,aaa@gmail.com,com.google,0xFFB99AFF,共有カレンダー,700,Asia/Tokyo,1,1,xxxyyyzzz@group.calendar.google.com
3,Contacts,aaa@gmail.com,com.google,0xFF92E1C0,Contacts,200,Asia/Tokyo,1,1,#contacts@group.v.calendar.google.com
4,日本の祝日,aaa@gmail.com,com.google,0xFF16A765,日本の祝日,200,Asia/Tokyo,1,1,ja.japanese#holiday@group.v.calendar.google.com
5,bbb@gmail.com,bbb@gmail.com,com.google,0xFF9FE1E7,bbb@gmail.com,700,Asia/Tokyo,1,1,bbb@gmail.com
6,Contacts,bbb@gmail.com,com.google,0xFF9A9CFF,Contacts,200,Asia/Tokyo,1,1,#contacts@group.v.calendar.google.com
7,日本の祝日,bbb@gmail.com,com.google,0xFF9A9CFF,日本の祝日,200,Asia/Tokyo,1,1,ja.japanese#holiday@group.v.calendar.google.com
8,docomo,docomo,com.android.nttdocomo,0xFF182C57,com.android.nttdocomo,700,Asia/Tokyo,0,1,docomo
```

# イベントの一覧取得

特定のカレンダーに登録されているイベントの一覧を取得します。

個々のイベントから取得可能な情報は、下記を参照してください。
- CalendarContract.java
- APIリファレンス: [CalendarContract.Events](https://developer.android.com/reference/android/provider/CalendarContract.Events.html?hl=ja)

以下に、個々のイベントから取得可能な情報のうち、主なものを列挙します。

| Constant | Type | Description |
|:---------|:-----|:------------|
| CALENDAR_ID | NTEGER (long) | イベントが属するカレンダーの _ID |
| TITLE | TEXT | イベントのタイトル |
| DESCRIPTION | TEXT | イベントの説明 |
| EVENT_LOCATION | TEXT | イベントの開催場所 |
| EVENT_COLOR | INTEGER (color value) | イベントの表示色 |
| DISPLAY_COLOR | INTEGER (color value) | イベントの実際の表示色<br>EVENT_COLORが未設定の場合には、CALENDAR_COLORが使用される |
| DTSTART | INTEGER (long; millis since epoch) | イベントの開始日時<br>UTCでの1970/01/01 00:00:00からのミリ秒単位での経過時刻で表現される |
| DTEND | INTEGER (long; millis since epoch) | イベントの終了日時<br>UTCでの1970/01/01 00:00:00からのミリ秒単位での経過時刻で表現される |
| DURATION | TEXT (duration in RFC5545 format) | イベントの期間<br>RFC5545(旧版RFC2445)の形式で表現される<br>例）"PT1H"⇒１時間、 "P2W"⇒2週間 |
| EVENT_TIMEZONE | TEXT | イベントのタイムゾーン |
| EVENT_END_TIMEZONE | TEXT | イベントの終了日時のタイムゾーン |
| ALL_DAY | INTEGER (boolean) | 終日のイベントか否か |
| RRULE | TEXT | イベントの繰り返しルール<br>RFC5545(旧版RFC2445)の形式で表現される<br>例）"FREQ=WEEKLY;COUNT=10;WKST=SU" |
| RDATE | TEXT | イベントの繰り返し発生日<br>RRULE と組み合わせて、繰り返しのイベント全体を定義するのに使用される |
| GUESTS_CAN_MODIFY | INTEGER (boolean) | ゲストがイベントを変更できるか否か |
| GUESTS_CAN_INVITE_OTHERS | INTEGER (boolean) | ゲストが他のゲストを招待できるか否か |
| GUESTS_CAN_SEE_GUESTS | INTEGER (boolean) | ゲストが他のゲストのリストを参照できるか否か |
| ORGANIZER | STRING | イベントの主催者（所有者）のメールアドレス |


## カレンダー内のイベントの一覧取得

まず、"android.provider.CalendarContract.Events"を static import しておきます。
"CalendarContract.Events.xxx"のままでは長いため、"Events.xxx"でアクセスできるようにします。

```java
import static android.provider.CalendarContract.Events;
```

次に、個々のイベントから取得したいプロパティを列挙します。

```java
    // プロジェクション配列。
    // 取得したいプロパティの一覧を指定する。
    public static final String[] EVENT_PROJECTION = new String[] {
            Events.CALENDAR_ID,
            Events.TITLE,
            Events.DESCRIPTION,
            Events.EVENT_LOCATION,
            Events.EVENT_COLOR,
            Events.DISPLAY_COLOR,
            Events.DTSTART,
            Events.DTEND,
            Events.DURATION,
            Events.EVENT_TIMEZONE,
            Events.EVENT_END_TIMEZONE,
            Events.ALL_DAY,
            Events.RRULE,
            Events.RDATE,
            Events.GUESTS_CAN_MODIFY,
            Events.GUESTS_CAN_INVITE_OTHERS,
            Events.GUESTS_CAN_SEE_GUESTS,
            Events.ORGANIZER,
    };

    // プロジェクション配列のインデックス。
    // パフォーマンス向上のために、動的に取得せずに、静的に定義しておく。
    private static final int EVENT_PROJECTION_IDX_ID = 0;
    private static final int EVENT_PROJECTION_IDX_CALENDAR_ID = 1;
    private static final int EVENT_PROJECTION_IDX_TITLE = 2;
    private static final int EVENT_PROJECTION_IDX_DESCRIPTION = 3;
    private static final int EVENT_PROJECTION_IDX_EVENT_LOCATION = 4;
    private static final int EVENT_PROJECTION_IDX_EVENT_COLOR = 5;
    private static final int EVENT_PROJECTION_IDX_DISPLAY_COLOR = 6;
    private static final int EVENT_PROJECTION_IDX_DTSTART = 7;
    private static final int EVENT_PROJECTION_IDX_DTEND = 8;
    private static final int EVENT_PROJECTION_IDX_DURATION = 9;
    private static final int EVENT_PROJECTION_IDX_EVENT_TIMEZONE = 10;
    private static final int EVENT_PROJECTION_IDX_EVENT_END_TIMEZONE = 11;
    private static final int EVENT_PROJECTION_IDX_ALL_DAY = 12;
    private static final int EVENT_PROJECTION_IDX_RRULE = 13;
    private static final int EVENT_PROJECTION_IDX_RDATE = 14;
    private static final int EVENT_PROJECTION_IDX_GUESTS_CAN_MODIFY = 15;
    private static final int EVENT_PROJECTION_IDX_GUESTS_CAN_INVITE_OTHERS = 16;
    private static final int EVENT_PROJECTION_IDX_GUESTS_CAN_SEE_GUESTS = 17;
    private static final int EVENT_PROJECTION_IDX_ORGANIZER = 18;
```

ここで、UNIX time (1970/01/01 00:00:00からの経過時刻[msec])を文字列化するためのメソッドを用意しておきます。

```java
    private static String getDateTimeText(
            final Context context,
            final String timeZone,
            final long dateTimeInMillis
    ) {
        final Calendar calendar = GregorianCalendar.getInstance(TimeZone.getTimeZone(timeZone));
        calendar.setTimeInMillis(dateTimeInMillis);
        return DateUtils.formatDateRange(context, new java.util.Formatter(), calendar.getTimeInMillis(), calendar.getTimeInMillis(),
                DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR, timeZone).toString();
    }
```

最後に、検索対象のカレンダーの _ID を指定し、クエリを発行してカーソルを取得し、カーソルから個々のイベントの情報を取得します。
以下はCSV形式で、logcatに出力します。

```java
        // 検索対象のカレンダーの _ID
        final long targetCalendarId = 1;

        // クエリ条件を設定する
        final Uri uri = CalendarContract.Events.CONTENT_URI;
        final String[] projection = CALENDAR_PROJECTION;
        final String selection = "(" + CalendarContract.Events.CALENDAR_ID + " = ?)";
        final String[] selectionArgs = new String[] {String.valueOf(targetCalendarId)};
        final String sortOrder = null;

        // クエリを発行してカーソルを取得する
        final ContentResolver cr = getContentResolver();        
        final Cursor cur = cr.query(uri, EVENT_PROJECTION, selection, selectionArgs, null);

        // ログ出力 (Header)
        final StringBuilder sbEvents = new StringBuilder();
        for (final String property : EVENT_PROJECTION) {
            sbEvents.append(property).append(',');
        }
        Log.d(TAG, sbEvents.toString());

        while (cur.moveToNext()) {
            // カーソルから各プロパティを取得する
            final long id = cur.getLong(EVENT_PROJECTION_IDX_ID);
            final long calendarId = cur.getLong(EVENT_PROJECTION_IDX_CALENDAR_ID);
            final String title = cur.getString(EVENT_PROJECTION_IDX_TITLE);
            final String description = cur.getString(EVENT_PROJECTION_IDX_DESCRIPTION);
            final String eventLocation = cur.getString(EVENT_PROJECTION_IDX_EVENT_LOCATION);
            final int eventColdr = cur.getInt(EVENT_PROJECTION_IDX_EVENT_COLOR);
            final int displayColor = cur.getInt(EVENT_PROJECTION_IDX_DISPLAY_COLOR);
            final long dtStart = cur.getLong(EVENT_PROJECTION_IDX_DTSTART);
            final long dtEnd = cur.getLong(EVENT_PROJECTION_IDX_DTEND);
            final String duration = cur.getString(EVENT_PROJECTION_IDX_DURATION);
            final String eventTimeZone = cur.getString(EVENT_PROJECTION_IDX_EVENT_TIMEZONE);
            final String eventEndTimeZone = cur.getString(EVENT_PROJECTION_IDX_EVENT_END_TIMEZONE);
            final int allDay = cur.getInt(EVENT_PROJECTION_IDX_ALL_DAY);
            final String rRule = cur.getString(EVENT_PROJECTION_IDX_RRULE);
            final String rDate = cur.getString(EVENT_PROJECTION_IDX_RDATE);
            final int guestsCanModify = cur.getInt(EVENT_PROJECTION_IDX_GUESTS_CAN_MODIFY);
            final int guestsCanInviteOthers = cur.getInt(EVENT_PROJECTION_IDX_GUESTS_CAN_INVITE_OTHERS);
            final int guestCanSeeGuests = cur.getInt(EVENT_PROJECTION_IDX_GUESTS_CAN_SEE_GUESTS);
            final String organizer = cur.getString(EVENT_PROJECTION_IDX_ORGANIZER);

            // ログ出力 (Body)
            final StringBuilder sbBody = new StringBuilder();
            sbBody.append(id).append(',');
            sbBody.append(name).append(',');
            sbBody.append(accountName).append(',');
            sbBody.append(accountType).append(',');
            sbBody.append(String.format("0x%08X", calendarColor)).append(',');
            sbBody.append(calendarDisplayName).append(',');
            sbBody.append(calendarAccessLevel).append(',');
            sbBody.append(calendarTimeZone).append(',');
            sbBody.append(visible).append(',');
            sbBody.append(syncEvents).append(',');
            sbBody.append(ownerAccount).append(',');
            Log.d(TAG, sbBody.toString());
        }
```

## イベントの一覧の取得結果

```csv
_id,calendar_id,title,description,eventLocation,eventColor,displayColor,dtstart,dtend,duration,eventTimezone,eventEndTimezone,allDay,rrule,rdate,guestsCanModify,guestsCanInviteOthers,guestsCanSeeGuests,organizer,
1,1,Google Developers Summit : Android,,コクヨホール,0x00000000,0xFF9A9CFF,2016年4月25日 13:00,2016年4月25日 17:30,null,Asia/Tokyo,null,0,null,null,0,1,1,aaa@gmail.com,
2,1,日本Androidの会 2016年9月定例会,「クロスプラットフォーム開発最前線」,日本マイクロソフト株式会社,0xFF46D6DB,0xFF46D6DB,2016年9月7日 19:00,2016年9月7日 21:40,null,Asia/Tokyo,null,0,null,null,0,1,1,aaa@gmail.com,
3,1,shibuya.apk #10,,株式会社サイバーエージェント,0xFF46D6DB,0xFF46D6DB,2016年9月16日 19:00,2016年9月16日 21:00,null,Asia/Tokyo,null,0,null,null,0,1,1,aaa@gmail.com,
4,1,daily meeting,,,0x00000000,0xFF9A9CFF,2016年10月11日 10:00,1970年1月1日 9:00,P3600S,Asia/Tokyo,null,0,FREQ=WEEKLY;WKST=SU;BYDAY=MO,TU,WE,TH,FR,null,0,1,1,aaa@gmail.com,
```

# 参考情報

- [Calendar Provider | Android Developers](https://developer.android.com/guide/topics/providers/calendar-provider.html)

- [Calendar Provider | Android Developers (日本語)](https://developer.android.com/guide/topics/providers/calendar-provider.html?hl=ja)

- [[Android Tips] Calendar Provider で Event を登録すると Google カレンダーアプリが強制終了することがある | Developers.IO (2014/12/17)](http://dev.classmethod.jp/smartphone/android/calendar-provider-event/)

- [[CalendarProviderについて | Developers.IO (2012/05/29)]](http://dev.classmethod.jp/smartphone/calendarprovider/)

- [AndroidでCalendarを操作する | Developers.IO (2011/09/03)](http://dev.classmethod.jp/smartphone/android-calendar/)