スマートフォン・ジン | Smartphone-Zine

引っ越し先→ https://smartphone-zine.com/

CalendarAPIを使用したアプリケーションの作成方法

GoogleカレンダーAPIを使用してAndroid用のカレンダーアプリケーションを作成します。 Calendar取得や書き込みはCalendarプロバイダ経由で行います。カレンダープロバイダAPIは4.0から公式APIとなりましたが、それ以前のバージョンでも非公式ながら使用することができます。ただしUriカラム名が一部異なるのでAndroidのバージョン毎に分岐させる必要があります。 非公式のAPIを使用するためにはソースコードを読み解く必要があります。今回はAPIレベル10以上をターゲットとしたいと思います。 まずはGitのインストール。 http://git-scm.com/download/win からWindows版Gitをダウンロードしてパソコンにインストールします。 次にソースコードの取得。Android API 14以降のソースコードは「Android SDKマネージャー」で簡単に取得できますが、それ以前のソースは別途取得する必要があります。今回、Gingerbreadでもカレンダーを使えるようにしたいのでAndroid2.3のカレンダープロバイダのソースを参照する必要があります。そのためGitでAndroid2.3のソースコードを取得してきましょう。 ソースはGoogle本家から取得するのはなかなか大変なのでhttps://github.com/OESF/を利用すると楽に取得できます。 たとえばAndroidのソース一式を取得するには次のようなGitコマンドで取得します。 [shell] git init git clone https://github.com/OESF/OHA-Android-2.3.4_r1.0.git [/shell] ソースが取得できたら、早速カレンダープロバイダのソースを参照してみましょう。ソースは \frameworks\base\core\java\android\provider\Calendar.java にあります。 このソースを見れば、プロバイダのURIやカラムが理解できると思います。   さて、それではプログラムを作成していきましょう。Googleカレンダーからスケジュールを取得しリスト表示するプログラムです。

まずはプロジェクトを作成して下さい。

設定項目

入力値/選択項目

New Android Application Application name

CalendarSample

Project Name CalendarSample
Package Name com.example.CalendarSample
Build SDK Android 4.1(API 16)
Minimum Required SDK API10:Android 2.3.3(Gingerbread)
Create Activity Create Activity BlankActivity
New Blank Activity Activity Name MainActivity
Layout Name activity_main
Navigation Type None
Hierarchical Parent (指定なし)
Title MainActivity

MainActivity.javaを次のように編集します。 [java] package com.example.calendarsample; import android.os.Bundle; import android.support.v4.app.FragmentActivity; public class MainActivity extends FragmentActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } } [/java] アクティビティはこれだけです。次にレイアウトです。 [xml] <fragment xmlns:android="http://schemas.android.com/apk/res/android" android:name="com.example.calendarsample.CalendarListFragment" android:id="@+id/List" android:layout_width="match_parent" android:layout_height="match_parent" /> [/xml] レイアウトには上のようにフラグメントを1つ配置しているだけです。ではそのフラグメントのソースを見ていきましょう。フラグメントはListFragmentを使い、スケジュールをリスト表示します。 CalendarListFragment.java [java] package com.example.calendarsample; import android.annotation.TargetApi; import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.provider.CalendarContract; import android.provider.CalendarContract.Instances; import android.support.v4.app.ListFragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.support.v4.widget.SimpleCursorAdapter; import android.support.v4.widget.SimpleCursorAdapter.ViewBinder; import android.text.format.Time; import android.util.Log; import android.view.View; import android.widget.TextView; public class CalendarListFragment extends ListFragment implements     LoaderManager.LoaderCallbacks, ViewBinder {     SimpleCursorAdapter mAdapter;     @Override     public void onActivityCreated(Bundle savedInstanceState) {         super.onActivityCreated(savedInstanceState);         setEmptyText("スケジュールはありません");         String from;         if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH){             from = new String { Instances.TITLE, Instances.BEGIN };         } else {             from = new String { Instances.TITLE, Instances.BEGIN };         }         mAdapter = new SimpleCursorAdapter(getActivity(),             android.R.layout.simple_list_item_2, null, from,             new int { android.R.id.text1, android.R.id.text2 }, 0);         mAdapter.setViewBinder(this);         setListAdapter(mAdapter);         getLoaderManager().initLoader(0, null, this);     }     @TargetApi(14)     @Override     public Loader onCreateLoader(int id, Bundle args) {         String tz = Time.TIMEZONE_UTC;         Time time = new Time(tz);         time.setToNow();         time.allDay = true;         time.year = time.year - 1;         time.month = 0;         time.monthDay = 1;         time.hour = 0;         time.minute = 0;         time.second = 0;         int begin = Time.getJulianDay(time.toMillis(true), 0);         time.year += 4;         time.month = 11;         time.monthDay = 31;         int end = Time.getJulianDay(time.toMillis(true), 0);         Uri content_by_day_uri;         String instance_projection;         String sort_order;         if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH){             content_by_day_uri =                 CalendarContract.Instances.CONTENT_BY_DAY_URI;             instance_projection = new String {                 Instances.ID,                 Instances.EVENT_ID,                 Instances.BEGIN,                 Instances.END,                 Instances.TITLE             };             sort_order =                 Instances.BEGIN + " ASC, " + Instances.END + " DESC, "                     + Instances.TITLE + " ASC";         } else {             final String authority = "com.android.calendar";             content_by_day_uri = Uri.parse("content://" + authority                 + "/instances/whenbyday");             instance_projection = new String[] {                 "id",                 "event_id",                 "begin",                 "end",                 "title"             };             sort_order = "begin ASC, end DESC, title ASC";         }         Uri baseUri = buildQueryUri(begin, end, content_by_day_uri);         return new CursorLoader(getActivity(), baseUri,                 instance_projection, null,                 null, sort_order);     }     @Override     public void onLoadFinished(Loader loader, Cursor data) {         mAdapter.swapCursor(data);     }     @Override     public void onLoaderReset(Loader loader) {         mAdapter.swapCursor(null);     }     @Override     public boolean setViewValue(View view, Cursor cursor, int index) {         Log.v("debug", "index:" + String.valueOf(index));         switch (index) {         case 4:             *1;             return true;         case 2:             String text = cursor.getString(index);             Time time = new Time();             time.set(Long.parseLong(text));             *2;             return true;         default:             break;         }         return false;     }     private Uri buildQueryUri(int start, int end, Uri content_by_day_uri) {         StringBuilder path = new StringBuilder();         path.append(start);         path.append('/');         path.append(end);         Uri uri =             Uri.withAppendedPath(content_by_day_uri, path.toString());         return uri;     } } [/java] ListFragmentを使用して、取得したスケジュールの情報を画面に一覧表示しています。取得したデータをListに渡すためにSimpleCursorAdapterを使っています。SimpleCursorAdapterはCursorAdapterを継承しておりCursorAdapterを継承したクラスには_id列がないと動作しません。 データの取得にはCursorLoaderを使います。CursorLoaderを使えば手軽に非同期でデータを取得することが出来ます。まずローダーの準備からです。

getLoaderManager().initLoader(0, null, this);

LoaderManagerがLoaderを管理します。LoaderManager.LoaderCallbacksを実装したクラスが必要になるので、Fragmentに実装しています。 onCreateLoader() コールバックメソッドでCursorLoaderを生成して戻り値にセットします。 リマインダーテーブルに個々のスケジュールが入っています。リマインダーテーブルを取得するコンテントプロバイダのURIは次のような形です。

content://com.android.calendar/instances/whenbyday/<開始日>/<終了日>

<開始日>および<終了日>は取得したいスケジュールの範囲をユリウス通日(Julian Day)で指定します。ユリウス通日とは、紀元前4713年1月1日から起算して何日目であるかという日数です。ユリウス通日を取得するにはTimeクラスを使います。

        String tz = Time.TIMEZONE_UTC;
        Time time = new Time(tz);
        time.setToNow();
        time.allDay = true;
        time.year = time.year - 1;
        time.month = 0;
        time.monthDay = 1;
        time.hour = 0;
        time.minute = 0;
        time.second = 0;
        int begin = Time.getJulianDay(time.toMillis(true), 0);

Timeクラスのインスタンスに現在日付を設定し、そこから1年前の1月1日の日付を設定しています。設定した日付からユリウス通日を取得するにはgetJulianDayメソッドを使います。このメソッドはUTCミリ秒をユリウス通日に変換してくれます。 CursorLoaderのコンストラクタにコンテントプロバイダのURIなどを渡して、CursorLoaderを生成して戻りとして戻しています。

        return new CursorLoader(getActivity(), baseUri,
                instance_projection, null,
                null, sort_order);

onLoadFinishedコールバックメソッドと、onLoaderResetコールバックメソッドはそれぞれローダーの読み込みが終わった時と、ローダーがリセットされてデータが読み込めなくなった時に呼ばれます。ここでAdapterに紐付くカーソルの更新を行うわけですが、SimpleCursorAdapterにカーソルの更新を行うためのメソッドであるswapCursorメソッドが用意されていますので、次のように実装します。

    @Override
    public void onLoadFinished(Loader loader, Cursor data) {
        mAdapter.swapCursor(data);
    }
    @Override
    public void onLoaderReset(Loader loader) {
        mAdapter.swapCursor(null);
    }

カレンダープロバイダから取得したタイトルと開始時間をリストに表示していますが、開始時間や終了時間はそのままだとUTCミリ秒なのでフォーマットして表示するようにしています。簡単にフォーマットを行うための仕組みとして、ViewBinderがあります。これを使ってフォーマットします。

        mAdapter.setViewBinder(this);

thisでFragment自身をViewBinderに指定しています。FragmentにはViewBinderインタフェースを実装し、フォーマット処理をsetViewValueコールバックメソッドに記述します。

    public boolean setViewValue(View view, Cursor cursor, int index) {
        Log.v("debug", "index:" + String.valueOf(index));
        switch (index) {
        case 4:
            ((TextView)view).setText(cursor.getString(index));
            return true;
        case 2:
            String text = cursor.getString(index);
            Time time = new Time();
            time.set(Long.parseLong(text));
            ((TextView)view).setText(time.format3339(false));
            return true;
        default:
            break;
        }
        return false;
    }

indexにカーソルのカラム番号を受け取るので、switchで分岐してカラム毎にフォーマット処理を行なっています。この例ではカラムとインデックスの対応は次のようになっています。

                Instances._ID,      // index 0
                Instances.EVENT_ID, // index 1
                Instances.BEGIN,    // index 2
                Instances.END,      // index 3
                Instances.TITLE     // index 4

indexが2の場合、開始時刻(BEGIN)を取得できるのでTimeクラスのformat3339メソッドでフォーマットしてリストに表示させています。 【追記】 最後に、カレンダーデータを読み取ることをAndroid Manifestに記載しなければなりません。READ_CALENDARパーミッションを追加します。 [xml] <uses-permission android:name="android.permission.READ_CALENDAR" /> [/xml] 以上でカレンダープロバイダからデータを取得してリストに表示することが出来ました。

*1:TextView)view).setText(cursor.getString(index

*2:TextView)view).setText(time.format3339(false