Kotlin: CalendarProviderでGoogleカレンダーの情報を取り出す

# はじめに
**CalendarProvider**を使って、端末に保存されたカレンダーに登録した予定を取得して取り出す方法です。（Googleログインなどは未実装）
GoogleCalendarAPIを叩かなくても簡易的にカレンダー情報を取得することができます。
最後の方に、簡易的にカレンダーを扱ったアプリの例も掲載しています。

# 目次　
1. 開発環境
2. カレンダーパーミッションの追加
3. アプリがカレンダーへアクセスするためのパーミッション設定
3. コードを書いてみる
3. 実際に使ってみる（アプリの例）
3. さいごに

# 開発環境
Android Studio Chipmunk|2021.2.1 Patch1

# カレンダーパーミッションの追加
`AndroidManifest.xml`に以下のパーミッションを追加します。

```kotlin: AndroidManifest.xml
    <uses-permission android:name="android.permission.READ_CALENDAR"/>
    <uses-permission android:name="android.permission.WRITE_CALENDAR"/>
```

# アプリがカレンダーへアクセスするためのパーミッション
実機やエミュレーターの個別のアプリ設定から、パーミッションを有効にします。
これよく忘れるので注意です。

<img width="300" alt="スクリーンショット 2022-12-20 11.16.34.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2988537/b1cd489c-5237-a7aa-f93b-0b489b145b39.png">

# コードを書いてみる
### 端末にあるカレンダー一覧を取り出す
googleカレンダーに限らず、端末にあるすべてのカレンダーを取り出すメソッドを作ります。
```kotlin:MainActicity.kt
//カレンダーId一覧を取り出すメソッドを書く
    fun searchCalendarId() {
        //取得したい情報をListにまとめて入れる
        val CALENDAR_PROJECTION = arrayOf(
            CalendarContract.Calendars._ID,
            CalendarContract.Calendars.NAME,
            CalendarContract.Calendars.OWNER_ACCOUNT
        )

        //CALENDAR_PROJECTION配列の要素順で番号を決める
        val CALENDAR_PROJECTION_IDX_ID = 0
        val CALENDAR_PROJECTION_IDX_NAME = 1
        val CALENDAR_PROJECTION_IDX_OWNER_ACCOUNT = 2

        // クエリ条件を設定する
        val uri = CalendarContract.Calendars.CONTENT_URI
        val selection: String? = null
        val selectionArgs: Array<String>? = null
        val sortOrder: String? = null

        // クエリを発行してカーソルを取得する
        val cr = contentResolver
        val cur = cr.query(uri, CALENDAR_PROJECTION, selection, selectionArgs, sortOrder)

        var textStr = "id, カレンダー名, ownerAccount\n"
        while (cur?.moveToNext() == true) {
            // カーソルから各プロパティを取得する
            val id = cur.getLong(CALENDAR_PROJECTION_IDX_ID)
            val name = cur.getString(CALENDAR_PROJECTION_IDX_NAME)
            val ownerAccount = cur.getString(CALENDAR_PROJECTION_IDX_OWNER_ACCOUNT)
            textStr += "$id, "
            textStr += "$name, "
            textStr += "$ownerAccount\n"
            binding.textBox.text = textStr
        }
    }
```

### あるカレンダーに登録されている情報を取り出す
上記で取り出したカレンダーの一覧から、カレンダーを一つ指定して、予定を取り出すメソッドを作ります。
今回は、eventId,イベントタイトル、開始時刻、終了時刻、詳細をtextViewに表示させます。
（必要に応じて、他の情報を扱えるように定義しています。）
```kotlin:MainActivity.kt
//指定したカレンダーIDのイベントを取得するメソッドをつくる
    @RequiresApi(Build.VERSION_CODES.O)
    fun getCalendarEventAll(targetCalendarId: Int) {
        val EVENT_PROJECTION = arrayOf(
            BaseColumns._ID,
            CalendarContract.Events.TITLE,
            CalendarContract.Events.DESCRIPTION,
            CalendarContract.Events.EVENT_LOCATION,
            CalendarContract.Events.EVENT_COLOR,
            CalendarContract.Events.DISPLAY_COLOR,
            CalendarContract.Events.DTSTART,
            CalendarContract.Events.DTEND,
            CalendarContract.Events.DURATION,
            CalendarContract.Events.EVENT_TIMEZONE,
            CalendarContract.Events.EVENT_END_TIMEZONE,
            CalendarContract.Events.ALL_DAY,
            CalendarContract.Events.RRULE,
            CalendarContract.Events.RDATE,
            CalendarContract.Events.GUESTS_CAN_MODIFY,
            CalendarContract.Events.GUESTS_CAN_INVITE_OTHERS,
            CalendarContract.Events.GUESTS_CAN_SEE_GUESTS,
            CalendarContract.Events.ORGANIZER,
            CalendarContract.Events.CALENDAR_ID,
        )
        // EVENT_PROJECTIONの順番と一致するID、要素数書き換えたり、順番変えたときには、ここも変える
        val EVENT_PROJECTION_IDX_EVENT_ID = 0
        val EVENT_PROJECTION_IDX_TITLE = 1
        val EVENT_PROJECTION_IDX_DESCRIPTION = 2
        val EVENT_PROJECTION_IDX_EVENT_LOCATION = 3
        val EVENT_PROJECTION_IDX_EVENT_COLOR = 4
        val EVENT_PROJECTION_IDX_DISPLAY_COLOR = 5
        val EVENT_PROJECTION_IDX_DTSTART = 6
        val EVENT_PROJECTION_IDX_DTEND = 7
        val EVENT_PROJECTION_IDX_DURATION = 8
        val EVENT_PROJECTION_IDX_EVENT_TIMEZONE = 9
        val EVENT_PROJECTION_IDX_EVENT_END_TIMEZONE = 10
        val EVENT_PROJECTION_IDX_ALL_DAY = 11
        val EVENT_PROJECTION_IDX_RRULE = 12
        val EVENT_PROJECTION_IDX_RDATE = 13
        val EVENT_PROJECTION_IDX_GUESTS_CAN_MODIFY = 14
        val EVENT_PROJECTION_IDX_GUESTS_CAN_INVITE_OTHERS = 15
        val EVENT_PROJECTION_IDX_GUESTS_CAN_SEE_GUESTS = 16
        val EVENT_PROJECTION_IDX_ORGANIZER = 17
        val EVENT_PROJECTION_IDX_CALENDAR_ID = 18

        val uri = CalendarContract.Events.CONTENT_URI
        // クエリ
        val selection = "(" + CalendarContract.Events.CALENDAR_ID + " = ?)"
        val selectionArgs = arrayOf(targetCalendarId.toString())

        val cr = contentResolver
        val cur: Cursor? = cr.query(uri, EVENT_PROJECTION, selection, selectionArgs, null)

        var textStr = "eventId, イベントタイトル,開始時間,終了時間,期間,詳細\n"

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

            // タイトルが無いものは、表示しない
            if (title.isNullOrEmpty().not()) {
                textStr += "$eventId, "
                textStr += "$title, "
                textStr += "$dtStart,"
                textStr += "$dtEnd,"
                textStr += "$description\n"

                binding.textBox.text = textStr
            }
        }
    }
```

それでは、メソッドを使ってみましょう。
`searchCalendarId()`で取り出されたcalendar一覧の中から、扱いたいカレンダーのIdを`getCalendarEventAll（）`のidにセットします。
```kotlin:MainActivity.kt
binding.button.setOnClickListener {
            searchCalendarId()
        }

binding.button2.setOnClickListener {
            getCalendarEventAll(6)
        }
```

# 実際に使ってみる（アプリの例）
以前に、**ClendarProvider**を扱って作った、時間管理アプリです。Googleカレンダーから予定が登録されていない時間を計算して、期日までの残り作業可能時間を算出する簡易的な計算ができるようにしました。
開始時間などはミリ秒とタイムゾーンのどちらでも取得できたりするので、使いやすい情報を抜き出すのが良いと思います。


![qiita advent calendar.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2988537/17021df0-5162-115e-99d3-7751d9a0cf83.jpeg)


# さいごに
**CalendarProvider**、いかがでしたでしょうか。
実際のアプリに組み込んだり、実用的なレベルで扱うにはGoogleカレンダーAPI（けっこう厄介）を叩くほうがいいかもしれませんが、簡易的に実装するには使いやすいと思います。
カレンダーを使ったオリジナリティの溢れるアプリ、ぜひ作ってみてください！