Googleカレンダーに予定を登録する。〜スケジュール管理ソフトをS!アプリで作ってみよう(その31)

今回は、Googleカレンダーに予定を登録できるようにする。

予定のクラス(Event)にgetXML()を追加して、XMLのデータを得られるようにする。


package jp.dip.ettem.scheduler;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Calendar;
import java.util.Date;

import org.kxml2.io.KXmlParser;
import org.kxml2.io.KXmlSerializer;
import org.xmlpull.v1.XmlPullParserException;

/**
* 1件の予定のクラス。
*/
public class Event {
public String what; // 件名
public String where; // 場所
public String discription; // 説明
public Date startTime; // 開始日時
public Date endTime; // 終了日時
public int recordID; // RecordStoreのID
public static final int newRecordID = 0; // RecordStoreに保存されていない
// 場合のID
public byte[] xmlData;

/**
* デフォルトの値でインスタンスを作成します。文字列フィールドは空文字列で
* 初期化します。日時は現在の日時で初期化します。
*/
public Event() {
what = "";
where = "";
discription = "";

// 時刻は0秒とする。
Calendar now = Calendar.getInstance();
now.set(Calendar.SECOND, 0);
now.set(Calendar.MILLISECOND, 0);

startTime = now.getTime();
endTime = now.getTime();
recordID = newRecordID;
}

public Event(byte[] xmlData) throws IOException {
this.xmlData = xmlData;
parseEvent(xmlData);
}

public Event(Date date) {
this();
startTime = date;
endTime = date;
}


void parseEvent(byte[] xmlData) throws IOException {
KXmlParser parser = new KXmlParser();

try {
parser.setInput(new ByteArrayInputStream(xmlData), "UTF-8");
int eventType = parser.getEventType();

while (eventType != KXmlParser.END_DOCUMENT) {
if (eventType == KXmlParser.START_DOCUMENT) {
; // 何もしない
} else if (eventType == KXmlParser.START_TAG) {
String elementName = parser.getName();
if ( elementName.equals("title")) {
what = getText(parser);
} else if (elementName.equals("content")) {
discription = getText(parser);
} else if (elementName.equals("gd:when")) {
startTime = GCal.rfc3339FormatToDate(
parser.getAttributeValue(null, "startTime"));
endTime = GCal.rfc3339FormatToDate(
parser.getAttributeValue(null, "endTime"));
}
}

eventType = parser.next();
}
} catch (XmlPullParserException e) {

}
}

byte[] getXML() {
ByteArrayOutputStream oStream = new ByteArrayOutputStream();
KXmlSerializer serializer = new KXmlSerializer();
final String ATOM_NAMESPACE = "http://www.w3.org/2005/Atom";
final String GD_NAMESPACE = "http://schemas.google.com/g/2005";

try {
serializer.setOutput(oStream, "UTF-8");
serializer.setPrefix("", ATOM_NAMESPACE);
serializer.setPrefix("gd", GD_NAMESPACE);

serializer.startTag(ATOM_NAMESPACE, "entry");

serializer.startTag(ATOM_NAMESPACE, "categry");
serializer.attribute("", "scheme", "http://schemas.google.com/g/2005#kind");
serializer.attribute("", "term", "http://schemas.google.com/g/2005#event");
serializer.text("");
serializer.endTag(ATOM_NAMESPACE, "categry");

serializer.startTag(ATOM_NAMESPACE, "title");
serializer.attribute("", "type", "text");
serializer.text(what);
serializer.endTag(ATOM_NAMESPACE, "title");

serializer.startTag(ATOM_NAMESPACE, "content");
serializer.attribute("", "type", "text");
serializer.text(discription);
serializer.endTag(ATOM_NAMESPACE, "content");

serializer.startTag(GD_NAMESPACE, "transparency");
serializer.attribute("", "value", "http://schemas.google.com/g/2005#event.opaque");
serializer.text("");
serializer.endTag(GD_NAMESPACE, "transparency");

serializer.startTag(GD_NAMESPACE, "eventStatus");
serializer.attribute("", "value", "http://schemas.google.com/g/2005#event.confirmed");
serializer.text("");
serializer.endTag(GD_NAMESPACE, "eventStatus");

serializer.startTag(GD_NAMESPACE, "where");
serializer.attribute("", "valueString", where);
serializer.text("");
serializer.endTag(GD_NAMESPACE, "where");

serializer.startTag(GD_NAMESPACE, "when");
serializer.attribute("", "startTime", GCal.dateToRFC3339Format(startTime, false));
serializer.attribute("", "endTime", GCal.dateToRFC3339Format(endTime, false));
serializer.text("");
serializer.endTag(GD_NAMESPACE, "when");

serializer.endTag(ATOM_NAMESPACE, "entry");
serializer.flush();
} catch (IOException e) {

}

return oStream.toByteArray();
}

String getText(KXmlParser parser) throws XmlPullParserException, IOException {
String text = "";
int eventType = parser.next();

if (eventType == KXmlParser.TEXT) {
text = parser.getText();
parser.next();
}

return text;
}
}

Googleカレンダーとの連携用クラス(GCal)にcreateEvent()を追加する。


package jp.dip.ettem.scheduler;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import java.util.Vector;

import javax.microedition.io.*;
import javax.microedition.rms.RecordStore;
import javax.microedition.rms.RecordStoreException;

/**
* Google Calenderと連携するためのクラス
*/
public class GCal implements GCalConstants {
static String account = new String();
static String passwd = new String();
private static String authCode;

private static RecordStore gCalSettingRecordStore;
private static final int ACCOUNT_RECORD_ID = 1;
private static final int PASSWD_RECORD_ID = 2;


/**
* 設定を保存するRecordStoreを設定します。
* @param scheduleRecordStore
*/
static void setRecordStore(RecordStore settingRecordStore) {
gCalSettingRecordStore = settingRecordStore;
}

/**
* RecordStoreからアカウント情報を読み込みます。
*
*/
static void readAccount() {
byte[] record;
try {
record = gCalSettingRecordStore.getRecord(ACCOUNT_RECORD_ID);
if (record != null) {
account = new String(record);
}

record = gCalSettingRecordStore.getRecord(PASSWD_RECORD_ID);
if (record != null) {
passwd = new String(record);
}
} catch (RecordStoreException e) {

}
}

/**
* RecordStoreにアカウント情報を書き込みます。
*
*/
static void writeAccount() {
byte[] record;
try {
record = account.getBytes();
if (gCalSettingRecordStore.getNumRecords() < 1) {
gCalSettingRecordStore.addRecord(record, 0, record.length);
} else {
gCalSettingRecordStore.setRecord(ACCOUNT_RECORD_ID, record, 0, record.length);
}

record = passwd.getBytes();
if (gCalSettingRecordStore.getNumRecords() < 2) {
gCalSettingRecordStore.addRecord(record, 0, record.length);
} else {
gCalSettingRecordStore.setRecord(PASSWD_RECORD_ID, record, 0, record.length);
}
} catch (RecordStoreException e) {

}
}

/**
* AuthCodeを設定します。
*
*/
static void setAuthCode() {
if (account == "" || passwd == "") {
return;
}

Thread authThread = new Thread () {
public void run () {
authCode = getAuthCode(account, passwd);
}
};
authThread.start();
}

/**
* Client Loginのための、AuthCodeを返します。
*
* @param eMail GoogleのログインのためのE-Mailアドレス
* @param passwd Googleのアカウントのパスワード
* @return
*/
private static String getAuthCode(String eMail, String passwd) {
String authCode = null;
HttpConnection connection = null;
OutputStream oStream = null;
InputStream iStream = null;

try {
// 接続先は、Googleではなく、私のサーバを指定。(しかもHTTPで接続)
// Googleが使っているSSL認証局に、Softbank Mobileが対応したら変更する予定。
connection = (HttpConnection) Connector.open(AUTH_URL);

connection.setRequestMethod(HttpConnection.POST);
byte[] postData =("Email=" + eMail + "&Passwd=" + passwd
+ "&source=jp.dip.ettem-scheduler-1&service=cl\r\n").getBytes();
connection.setRequestProperty("Content-Length", String.valueOf(postData.length));
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

oStream = connection.openOutputStream();
oStream.write(postData);

iStream = connection.openInputStream();
int len = (int) connection.getLength();
ByteArrayOutputStream content = getContent(iStream, len);

String contentStr = content.toString();

if (contentStr.indexOf("Auth=") > 0) {
authCode = contentStr.substring(contentStr.indexOf("Auth=") + "Auth=".length(),
contentStr.length() - 1);
}
} catch (IOException e) {

} finally {
try {
if (oStream != null) {
oStream.close();
}
if (iStream != null) {
iStream.close();
}
if (connection != null) {
connection.close();
}
} catch (IOException e) {
// 何もしない
}
}

return authCode;
}

/**
* 日付を指定して、予定を取得します。(Googleでの検索結果そのままです)
* ログインしていない場合は、長さ0の配列を返します。
* @param date
* @return
*/
static Event[] getEvents(Date date) {
Vector entries = new Vector();
Event[] events = new Event[0];
HttpConnection connection = null;
InputStream iStream = null;
String startMin = dateToRFC3339Format(getStartOfDay(date), true);
String startMax = dateToRFC3339Format(getEndOfDay(date), true);

if (authCode == null) {
return events;
}

try {
// 接続先は、Googleではなく、私のサーバを指定。
// Googleが使っているSSL認証局に、Softbank Mobileが対応したら変更する予定。
String queryURL = CALENDAR_BASE_URL
+ "?start-min=" + startMin + "&start-max=" + startMax;

connection = (HttpConnection) Connector.open(queryURL);
connection.setRequestProperty("Authorization", "GoogleLogin auth=" + authCode);

if (connection.getResponseCode() == HttpConnection.HTTP_MOVED_TEMP ) {
String redirectURL = connection.getHeaderField("location");
connection.close();
connection = (HttpConnection) Connector.open(redirectURL);
connection.setRequestProperty("Authorization", "GoogleLogin auth=" + authCode);
}

iStream = connection.openInputStream();
int len = (int) connection.getLength();
ByteArrayOutputStream oStream = getContent(iStream, len);
String result = oStream.toString();

entries = sliceToEntry(result);
events = new Event[entries.size()];
for (int i = 0; i < entries.size(); i++) {
events[i] = new Event(((String) entries.elementAt(i)).getBytes("UTF-8"));
}


} catch (IOException e) {

} finally {
try {
if (iStream != null) {
iStream.close();
}
if (connection != null) {
connection.close();
}
} catch (IOException e) {
// 何もしない
}
}

return events;
}

static void createEvent(byte[] xml) {
HttpConnection connection = null;
OutputStream oStream = null;
InputStream iStream = null;

try {
connection = (HttpConnection) Connector.open(CALENDAR_BASE_URL);
connection.setRequestMethod(HttpConnection.POST);
connection.setRequestProperty("Content-Length", String.valueOf(xml.length));
connection.setRequestProperty("Content-Type", "application/atom+xml");
connection.setRequestProperty("Authorization", "GoogleLogin auth=" + authCode);

oStream = connection.openOutputStream();
oStream.write(xml);

if (connection.getResponseCode() == HttpConnection.HTTP_MOVED_TEMP ) {
String redirectURL = connection.getHeaderField("location");
connection.close();
oStream.close();
connection = (HttpConnection) Connector.open(redirectURL);
connection.setRequestMethod(HttpConnection.POST);
connection.setRequestProperty("Content-Length", String.valueOf(xml.length));
connection.setRequestProperty("Content-Type", "application/atom+xml");
connection.setRequestProperty("Authorization", "GoogleLogin auth=" + authCode);

oStream = connection.openOutputStream();
oStream.write(xml);
connection.getResponseCode();
}
} catch (IOException e) {

} finally {
try {
if (oStream != null) {
oStream.close();
}
if (iStream != null) {
iStream.close();
}
if (connection != null) {
connection.close();
}
} catch (IOException e) {
// 何もしない
}
}

}

/**
* HTTPのボディ部分を返します。
* @param iStream HTTPのボディ部分の入力ストリーム
* @param length HTTPのレスポンスのcontentの長さ(byte数)
* @return ボディ部分のByteArrayOutputStream
* @throws IOException
*/
static ByteArrayOutputStream getContent(InputStream iStream, int length)
throws IOException {
ByteArrayOutputStream oStream = new ByteArrayOutputStream();

if (length > 0) {
int actual = 0;
int bytesread = 0 ;
byte[] data = new byte[length];
while ((bytesread != length) && (actual != -1)) {
actual = iStream.read(data, bytesread, length - bytesread);
bytesread += actual;
}
oStream.write(data, 0, length);
} else {
int ch;
while ((ch = iStream.read()) != -1) {
oStream.write(ch);
}
}

return oStream;
}

/**
* 検索結果をentryに分割します。
* @param content 検索結果
* @return
*/
static Vector sliceToEntry(String content) {
Vector entries = new Vector();
int fromIndex = 0;
int beginIndex;
int endIndex;

for (beginIndex = content.indexOf(""), endIndex = content.indexOf("");
beginIndex != -1 && endIndex != -1;
fromIndex = endIndex + "".length(),
beginIndex = content.indexOf("", fromIndex),
endIndex = content.indexOf("
", fromIndex)) {
entries.addElement(content.substring(beginIndex, endIndex + "".length()));
}

return entries;
}

/**
* RFC3339のフォーマットの文字列を受け取って、Dateオブジェクトを返します。
* 秒以下の単位の値は0にセットされます。
* @param date
* @return
*/
static Date rfc3339FormatToDate(String date) {
/*
* dateのフォーマットは以下の形式
* YYYY-MM-DDThh:mm:ss.xxx+hh:mm
* Tや+以降の部分はない事もある
*/
int year = 0, month = 0, day = 0, hour = 0, min = 0;
if (date.length() >= 10) {
year = Integer.parseInt(date.substring(0, 4));
month = Integer.parseInt(date.substring(5, 7));
day = Integer.parseInt(date.substring(8, 10));
}
if (date.length() >= 16) {
hour = Integer.parseInt(date.substring(11, 13));
min = Integer.parseInt(date.substring(14, 16));
}

Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, year);
cal.set(Calendar.MONTH, month);
cal.set(Calendar.DATE, day);
cal.set(Calendar.HOUR_OF_DAY, hour);
cal.set(Calendar.MINUTE, min);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);

return cal.getTime();
}

/**
* Dateオブジェクトを受け取り、RFC3339のフォーマットの文字列を返します。
* 文字列はURLエンコードしたものです。
* @param date
* @param urlEncode TODO
* @return
*/
static String dateToRFC3339Format(Date date, boolean urlEncode) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
String colon = urlEncode ? "%3A" : ":";
String OffsetSign = urlEncode ? "%2B" : "+";
TimeZone timeZone = cal.getTimeZone();
int rawOffset = timeZone.getRawOffset();
OffsetSign = rawOffset < 0 ? "-" : OffsetSign;
int hourOffset = rawOffset / 1000 / 60 / 60;
int minOffset = rawOffset / 1000 / 60 % 60;

String rfc3339Format = cal.get(Calendar.YEAR) + "-"
+ getZeroPaddingString(cal.get(Calendar.MONTH) + 1, 2)
+ "-"
+ getZeroPaddingString(cal.get(Calendar.DAY_OF_MONTH), 2)
+ "T"
+ getZeroPaddingString(cal.get(Calendar.HOUR_OF_DAY), 2)
+ colon
+ getZeroPaddingString(cal.get(Calendar.MINUTE), 2)
+ colon
+ getZeroPaddingString(cal.get(Calendar.SECOND), 2)
+ OffsetSign
+ getZeroPaddingString(hourOffset, 2)
+ colon
+ getZeroPaddingString(minOffset, 2);

return rfc3339Format;
}

/**
* その日のAM 0:00を返します。
* @param date
* @return
*/
static Date getStartOfDay(Date date) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
Date startOfDay = cal.getTime();

return startOfDay;
}

/**
* その日のPM 11:59:59.999を返します。
* @param date
* @return
*/
static Date getEndOfDay(Date date) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
cal.set(Calendar.HOUR_OF_DAY, 23);
cal.set(Calendar.MINUTE, 59);
cal.set(Calendar.SECOND, 59);
cal.set(Calendar.MILLISECOND, 999);
Date endOfDate = cal.getTime();

return endOfDate;
}

static String getZeroPaddingString(int val, int len) {
StringBuffer strBuf = new StringBuffer();
String strVal = Integer.toString(val);

for (int i = 0; i < len - strVal.length(); i++) {
strBuf.append("0");
}
strBuf.append(strVal);

return strBuf.toString();
}
}

予定の入力フォームにGCalを呼び出して、予定を登録するようにする。


package jp.dip.ettem.scheduler;

import javax.microedition.lcdui.*;

/**
* 予定の入力フォーム。
* Saveのボタンが押されたら、呼び出し側に戻る。
*/
public class EventForm extends Form implements CommandListener, ItemStateListener {
private Display display;
private Displayable callerDisplayable; // 呼び出し元

private Command saveCommand;
private TextField what; // 件名の入力文字列
private final int WHAT_LEN = 256;
private DateField startTime; // 開始日時
private DateField endTime; // 終了日時
private TextField where; // 場所
private final int WHERE_LEN = WHAT_LEN;
private TextField discription; // 説明
private final int DISCRIPTION_LEN = 1024;

private Event event; // 予定

/**
* 予定の入力フォームを作成します。入力が終了したら、呼び出し元のDisplayable
* をCurrentにします。
* @param display ディスプレイを指定します。
* @param callerDisplayable 呼び出し元のクラスを指定します。
* @param event 予定を指定します。
*/
public EventForm(Display display, Displayable callerDisplayable, Event event) {
super("Event");
this.display = display;
this.callerDisplayable = callerDisplayable;
this.event = event;

// 予定の保存コマンドを登録
saveCommand = new Command("Save", Command.OK, 1);
addCommand(saveCommand);
setCommandListener(this);
setItemStateListener(this);

// 件名の入力欄のItemを作成
what = new TextField("What", "", WHAT_LEN, TextField.ANY);
append(what);

// 日時指定のItemを作成
startTime = new DateField("Start", DateField.DATE_TIME);
startTime.setDate(event.startTime);
append(startTime);
endTime = new DateField("End", DateField.DATE_TIME);
endTime.setDate(event.endTime);
append(endTime);

// 場所の入力欄のItemを作成
where = new TextField("Where", "", WHERE_LEN, TextField.ANY);
append(where);
// 説明の入力欄のItemを作成
discription = new TextField("Discription", "", DISCRIPTION_LEN, TextField.ANY);
append(discription);
}

public void commandAction(Command command, Displayable displayable) {
// Saveボタンが押されたら予定を保存
if (command == saveCommand) {
GCal.createEvent(event.getXML());
Events.setEvent(event);
display.setCurrent(callerDisplayable);
}
}

public void itemStateChanged(Item changedItem) {
// Itemの変更に合わせて、Eventのインスタンスを更新
if (changedItem == what) {
event.what = ((TextField) what).getString();
} else if (changedItem == startTime) {
event.startTime = ((DateField) changedItem).getDate();
} else if (changedItem == endTime) {
event.endTime = ((DateField) changedItem).getDate();
} else if (changedItem == where) {
event.where = ((TextField) where).getString();
} else if (changedItem == discription) {
event.discription = ((TextField) discription).getString();
}
}
}