summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorRohit Yengisetty <rohit@cyngn.com>2014-11-20 14:09:53 -0800
committerSteve Kondik <steve@cyngn.com>2015-10-18 13:52:41 -0700
commit990c61ad0a076a13598660b7fa477e6c30f7b783 (patch)
tree3c070a202f24337908eaa4f190e1b9f0bedb218d /src
parent515ade24eaaff7e898392dc7a2784e64f0193723 (diff)
downloadandroid_packages_apps_Calendar-990c61ad0a076a13598660b7fa477e6c30f7b783.tar.gz
android_packages_apps_Calendar-990c61ad0a076a13598660b7fa477e6c30f7b783.tar.bz2
android_packages_apps_Calendar-990c61ad0a076a13598660b7fa477e6c30f7b783.zip
Calendar - Add the ability to share calendar events through an ics file.
iCal specification rescources: http://build.mnode.org/projects/ical4j/apidocs/index.html http://www.kanzaki.com/docs/ical/ iCal validators: http://icalvalid.cloudapp.net http://severinghaus.org/projects/icv/ Change-Id: I87b12fd37aa7e3ad29bea79d4dbb152f868ff4f8
Diffstat (limited to 'src')
-rw-r--r--src/com/android/calendar/AllInOneActivity.java37
-rw-r--r--src/com/android/calendar/EventInfoFragment.java103
-rw-r--r--src/com/android/calendar/icalendar/Attendee.java77
-rw-r--r--src/com/android/calendar/icalendar/IcalendarUtils.java183
-rw-r--r--src/com/android/calendar/icalendar/Organizer.java42
-rw-r--r--src/com/android/calendar/icalendar/VCalendar.java113
-rw-r--r--src/com/android/calendar/icalendar/VEvent.java176
7 files changed, 729 insertions, 2 deletions
diff --git a/src/com/android/calendar/AllInOneActivity.java b/src/com/android/calendar/AllInOneActivity.java
index 17aaff1b..fcf2b0cd 100644
--- a/src/com/android/calendar/AllInOneActivity.java
+++ b/src/com/android/calendar/AllInOneActivity.java
@@ -37,7 +37,6 @@ import android.content.AsyncQueryHandler;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.ContentUris;
-import android.content.Context;
import android.content.CursorLoader;
import android.content.DialogInterface;
import android.content.Loader;
@@ -51,6 +50,7 @@ import android.database.Cursor;
import android.graphics.drawable.LayerDrawable;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Environment;
import android.os.Handler;
import android.provider.CalendarContract;
import android.provider.CalendarContract.Attendees;
@@ -81,6 +81,7 @@ import com.android.calendar.agenda.AgendaFragment;
import com.android.calendar.month.MonthByWeekFragment;
import com.android.calendar.selectcalendars.SelectVisibleCalendarsFragment;
+import java.io.File;
import java.io.IOException;
import java.util.Calendar;
import java.util.List;
@@ -446,6 +447,9 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH
if (getResources().getBoolean(R.bool.show_delete_events_menu)) {
getLoaderManager().initLoader(0, null, this);
}
+
+ // clean up cached ics files - in case onDestroy() didn't run the last time
+ cleanupCachedIcsFiles();
}
private long parseViewAction(final Intent intent) {
@@ -633,6 +637,37 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH
mController.deregisterAllEventHandlers();
CalendarController.removeInstance(this);
+
+ // clean up cached ics files
+ cleanupCachedIcsFiles();
+ }
+
+ /**
+ * Cleans up the temporarily generated ics files in the cache directory
+ * The files are of the format *.ics
+ */
+ private void cleanupCachedIcsFiles() {
+ if (!isExternalStorageWritable()) return;
+ File cacheDir = getExternalCacheDir();
+ File[] files = cacheDir.listFiles();
+ if (files == null) return;
+ for (File file : files) {
+ String filename = file.getName();
+ if (filename.endsWith(".ics")) {
+ file.delete();
+ }
+ }
+ }
+
+ /**
+ * Checks if external storage is available for read and write
+ */
+ public boolean isExternalStorageWritable() {
+ String state = Environment.getExternalStorageState();
+ if (Environment.MEDIA_MOUNTED.equals(state)) {
+ return true;
+ }
+ return false;
}
private void initFragments(long timeMillis, int viewType, Bundle icicle) {
diff --git a/src/com/android/calendar/EventInfoFragment.java b/src/com/android/calendar/EventInfoFragment.java
index f72938e7..85be389d 100644
--- a/src/com/android/calendar/EventInfoFragment.java
+++ b/src/com/android/calendar/EventInfoFragment.java
@@ -103,12 +103,18 @@ import com.android.calendar.event.EditEventActivity;
import com.android.calendar.event.EditEventHelper;
import com.android.calendar.event.EventColorPickerDialog;
import com.android.calendar.event.EventViewUtils;
+import com.android.calendar.icalendar.IcalendarUtils;
+import com.android.calendar.icalendar.Organizer;
+import com.android.calendar.icalendar.VCalendar;
+import com.android.calendar.icalendar.VEvent;
import com.android.calendarcommon2.DateException;
import com.android.calendarcommon2.Duration;
import com.android.calendarcommon2.EventRecurrence;
import com.android.colorpicker.ColorPickerSwatch.OnColorSelectedListener;
import com.android.colorpicker.HsvColorComparator;
+import java.io.File;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -412,7 +418,6 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange
private QueryHandler mHandler;
-
private final Runnable mTZUpdater = new Runnable() {
@Override
public void run() {
@@ -1252,10 +1257,106 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange
mDeleteHelper.delete(mStartMillis, mEndMillis, mEventId, -1, onDeleteRunnable);
} else if (itemId == R.id.info_action_change_color) {
showEventColorPickerDialog();
+ } else if (itemId == R.id.info_action_share_event) {
+ shareEvent();
}
return super.onOptionsItemSelected(item);
}
+ /**
+ * Generates an .ics formatted file with the event info and launches intent chooser to
+ * share said file
+ */
+ private void shareEvent() {
+ // Create the respective ICalendar objects from the event info
+ VCalendar calendar = new VCalendar();
+ calendar.addProperty(VCalendar.VERSION, "2.0");
+ calendar.addProperty(VCalendar.PRODID, VCalendar.PRODUCT_IDENTIFIER);
+ calendar.addProperty(VCalendar.CALSCALE, "GREGORIAN");
+ calendar.addProperty(VCalendar.METHOD, "REQUEST");
+
+ VEvent event = new VEvent();
+ mEventCursor.moveToFirst();
+ // add event start and end datetime
+ if (!mAllDay) {
+ String eventTimeZone = mEventCursor.getString(EVENT_INDEX_EVENT_TIMEZONE);
+ event.addEventStart(mStartMillis, eventTimeZone);
+ event.addEventEnd(mEndMillis, eventTimeZone);
+ } else {
+ // All-day events' start and end time are stored as UTC.
+ // Treat the event start and end time as being in the local time zone and convert them
+ // to the corresponding UTC datetime. If the UTC time is used as is, the ical recipients
+ // will report the wrong start and end time (+/- 1 day) for the event as they will
+ // convert the UTC time to their respective local time-zones
+ String localTimeZone = Utils.getTimeZone(mActivity, mTZUpdater);
+ long eventStart = IcalendarUtils.convertTimeToUtc(mStartMillis, localTimeZone);
+ long eventEnd = IcalendarUtils.convertTimeToUtc(mEndMillis, localTimeZone);
+ event.addEventStart(eventStart, "UTC");
+ event.addEventEnd(eventEnd, "UTC");
+ }
+
+ event.addProperty(VEvent.LOCATION, mEventCursor.getString(EVENT_INDEX_EVENT_LOCATION));
+ event.addProperty(VEvent.DESCRIPTION, mEventCursor.getString(EVENT_INDEX_DESCRIPTION));
+ event.addProperty(VEvent.SUMMARY, mEventCursor.getString(EVENT_INDEX_TITLE));
+ event.addOrganizer(new Organizer(mEventOrganizerDisplayName, mEventOrganizerEmail));
+
+ // Add Attendees to event
+ for (Attendee attendee : mAcceptedAttendees) {
+ IcalendarUtils.addAttendeeToEvent(attendee, event);
+ }
+
+ for (Attendee attendee : mDeclinedAttendees) {
+ IcalendarUtils.addAttendeeToEvent(attendee, event);
+ }
+
+ for (Attendee attendee : mTentativeAttendees) {
+ IcalendarUtils.addAttendeeToEvent(attendee, event);
+ }
+
+ for (Attendee attendee : mNoResponseAttendees) {
+ IcalendarUtils.addAttendeeToEvent(attendee, event);
+ }
+
+ // compose all of the ICalendar objects
+ calendar.addEvent(event);
+
+ // create and share ics file
+ boolean isShareSuccessful = false;
+ try {
+ // event title serves as the file name prefix
+ String filePrefix = event.getProperty(VEvent.SUMMARY);
+ if (filePrefix == null || filePrefix.length() < 3) {
+ // default to a generic filename if event title doesn't qualify
+ // prefix length constraint is imposed by File#createTempFile
+ filePrefix = "invite";
+ }
+ File inviteFile = File.createTempFile(filePrefix, ".ics",
+ mActivity.getExternalCacheDir());
+ if (IcalendarUtils.writeCalendarToFile(calendar, inviteFile)) {
+ inviteFile.setReadable(true,false); // set world-readable
+ Intent shareIntent = new Intent();
+ shareIntent.setAction(Intent.ACTION_SEND);
+ shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(inviteFile));
+ // the ics file is sent as an extra, the receiving application decides whether to
+ // parse the file to extract calendar events or treat it as a regular file
+ shareIntent.setType("application/octet-stream");
+ startActivity(shareIntent);
+ isShareSuccessful = true;
+
+ } else {
+ // error writing event info to file
+ isShareSuccessful = false;
+ }
+ } catch (IOException e) {
+ isShareSuccessful = false;
+ }
+
+ if (!isShareSuccessful) {
+ Log.e(TAG, "Couldn't generate ics file");
+ Toast.makeText(mActivity, R.string.error_generating_ics, Toast.LENGTH_SHORT).show();
+ }
+ }
+
private void showEventColorPickerDialog() {
if (mColorPickerDialog == null) {
mColorPickerDialog = EventColorPickerDialog.newInstance(mColors, mCurrentColor,
diff --git a/src/com/android/calendar/icalendar/Attendee.java b/src/com/android/calendar/icalendar/Attendee.java
new file mode 100644
index 00000000..fc8c78bb
--- /dev/null
+++ b/src/com/android/calendar/icalendar/Attendee.java
@@ -0,0 +1,77 @@
+/**
+ * Copyright (C) 2014 The CyanogenMod Project
+ */
+
+package com.android.calendar.icalendar;
+
+import java.util.HashMap;
+
+/**
+ * Models the Attendee component of a calendar event
+ */
+public class Attendee {
+
+ // property strings
+ // TODO: only a partial list of attributes have been implemented, implement the rest
+ public static String CN = "CN"; // Attendee Name
+ public static String PARTSTAT = "PARTSTAT"; // Participant Status (Attending , Declined .. )
+ public static String RSVP = "RSVP";
+ public static String ROLE = "ROLE";
+ public static String CUTYPE = "CUTYPE";
+
+
+ private static HashMap<String, Integer> sPropertyList = new HashMap<String, Integer>();
+ // initialize the approved list of mProperties for a calendar event
+ static {
+ sPropertyList.put(CN,1);
+ sPropertyList.put(PARTSTAT, 1);
+ sPropertyList.put(RSVP, 1);
+ sPropertyList.put(ROLE, 1);
+ sPropertyList.put(CUTYPE, 1);
+ }
+
+ public HashMap<String, String> mProperties; // stores (property, value) pairs
+ public String mEmail;
+
+ public Attendee() {
+ mProperties = new HashMap<String, String>();
+ }
+
+ /**
+ * Add Attendee properties
+ * @param property
+ * @param value
+ * @return
+ */
+ public boolean addProperty(String property, String value) {
+ // only unary properties for now
+ if (sPropertyList.containsKey(property) && sPropertyList.get(property) == 1 &&
+ value != null) {
+ mProperties.put(property, value);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns an iCal formatted string of the Attendee component
+ * @return
+ */
+ public String getICalFormattedString() {
+ StringBuilder output = new StringBuilder();
+
+ // Add Event mProperties
+ output.append("ATTENDEE;");
+ for (String property : mProperties.keySet() ) {
+ // append properties in the following format: attribute=value;
+ output.append(property + "=" + mProperties.get(property) + ";");
+ }
+ output.append("X-NUM-GUESTS=0:mailto:" + mEmail);
+
+ output = IcalendarUtils.enforceICalLineLength(output);
+
+ output.append("\n");
+ return output.toString();
+ }
+
+}
diff --git a/src/com/android/calendar/icalendar/IcalendarUtils.java b/src/com/android/calendar/icalendar/IcalendarUtils.java
new file mode 100644
index 00000000..ef96674b
--- /dev/null
+++ b/src/com/android/calendar/icalendar/IcalendarUtils.java
@@ -0,0 +1,183 @@
+/**
+ * Copyright (C) 2014 The CyanogenMod Project
+ */
+
+package com.android.calendar.icalendar;
+
+import android.provider.CalendarContract;
+import android.util.Log;
+import com.android.calendar.CalendarEventModel;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * Helper functions to help adhere to the iCalendar format.
+ */
+public class IcalendarUtils {
+
+ private static int sPermittedLineLength = 75; // Line length mandated by iCalendar format
+
+ /**
+ * ensure the string conforms to the iCalendar encoding requirements
+ * escape line breaks , commas and semicolons
+ * @param sequence
+ * @return
+ */
+ public static String cleanseString(CharSequence sequence) {
+ if (sequence == null) return null;
+ String input = sequence.toString();
+
+ // replace new lines with the literal '\n'
+ input = input.replaceAll("\\r|\\n|\\r\\n", "\\\\n");
+ // escape semicolons and commas
+ input = input.replace(";", "\\;");
+ input = input.replace(",", "\\,");
+
+ return input;
+ }
+
+ /**
+ * Stringify VCalendar object and write to file
+ * @param calendar
+ * @param file
+ * @return success status of the file write operation
+ */
+ public static boolean writeCalendarToFile(VCalendar calendar, File file) {
+ if (calendar == null || file == null) return false;
+ String icsFormattedString = calendar.getICalFormattedString();
+ FileOutputStream outStream = null;
+ try {
+ outStream = new FileOutputStream(file);
+ outStream.write(icsFormattedString.getBytes());
+ } catch (FileNotFoundException e) {
+ return false;
+ } catch (IOException e) {
+ return false;
+ } finally {
+ try {
+ if (outStream != null) outStream.close();
+ } catch (IOException ioe) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Formats the given input to adhere to the iCal line length and formatting requirements
+ * @param input
+ * @return
+ */
+ public static StringBuilder enforceICalLineLength(StringBuilder input) {
+ if (input == null) return null;
+ StringBuilder output = new StringBuilder();
+ int length = input.length();
+
+ // bail if no work needs to be done
+ if (length <= sPermittedLineLength) {
+ return input;
+ }
+
+ for (int i = 0, currentLineLength = 0; i < length; i++) {
+ char currentChar = input.charAt(i);
+ if (currentChar == '\n') { // new line encountered
+ output.append(currentChar);
+ currentLineLength = 0; // reset char counter
+
+ } else if (currentChar != '\n' && currentLineLength <= sPermittedLineLength) {
+ // a non-newline char that can be part of the current line
+ output.append(currentChar);
+ currentLineLength++;
+
+ } else if (currentLineLength > sPermittedLineLength) {
+ // need to branch out to a new line
+ // add a new line and a space - iCal requirement
+ output.append("\n ");
+ output.append(currentChar);
+ currentLineLength = 2; // already has 2 chars : space and currentChar
+ }
+ }
+
+ return output;
+ }
+
+ /**
+ * create an iCal Attendee with properties from CalendarModel attendee
+ *
+ * @param attendee
+ * @param event
+ */
+ public static void addAttendeeToEvent(CalendarEventModel.Attendee attendee, VEvent event) {
+ if (attendee == null || event == null) return;
+ Attendee vAttendee = new Attendee();
+ vAttendee.addProperty(Attendee.CN, attendee.mName);
+
+ String participationStatus;
+ switch (attendee.mStatus) {
+ case CalendarContract.Attendees.ATTENDEE_STATUS_ACCEPTED:
+ participationStatus = "ACCEPTED";
+ break;
+ case CalendarContract.Attendees.ATTENDEE_STATUS_DECLINED:
+ participationStatus = "DECLINED";
+ break;
+ case CalendarContract.Attendees.ATTENDEE_STATUS_TENTATIVE:
+ participationStatus = "TENTATIVE";
+ break;
+ case CalendarContract.Attendees.ATTENDEE_STATUS_NONE:
+ default:
+ participationStatus = "NEEDS-ACTION";
+ break;
+ }
+ vAttendee.addProperty(Attendee.PARTSTAT, participationStatus);
+ vAttendee.mEmail = attendee.mEmail;
+
+ event.addAttendee(vAttendee);
+ }
+
+ /**
+ * returns an iCalendar formatted UTC date-time
+ * ex: 20141120T120000Z for noon on Nov 20, 2014
+ *
+ * @param millis in epoch time
+ * @param timeZone indicates the time zone of the input epoch time
+ * @return
+ */
+ public static String getICalFormattedDateTime(long millis, String timeZone) {
+ if (millis < 0) return null;
+
+ Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone(timeZone));
+ calendar.setTimeInMillis(millis);
+
+ SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
+ simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ String dateTime = simpleDateFormat.format(calendar.getTime());
+ StringBuilder output = new StringBuilder(16);
+
+ // iCal UTC date format : <yyyyMMdd>T<HHmmss>Z
+ return output.append(dateTime.subSequence(0,8))
+ .append("T")
+ .append(dateTime.substring(8))
+ .append("Z")
+ .toString();
+ }
+
+ /**
+ * Converts the time in a local time zone to UTC time
+ * @param millis epoch time in the local timezone
+ * @param localTimeZone string id of the local time zone
+ * @return
+ */
+ public static long convertTimeToUtc(long millis, String localTimeZone) {
+ if (millis < 0) return 0;
+
+ // remove the local time zone's UTC offset
+ return millis - TimeZone.getTimeZone(localTimeZone).getRawOffset();
+ }
+}
diff --git a/src/com/android/calendar/icalendar/Organizer.java b/src/com/android/calendar/icalendar/Organizer.java
new file mode 100644
index 00000000..2e039849
--- /dev/null
+++ b/src/com/android/calendar/icalendar/Organizer.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright (C) 2014 The CyanogenMod Project
+ */
+
+package com.android.calendar.icalendar;
+
+/**
+ * Event Organizer component
+ * Fulfils the ORGANIZER property of an Event
+ */
+public class Organizer {
+
+ public String mName;
+ public String mEmail;
+
+ public Organizer(String name, String email) {
+ if (name != null) {
+ mName = name;
+ } else {
+ mName = "UNKNOWN";
+ }
+ if (email != null) {
+ mEmail = email;
+ } else {
+ mEmail = "UNKNOWN";
+ }
+ }
+
+ /**
+ * Returns an iCal formatted string
+ */
+ public String getICalFormattedString() {
+ StringBuilder output = new StringBuilder();
+ // add the organizer info
+ output.append("ORGANIZER;CN=" + mName + ":mailto:" + mEmail);
+ // enforce line length constraints
+ output = IcalendarUtils.enforceICalLineLength(output);
+ output.append("\n");
+ return output.toString();
+ }
+
+}
diff --git a/src/com/android/calendar/icalendar/VCalendar.java b/src/com/android/calendar/icalendar/VCalendar.java
new file mode 100644
index 00000000..00c7b81b
--- /dev/null
+++ b/src/com/android/calendar/icalendar/VCalendar.java
@@ -0,0 +1,113 @@
+/**
+ * Copyright (C) 2014 The CyanogenMod Project
+ */
+
+package com.android.calendar.icalendar;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+
+/**
+ * Models the Calendar/VCalendar component of the iCalendar format
+ */
+public class VCalendar {
+
+ // valid property identifiers of the component
+ // TODO: only a partial list of attributes have been implemented, implement the rest
+ public static String VERSION = "VERSION";
+ public static String PRODID = "PRODID";
+ public static String CALSCALE = "CALSCALE";
+ public static String METHOD = "METHOD";
+
+ public final static String PRODUCT_IDENTIFIER = "-//Cyanogen Inc//com.android.calendar";
+
+ // stores the -arity of the attributes that this component can have
+ private final static HashMap<String, Integer> sPropertyList = new HashMap<String, Integer>();
+
+ // initialize approved list of iCal Calendar properties
+ static {
+ sPropertyList.put(VERSION, 1);
+ sPropertyList.put(PRODID, 1);
+ sPropertyList.put(CALSCALE, 1);
+ sPropertyList.put(METHOD, 1);
+ }
+
+ // stores attributes and their corresponding values belonging to the Calendar object
+ public HashMap<String, String> mProperties;
+ public LinkedList<VEvent> mEvents; // events that belong to this Calendar object
+
+ /**
+ * Constructor
+ */
+ public VCalendar() {
+ mProperties = new HashMap<String, String>();
+ mEvents = new LinkedList<VEvent>();
+ }
+
+ /**
+ * Add specified property
+ * @param property
+ * @param value
+ * @return
+ */
+ public boolean addProperty(String property, String value) {
+ // since all the required mProperties are unary (only one can exist) , taking a shortcut here
+ // when multiples of a property can exist , enforce that here .. cleverly
+ if (sPropertyList.containsKey(property) && value != null) {
+ mProperties.put(property, IcalendarUtils.cleanseString(value));
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Add Event to calendar
+ * @param event
+ */
+ public void addEvent(VEvent event) {
+ if (event != null) mEvents.add(event);
+ }
+
+ /**
+ *
+ * @return
+ */
+ public LinkedList<VEvent> getAllEvents() {
+ return mEvents;
+ }
+
+ /**
+ * Returns the iCal representation of the calendar and all of its inherent components
+ * @return
+ */
+ public String getICalFormattedString() {
+ StringBuilder output = new StringBuilder();
+
+ // Add Event properties
+ // TODO: add the ability to specify the order in which to compose the properties
+ output.append("BEGIN:VCALENDAR\n");
+ for (String property : mProperties.keySet() ) {
+ output.append(property + ":" + mProperties.get(property) + "\n");
+ }
+
+ // enforce line length requirements
+ output = IcalendarUtils.enforceICalLineLength(output);
+ // add event
+ for (VEvent event : mEvents) {
+ output.append(event.getICalFormattedString());
+ }
+
+ output.append("END:VCALENDAR\n");
+
+ return output.toString();
+ }
+
+ /**
+ * TODO: Aggressive validation of VCalendar and all of its components to ensure they conform
+ * to the ical specification
+ * @return
+ */
+ private boolean validate() {
+ return false;
+ }
+} \ No newline at end of file
diff --git a/src/com/android/calendar/icalendar/VEvent.java b/src/com/android/calendar/icalendar/VEvent.java
new file mode 100644
index 00000000..1aff0bd4
--- /dev/null
+++ b/src/com/android/calendar/icalendar/VEvent.java
@@ -0,0 +1,176 @@
+/**
+ * Copyright (C) 2014 The CyanogenMod Project
+ */
+
+package com.android.calendar.icalendar;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.UUID;
+
+/**
+ * Models the Event/VEvent component of the iCalendar format
+ */
+public class VEvent {
+
+ // valid property identifiers for an event component
+ // TODO: only a partial list of attributes has been implemented, implement the rest
+ public static String CLASS = "CLASS";
+ public static String CREATED = "CREATED";
+ public static String LOCATION = "LOCATION";
+ public static String ORGANIZER = "ORGANIZER";
+ public static String PRIORITY = "PRIORITY";
+ public static String SEQ = "SEQ";
+ public static String STATUS = "STATUS";
+ public static String UID = "UID";
+ public static String URL = "URL";
+ public static String DTSTART = "DTSTART";
+ public static String DTEND = "DTEND";
+ public static String DURATION = "DURATION";
+ public static String DTSTAMP = "DTSTAMP";
+ public static String SUMMARY = "SUMMARY";
+ public static String DESCRIPTION = "DESCRIPTION";
+ public static String ATTENDEE = "ATTENDEE";
+ public static String CATEGORIES = "CATEGORIES";
+
+ // stores the -arity of the attributes that this component can have
+ private static HashMap<String, Integer> sPropertyList = new HashMap<String, Integer>();
+
+ // initialize the approved list of mProperties for a calendar event
+ static {
+ sPropertyList.put(CLASS,1);
+ sPropertyList.put(CREATED,1);
+ sPropertyList.put(LOCATION,1);
+ sPropertyList.put(ORGANIZER,1);
+ sPropertyList.put(PRIORITY,1);
+ sPropertyList.put(SEQ,1);
+ sPropertyList.put(STATUS,1);
+ sPropertyList.put(UID,1);
+ sPropertyList.put(URL,1);
+ sPropertyList.put(DTSTART,1);
+ sPropertyList.put(DTEND,1);
+ sPropertyList.put(DURATION, 1);
+ sPropertyList.put(DTSTAMP,1);
+ sPropertyList.put(SUMMARY,1);
+ sPropertyList.put(DESCRIPTION,1);
+
+ sPropertyList.put(ATTENDEE, Integer.MAX_VALUE);
+ sPropertyList.put(CATEGORIES, Integer.MAX_VALUE);
+ sPropertyList.put(CATEGORIES, Integer.MAX_VALUE);
+ }
+
+ // stores attributes and their corresponding values belonging to the Event component
+ public HashMap<String, String> mProperties;
+
+ public LinkedList<Attendee> mAttendees;
+ public Organizer mOrganizer;
+
+ /**
+ * Constructor
+ */
+ public VEvent() {
+ mProperties = new HashMap<String, String>();
+ mAttendees = new LinkedList<Attendee>();
+
+ // generate and add a unique identifier to this event - ical requisite
+ addProperty(UID , UUID.randomUUID().toString() + "@cyanogenmod.com");
+ addTimeStamp();
+ }
+
+ /**
+ * For adding unary properties. For adding other property attributes , use the respective
+ * component methods to create and add these special components.
+ * @param property
+ * @param value
+ * @return
+ */
+ public boolean addProperty(String property, String value) {
+ // only unary-properties for now
+ if (sPropertyList.containsKey(property) && sPropertyList.get(property) == 1 &&
+ value != null) {
+ mProperties.put(property, IcalendarUtils.cleanseString(value));
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * returns the value of the requested event property or null if there isn't one
+ */
+ public String getProperty(String property) {
+ return mProperties.get(property);
+ }
+
+ /**
+ * Add attendees to the event
+ * @param attendee
+ */
+ public void addAttendee(Attendee attendee) {
+ if(attendee != null) mAttendees.add(attendee);
+ }
+
+ /**
+ * Add an Organizer to the Event
+ * @param organizer
+ */
+ public void addOrganizer(Organizer organizer) {
+ if (organizer != null) mOrganizer = organizer;
+ }
+
+ /**
+ * Add an start date-time to the event
+ */
+ public void addEventStart(long startMillis, String timeZone) {
+ if (startMillis < 0) return;
+
+ String formattedDateTime = IcalendarUtils.getICalFormattedDateTime(startMillis, timeZone);
+ addProperty(DTSTART, formattedDateTime);
+ }
+
+ /**
+ * Add an end date-time for event
+ */
+ public void addEventEnd(long endMillis, String timeZone) {
+ if (endMillis < 0) return;
+
+ String formattedDateTime = IcalendarUtils.getICalFormattedDateTime(endMillis, timeZone);
+ addProperty(DTEND, formattedDateTime);
+ }
+
+ /**
+ * Timestamps the events with the current date-time
+ */
+ private void addTimeStamp() {
+ String formattedDateTime = IcalendarUtils.getICalFormattedDateTime(
+ System.currentTimeMillis(), "UTC");
+ addProperty(DTSTAMP, formattedDateTime);
+ }
+
+ /**
+ * Returns the iCal representation of the Event component
+ */
+ public String getICalFormattedString() {
+ StringBuilder sb = new StringBuilder();
+
+ // Add Event properties
+ sb.append("BEGIN:VEVENT\n");
+ for (String property : mProperties.keySet() ) {
+ sb.append(property + ":" + mProperties.get(property) + "\n");
+ }
+
+ // Enforce line length requirements
+ sb = IcalendarUtils.enforceICalLineLength(sb);
+
+ sb.append(mOrganizer.getICalFormattedString());
+
+ // add event Attendees
+ for (Attendee attendee : mAttendees) {
+ sb.append(attendee.getICalFormattedString());
+ }
+
+ sb.append("END:VEVENT\n");
+
+ return sb.toString();
+ }
+
+}