package interdroid.swan.sensors.impl;
import interdroid.swan.R;
import interdroid.swan.sensors.AbstractConfigurationActivity;
import interdroid.swan.sensors.AbstractSwanSensor;
import interdroid.swan.swansong.TimestampedValue;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import android.os.Bundle;
import android.util.Log;
public class CalendarSensor extends AbstractSwanSensor {
public static final String TAG = "Calendar";
/**
* The configuration activity for this sensor.
*
* @author nick <palmer@cs.vu.nl>
*
*/
public static class ConfigurationActivity extends AbstractConfigurationActivity {
@Override
public final int getPreferencesXML() {
return R.xml.calendar_preferences;
}
}
public static final String START_TIME_NEXT_EVENT_FIELD = "start_time_next_event";
public static final String SAMPLE_INTERVAL = "sample_interval";
public static final String IGNORE_FREE_EVENTS = "ignore_free_events";
public static final String IGNORE_ALLDAY_EVENTS = "ignore_allday_events";
/**
* Get your private calendar URL from Google Calendar in the browser, click
* on small arrow next to a given calendar, choose calendar settings, get
* the address from private address (xml), without the ending '/basic'
*/
public static final String PRIVATE_CALENDAR_URL = "private_calendar_url";
public static final long DEFAULT_SAMPLE_INTERVAL = 5 * 60 * 1000;
public static final boolean DEFAULT_IGNORE_FREE_EVENTS = true;
public static final boolean DEFAULT_IGNORE_ALLDAY_EVENTS = true;
protected static final int HISTORY_SIZE = 10;
// Google Calendar specific variables
private static final String FREE_EVENT = "http://schemas.google.com/g/2005#event.transparent";
private static final String ISO_8601_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";
private static final String ISO_8601_DATE_FORMAT_DAY = "yyyy-MM-dd";
private static final SimpleDateFormat FORMATTER = new SimpleDateFormat(
ISO_8601_DATE_FORMAT, new Locale("nl", "NL"));
private static final SimpleDateFormat FORMATTER_DAY = new SimpleDateFormat(
ISO_8601_DATE_FORMAT_DAY, new Locale("nl", "NL"));
private Map<String, CalendarPoller> activeThreads = new HashMap<String, CalendarPoller>();
private Date sampleCalendar(String privateCalendarUrl,
boolean ignoreAllDay, boolean ignoreFree) {
try {
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(privateCalendarUrl);
HttpResponse httpResponse = httpClient.execute(httpGet);
HttpEntity httpEntity = httpResponse.getEntity();
DocumentBuilderFactory factory = DocumentBuilderFactory
.newInstance();
Document doc = factory.newDocumentBuilder().parse(
httpEntity.getContent());
Element root = doc.getDocumentElement();
NodeList items = root.getElementsByTagName("entry");
for (int i = 0; i < items.getLength(); i++) {
NodeList properties = items.item(i).getChildNodes();
Date result = null;
for (int j = 0; j < properties.getLength(); j++) {
Element element = (Element) properties.item(j);
String name = element.getNodeName();
if ("gd:when".equals(name)) {
try {
result = FORMATTER.parse(element
.getAttribute("startTime"));
} catch (ParseException e) {
// cannot be parsed, must be an allday event
if (ignoreAllDay) {
continue;
} else {
// parse it as an allday event
result = FORMATTER_DAY.parse(element
.getAttribute("startTime"));
}
}
}
if ("gd:transparency".equals(name)) {
if (ignoreFree
&& element.getAttribute("value").equals(
FREE_EVENT)) {
continue;
}
}
}
if (result != null) {
return result;
}
}
} catch (UnsupportedEncodingException e) {
Log.e(TAG, "XML error. Unsupported encoding." + e.getMessage());
} catch (MalformedURLException e) {
Log.e(TAG, "XML error. Incorrect URL." + e.getMessage());
} catch (IOException e) {
Log.e(TAG, "XML error. IOException." + e.getMessage());
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SAXException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ParserConfigurationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (DOMException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
@Override
public String[] getValuePaths() {
return new String[] { START_TIME_NEXT_EVENT_FIELD };
}
@Override
public void initDefaultConfiguration(Bundle DEFAULT_CONFIGURATION) {
DEFAULT_CONFIGURATION.putLong(SAMPLE_INTERVAL, DEFAULT_SAMPLE_INTERVAL);
DEFAULT_CONFIGURATION.putBoolean(IGNORE_ALLDAY_EVENTS,
DEFAULT_IGNORE_ALLDAY_EVENTS);
DEFAULT_CONFIGURATION.putBoolean(IGNORE_FREE_EVENTS,
DEFAULT_IGNORE_FREE_EVENTS);
}
@Override
public void onConnected() {
SENSOR_NAME = "Calendar";
}
@Override
public final void register(String id, String valuePath, Bundle configuration) {
CalendarPoller calendarPoller = new CalendarPoller(id, configuration);
activeThreads.put(id, calendarPoller);
calendarPoller.start();
}
@Override
public final void unregister(String id) {
activeThreads.remove(id).interrupt();
}
class CalendarPoller extends Thread {
private Bundle configuration;
private List<TimestampedValue> values = new ArrayList<TimestampedValue>();
private String id;
CalendarPoller(String id, Bundle configuration) {
this.configuration = configuration;
this.id = id;
}
public void run() {
boolean ignoreFreeEvents = configuration.getBoolean(
IGNORE_FREE_EVENTS,
mDefaultConfiguration.getBoolean(IGNORE_FREE_EVENTS));
boolean ignoreAlldayEvents = configuration.getBoolean(
IGNORE_ALLDAY_EVENTS,
mDefaultConfiguration.getBoolean(IGNORE_ALLDAY_EVENTS));
String privateCalendarURL = configuration
.getString(PRIVATE_CALENDAR_URL)
+ "/full/?max-results=5&singleevents=true&futureevents=true&orderby=starttime&sortorder=a";
while (!isInterrupted()) {
long start = System.currentTimeMillis();
if (values.size() >= HISTORY_SIZE) {
values.remove(0);
}
Date date = sampleCalendar(privateCalendarURL,
ignoreAlldayEvents, ignoreFreeEvents);
if (date != null) {
values.add(new TimestampedValue(date, start));
notifyDataChangedForId(id);
}
try {
Thread.sleep(configuration.getLong(SAMPLE_INTERVAL,
mDefaultConfiguration.getLong(SAMPLE_INTERVAL))
+ start - System.currentTimeMillis());
} catch (InterruptedException e) {
}
}
}
public List<TimestampedValue> getValues() {
return values;
}
}
@Override
public void onDestroySensor() {
for (CalendarPoller calendarPoller : activeThreads.values()) {
calendarPoller.interrupt();
}
super.onDestroySensor();
}
}