CalendarProviderについて
はじめに
こんにちは。こむろです。
今回は、OS Version 4.0以降 にカレンダーのアクセス方法などに変更があったため、違いを調査してみました
Androidのカレンダーへのアクセスについて
Android 4.0以前でも公式ではないものの(公式APIがなかった)、カレンダーデータにアクセスすることができましたが、Android 4.0(SDK Version 14)以降に公式APIとして、「CalendarProvider」が提供されました。
基本的な操作については、あまり変更はないようですが、細かい部分で異なる部分があるため、カレンダーデータを操作するアプリケーションの場合、既存のロジックを修正する必要があります。
CalendarProviderについて
公式ドキュメントに開発ガイドに追加されています>CalendarProvider
違い
標準APIが用意されたタイミングで、カレンダーデータのデータスキーマにも変更が出ています。4.0以前と以後でどのように変化したのかを見てみます
カラム名 | ~4.0 | 4.0~ | 変更有 |
---|---|---|---|
Account Name | _sync_account | account_name (Calendars.ACCOUNT_NAME) | ○ |
Account Type | _sync_account_type | account_type (Calendars.ACCOUNT_TYPE) | ○ |
Display Name | displayName | calendar_displayName (Calendars.CALENDAR_DISPLAY_NAME) | ○ |
Access Level | access_level | calendar_access_level (Calendars.CALENDAR_ACCESS_LEVEL) | ○ |
Timezone | timezone | calendar_timezone (Calendars.CALENDAR_TIME_ZONE) | ○ |
Color | color | calendar_color (Calendars.CALENDAR_COLOR) | ○ |
ID | _id | _id (Calendars.ID) | × |
Name | name | name (Calendars.NAME) | × |
Owner Account | ownerAccount | owner_account (Calendars.OWNER_ACCOUNT) | × |
Sync events | sync_events | sync_events (Calendars.SYNC_EVENTS) | × |
表を見て分かるように、主にデータへアクセスするためのアカウント情報やカレンダーを一意に決定する情報が変更されています
公式のSampleで比較してみます. sampleuser@gmail.comというユーザーが所有しているカレンダーを取得します
// 2.2~4.0 Cursor cur = null; ContentResolver cr = getContentResolver(); Uri uri = Uri.parse("content://com.android.calendar/calendars"); String selection = "((" + "_sync_account" + " = ?) AND (" + "_sync_account_type" + " = ?) AND (" + "owner_account" + " = ?))"; String[] selectionArgs = new String[] {"sampleuser@gmail.com", "com.google", "sampleuser@gmail.com"}; // Submit the query and get a Cursor object back. cur = cr.query(uri, EVENT_PROJECTION, selection, selectionArgs, null);
// 4.0~ 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[] {"sampleuser@gmail.com", "com.google", "sampleuser@gmail.com"}; // Submit the query and get a Cursor object back. cur = cr.query(uri, EVENT_PROJECTION, selection, selectionArgs, null);
こんな感じに修正します。4.0以降の端末で1つ目のコードを動かそうとすると、そんなカラム名はないよ!と例外をThrowされてしまいます
データ挿入時の変更点
カラム名の変更などに加えて、カレンダー登録時にも重要な変更があります。公式にはマイナーアップデートと書いてありますが。既存のロジックのまま、Android4.0以降で実行するとIllegalArgumentExceptionがThrowされます。当初、カレンダー登録時になぜ例外が吐かれるのかわかりませんでした。
Android4.0~で実行すると例外が発生するコード
/** * * @param calendarName calendar名(一意) * @param displayName 表示名(一意の方が良いと思う) * @param color calendarの表示色 * @return 新たに作成されたカレンダー情報を格納したCalendarDto */ public CalendarDto entryCalendar(String calendarName, String displayName, int color) { ContentResolver contentResolver = context.getContentResolver(); ContentValues calVal = new ContentValues(); calVal.put(Calendars.ACCOUNT_NAME, "sampleCalendar"); calVal.put(Calendars.ACCOUNT_TYPE, "jp.classmethod.sample.calendar"); calVal.put(Calendars.NAME, calendarName); calVal.put(Calendars.CALENDAR_DISPLAY_NAME, displayName); calVal.put(Calendars.CALENDAR_COLOR, color); calVal.put(Calendars.CALENDAR_ACCESS_LEVEL, 700); calVal.put(Calendars.SYNC_EVENTS, 1); calVal.put(Calendars.CALENDAR_TIME_ZONE, TimeZone.getDefault().getID()); calVal.put(Calendars.OWNER_ACCOUNT, "owner"); String result = contentResolver.insert(Calendars.CONTENT_URI, calVal).toString(); // result = content://com.android.calendar/calendars/4 String strId = result.replaceAll(Calendars.CONTENT_URI.toString() + "/", ""); // strId = 4 return new CalendarDto(Integer.parseInt(strId), displayName); } /** * カレンダー自体の情報を表すDTO. */ public class CalendarDto { public CalendarDto(int id, String displayName) { this.id = id; this.displayName = displayName; } /** ID */ public int id; /** 表示名 */ public String displayName; }
例外
E/AndroidRuntime(3688): java.lang.IllegalArgumentException: Only sync adapters may write to account_name
原因はこの子です>SyncAdapter
SyncAdapterという機構が導入されているようです。とりあえず、公式のドキュメントに沿って次のコードを挿入します
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(); }
これをもとに先ほど例外が吐かれたコードを修正します
/** * * @param calendarName calendar名(一意) * @param displayName 表示名(一意の方が良いと思う) * @param color calendarの表示色 * @return 新たに作成されたカレンダー情報を格納したCalendarDto */ public CalendarDto entryCalendar(String calendarName, String displayName, int color) { ContentResolver contentResolver = context.getContentResolver(); ContentValues calVal = new ContentValues(); calVal.put(Calendars.ACCOUNT_NAME, "sampleCalendar"); calVal.put(Calendars.ACCOUNT_TYPE, "jp.classmethod.sample.calendar"); calVal.put(Calendars.NAME, calendarName); calVal.put(Calendars.CALENDAR_DISPLAY_NAME, displayName); calVal.put(Calendars.CALENDAR_COLOR, color); calVal.put(Calendars.CALENDAR_ACCESS_LEVEL, 700); calVal.put(Calendars.SYNC_EVENTS, 1); calVal.put(Calendars.CALENDAR_TIME_ZONE, TimeZone.getDefault().getID()); calVal.put(Calendars.OWNER_ACCOUNT, "owner");
String result = contentResolver.insert(asSyncAdapter(Calendars.CONTENT_URI, "sampleCalendar", "jp.classmethod.sample.calendar"), calVal).toString(); // result = content://com.android.calendar/calendars/4?caller_is_syncadapter=true&account_name=sampleCalendar&account_type=jp.classmethod.sample.calendar String strId = result.replaceAll(Calendars.CONTENT_URI.toString() + "/", ""); // strId = 4?caller_is_syncadapter=true&account_name=sampleCalendar&account_type=jp.classmethod.sample.calendar
// ICS以降, URIにAccoutNameとAccountTypeをAppend Queryしているので, 「?」以降切る int index = strId.lastIndexOf('?'); // Parameterが見つからなかった場合は、strIdを特に加工せずに利用. strId = index < 0 ? strId : strId.substring(0, index); return new CalendarDto(Integer.parseInt(strId), displayName); } [/java]
これで修正は完了です。先ほどの例外はThrowされずに、とりあえずカレンダーの登録は正常に行えました
SyncAdapterについては、また別の機会に解説します