CalendarProviderについて

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

こんにちは。こむろです。

今回は、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については、また別の機会に解説します