From ce5b4a44e51afdaf73c08f499f55bbb96985cc71 Mon Sep 17 00:00:00 2001 From: Danesh M Date: Tue, 16 Dec 2014 17:20:05 -0800 Subject: Calendar : Add ability to import/export from sdcard Change-Id: Ia2ae56b50bff764786cdd37b760eb6b94a63743f --- AndroidManifest.xml | 14 ++ res/drawable-hdpi/ic_menu_export.png | Bin 0 -> 359 bytes res/drawable-mdpi/ic_menu_export.png | Bin 0 -> 235 bytes res/drawable-xhdpi/ic_menu_export.png | Bin 0 -> 511 bytes res/drawable-xxhdpi/ic_menu_export.png | Bin 0 -> 739 bytes res/menu/all_in_one_title_bar.xml | 7 + res/menu/event_info_title_bar.xml | 9 +- res/values/cm_strings.xml | 8 + src/com/android/calendar/AllInOneActivity.java | 5 + src/com/android/calendar/EventInfoFragment.java | 99 +++++++---- src/com/android/calendar/ImportActivity.java | 186 +++++++++++++++++++++ src/com/android/calendar/icalendar/Attendee.java | 10 ++ .../android/calendar/icalendar/IcalendarUtils.java | 124 ++++++++++++-- src/com/android/calendar/icalendar/Organizer.java | 8 + src/com/android/calendar/icalendar/VCalendar.java | 27 +++ src/com/android/calendar/icalendar/VEvent.java | 44 +++++ 16 files changed, 499 insertions(+), 42 deletions(-) create mode 100644 res/drawable-hdpi/ic_menu_export.png create mode 100644 res/drawable-mdpi/ic_menu_export.png create mode 100644 res/drawable-xhdpi/ic_menu_export.png create mode 100644 res/drawable-xxhdpi/ic_menu_export.png create mode 100644 src/com/android/calendar/ImportActivity.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 3e31e412..bb4bf3e3 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -27,6 +27,7 @@ --> + @@ -156,6 +157,19 @@ + + + + + + + + + + + diff --git a/res/drawable-hdpi/ic_menu_export.png b/res/drawable-hdpi/ic_menu_export.png new file mode 100644 index 00000000..3fa4c80b Binary files /dev/null and b/res/drawable-hdpi/ic_menu_export.png differ diff --git a/res/drawable-mdpi/ic_menu_export.png b/res/drawable-mdpi/ic_menu_export.png new file mode 100644 index 00000000..1aa12976 Binary files /dev/null and b/res/drawable-mdpi/ic_menu_export.png differ diff --git a/res/drawable-xhdpi/ic_menu_export.png b/res/drawable-xhdpi/ic_menu_export.png new file mode 100644 index 00000000..ccbaa1ba Binary files /dev/null and b/res/drawable-xhdpi/ic_menu_export.png differ diff --git a/res/drawable-xxhdpi/ic_menu_export.png b/res/drawable-xxhdpi/ic_menu_export.png new file mode 100644 index 00000000..8c60c7e0 Binary files /dev/null and b/res/drawable-xxhdpi/ic_menu_export.png differ diff --git a/res/menu/all_in_one_title_bar.xml b/res/menu/all_in_one_title_bar.xml index 118eb5d1..94aebaae 100644 --- a/res/menu/all_in_one_title_bar.xml +++ b/res/menu/all_in_one_title_bar.xml @@ -63,4 +63,11 @@ android:icon="@drawable/ic_menu_settings_holo_light" android:showAsAction="never" android:orderInCategory="8" /> + diff --git a/res/menu/event_info_title_bar.xml b/res/menu/event_info_title_bar.xml index 75623a44..c78beb31 100644 --- a/res/menu/event_info_title_bar.xml +++ b/res/menu/event_info_title_bar.xml @@ -36,10 +36,17 @@ android:enabled="false" android:visible="false" /> + diff --git a/res/values/cm_strings.xml b/res/values/cm_strings.xml index 5652b3ef..65409309 100644 --- a/res/values/cm_strings.xml +++ b/res/values/cm_strings.xml @@ -50,4 +50,12 @@ Send event to: + + Export to sdcard + Event exported successfully : %1s + Import event + Nothing to import + Importing to calendar failed + Pick file to import + diff --git a/src/com/android/calendar/AllInOneActivity.java b/src/com/android/calendar/AllInOneActivity.java index d9e1a9ca..b43a1262 100644 --- a/src/com/android/calendar/AllInOneActivity.java +++ b/src/com/android/calendar/AllInOneActivity.java @@ -772,6 +772,9 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH getMenuInflater().inflate(extensionMenuRes, menu); } + MenuItem item = menu.findItem(R.id.action_import); + item.setVisible(ImportActivity.hasThingsToImport(this)); + mSearchMenu = menu.findItem(R.id.action_search); mSearchView = (SearchView) mSearchMenu.getActionView(); if (mSearchView != null) { @@ -885,6 +888,8 @@ public class AllInOneActivity extends AbstractCalendarActivity implements EventH GoToDialogFragment goToFrg = GoToDialogFragment.newInstance(timeZone); goToFrg.show(getFragmentManager(), "goto"); return true; + } else if (itemId == R.id.action_import) { + ImportActivity.pickImportFile(this); } else { return mExtensions.handleItemSelected(item, this); } diff --git a/src/com/android/calendar/EventInfoFragment.java b/src/com/android/calendar/EventInfoFragment.java index 90f73b02..4374226e 100644 --- a/src/com/android/calendar/EventInfoFragment.java +++ b/src/com/android/calendar/EventInfoFragment.java @@ -48,6 +48,7 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; +import android.os.Environment; import android.provider.CalendarContract; import android.provider.CalendarContract.Attendees; import android.provider.CalendarContract.Calendars; @@ -185,6 +186,14 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange | TOKEN_QUERY_ATTENDEES | TOKEN_QUERY_CALENDARS | TOKEN_QUERY_EVENT | TOKEN_QUERY_REMINDERS | TOKEN_QUERY_VISIBLE_CALENDARS | TOKEN_QUERY_COLORS; + public static final File EXPORT_SDCARD_DIRECTORY = new File( + Environment.getExternalStorageDirectory(), "CalendarEvents"); + + private enum ShareType { + SDCARD, + INTENT + } + private int mCurrentQuery = 0; private static final String[] EVENT_PROJECTION = new String[] { @@ -1258,7 +1267,9 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange } else if (itemId == R.id.info_action_change_color) { showEventColorPickerDialog(); } else if (itemId == R.id.info_action_share_event) { - shareEvent(); + shareEvent(ShareType.INTENT); + } else if (itemId == R.id.info_action_export) { + shareEvent(ShareType.SDCARD); } return super.onOptionsItemSelected(item); } @@ -1267,7 +1278,7 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange * Generates an .ics formatted file with the event info and launches intent chooser to * share said file */ - private void shareEvent() { + private void shareEvent(ShareType type) { // Create the respective ICalendar objects from the event info VCalendar calendar = new VCalendar(); calendar.addProperty(VCalendar.VERSION, "2.0"); @@ -1330,37 +1341,62 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange // prefix length constraint is imposed by File#createTempFile filePrefix = "invite"; } - File inviteFile = File.createTempFile(filePrefix, ".ics", - mActivity.getExternalCacheDir()); + + filePrefix = filePrefix.replaceAll("\\W+", " "); + + if (!filePrefix.endsWith(" ")) { + filePrefix += " "; + } + + File dir; + if (type == ShareType.SDCARD) { + dir = EXPORT_SDCARD_DIRECTORY; + if (!dir.exists()) { + dir.mkdir(); + } + } else { + dir = mActivity.getExternalCacheDir(); + } + + File inviteFile = IcalendarUtils.createTempFile(filePrefix, ".ics", + dir); + 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"); - - Intent chooserIntent = Intent.createChooser(shareIntent, - getResources().getString(R.string.cal_share_intent_title)); - - // The MMS app only responds to "text/x-vcalendar" so we create a chooser intent - // that includes the targeted mms intent + any that respond to the above general - // purpose "application/octet-stream" intent. - File vcsInviteFile = File.createTempFile(filePrefix, ".vcs", - mActivity.getExternalCacheDir()); - // for now , we are duplicating ics file and using that as the vcs file - // TODO: revisit above - if (IcalendarUtils.copyFile(inviteFile, vcsInviteFile)) { - Intent mmsShareIntent = new Intent(); - mmsShareIntent.setAction(Intent.ACTION_SEND); - mmsShareIntent.setPackage("com.android.mms"); - mmsShareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(vcsInviteFile)); - mmsShareIntent.setType("text/x-vcalendar"); - chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, - new Intent[]{mmsShareIntent}); + if (type == ShareType.INTENT) { + 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"); + + Intent chooserIntent = Intent.createChooser(shareIntent, + getResources().getString(R.string.cal_share_intent_title)); + + // The MMS app only responds to "text/x-vcalendar" so we create a chooser intent + // that includes the targeted mms intent + any that respond to the above general + // purpose "application/octet-stream" intent. + File vcsInviteFile = File.createTempFile(filePrefix, ".vcs", + mActivity.getExternalCacheDir()); + + // for now , we are duplicating ics file and using that as the vcs file + // TODO: revisit above + if (IcalendarUtils.copyFile(inviteFile, vcsInviteFile)) { + Intent mmsShareIntent = new Intent(); + mmsShareIntent.setAction(Intent.ACTION_SEND); + mmsShareIntent.setPackage("com.android.mms"); + mmsShareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(vcsInviteFile)); + mmsShareIntent.setType("text/x-vcalendar"); + chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, + new Intent[]{mmsShareIntent}); + } + startActivity(chooserIntent); + } else { + String msg = getString(R.string.cal_export_succ_msg); + Toast.makeText(mActivity, String.format(msg, inviteFile), + Toast.LENGTH_SHORT).show(); } - startActivity(chooserIntent); isShareSuccessful = true; } else { @@ -1368,6 +1404,7 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange isShareSuccessful = false; } } catch (IOException e) { + e.printStackTrace(); isShareSuccessful = false; } diff --git a/src/com/android/calendar/ImportActivity.java b/src/com/android/calendar/ImportActivity.java new file mode 100644 index 00000000..0a4a89a1 --- /dev/null +++ b/src/com/android/calendar/ImportActivity.java @@ -0,0 +1,186 @@ +package com.android.calendar; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.ActivityNotFoundException; +import android.content.ContentResolver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.provider.CalendarContract; +import android.text.TextUtils; +import android.widget.Toast; +import com.android.calendar.icalendar.Attendee; +import com.android.calendar.icalendar.IcalendarUtils; +import com.android.calendar.icalendar.VCalendar; +import com.android.calendar.icalendar.VEvent; + +import java.io.File; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.LinkedList; +import java.util.TimeZone; + +public class ImportActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (!isValidIntent()) { + Toast.makeText(this, R.string.cal_nothing_to_import, Toast.LENGTH_SHORT).show(); + finish(); + } else { + parseCalFile(); + } + } + + private long getLocalTimeFromString(String iCalDate) { + SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); + format.setTimeZone(TimeZone.getTimeZone("UTC")); + + try { + format.parse(iCalDate); + format.setTimeZone(TimeZone.getDefault()); + return format.getCalendar().getTimeInMillis(); + } catch (ParseException e) { + e.printStackTrace(); + } + + return System.currentTimeMillis(); + } + + private void showErrorToast() { + Toast.makeText(this, R.string.cal_import_error_msg, Toast.LENGTH_SHORT).show(); + finish(); + } + + private void parseCalFile() { + Uri uri = getIntent().getData(); + VCalendar calendar = IcalendarUtils.readCalendarFromFile(this, uri); + + if (calendar == null) { + showErrorToast(); + return; + } + + Intent calIntent = new Intent(Intent.ACTION_INSERT); + calIntent.setType("vnd.android.cursor.item/event"); + + LinkedList events = calendar.getAllEvents(); + if (events == null) { + showErrorToast(); + return; + } + + VEvent firstEvent = calendar.getAllEvents().getFirst(); + calIntent.putExtra(CalendarContract.Events.TITLE, + IcalendarUtils.uncleanseString(firstEvent.getProperty(VEvent.SUMMARY))); + calIntent.putExtra(CalendarContract.Events.EVENT_LOCATION, + IcalendarUtils.uncleanseString(firstEvent.getProperty(VEvent.LOCATION))); + calIntent.putExtra(CalendarContract.Events.DESCRIPTION, + IcalendarUtils.uncleanseString(firstEvent.getProperty(VEvent.DESCRIPTION))); + calIntent.putExtra(CalendarContract.Events.ORGANIZER, + IcalendarUtils.uncleanseString(firstEvent.getProperty(VEvent.ORGANIZER))); + + if (firstEvent.mAttendees.size() > 0) { + StringBuilder builder = new StringBuilder(); + for (Attendee attendee : firstEvent.mAttendees) { + builder.append(attendee.mEmail); + builder.append(","); + } + calIntent.putExtra(Intent.EXTRA_EMAIL, builder.toString()); + } + + String dtStart = firstEvent.getProperty(VEvent.DTSTART); + if (!TextUtils.isEmpty(dtStart)) { + calIntent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, + getLocalTimeFromString(dtStart)); + } + + String dtEnd = firstEvent.getProperty(VEvent.DTEND); + if (!TextUtils.isEmpty(dtEnd)) { + calIntent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, + getLocalTimeFromString(dtEnd)); + } + + try { + startActivity(calIntent); + } catch (ActivityNotFoundException e) { + // Oh well... + } finally { + finish(); + } + } + + private boolean isValidIntent() { + Intent intent = getIntent(); + if (intent == null) { + return false; + } + Uri fileUri = intent.getData(); + if (fileUri == null) { + return false; + } + String scheme = fileUri.getScheme(); + return ContentResolver.SCHEME_CONTENT.equals(scheme) + || ContentResolver.SCHEME_FILE.equals(scheme); + } + + private static class ListFilesTask extends AsyncTask { + + private final Activity mActivity; + + public ListFilesTask(Activity activity) { + mActivity = activity; + } + + @Override + protected String[] doInBackground(Void... params) { + if (!hasThingsToImport(mActivity)) { + return null; + } + File folder = EventInfoFragment.EXPORT_SDCARD_DIRECTORY; + String[] result = null; + if (folder.exists()) { + result = folder.list(); + } + return result; + } + + @Override + protected void onPostExecute(final String[] files) { + if (files == null || files.length == 0) { + Toast.makeText(mActivity, R.string.cal_nothing_to_import, + Toast.LENGTH_SHORT).show(); + return; + } + + AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); + builder.setTitle(R.string.cal_pick_ics) + .setItems(files, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + Intent i = new Intent(mActivity, ImportActivity.class); + File f = new File(EventInfoFragment.EXPORT_SDCARD_DIRECTORY, + files[which]); + i.setData(Uri.fromFile(f)); + mActivity.startActivity(i); + } + }); + builder.show(); + } + + } + + public static void pickImportFile(Activity activity) { + new ListFilesTask(activity).execute(); + } + + public static boolean hasThingsToImport(Context context) { + File folder = EventInfoFragment.EXPORT_SDCARD_DIRECTORY; + return folder.exists() && folder.list().length > 0; + } + +} diff --git a/src/com/android/calendar/icalendar/Attendee.java b/src/com/android/calendar/icalendar/Attendee.java index fc8c78bb..c3c2ba84 100644 --- a/src/com/android/calendar/icalendar/Attendee.java +++ b/src/com/android/calendar/icalendar/Attendee.java @@ -5,6 +5,7 @@ package com.android.calendar.icalendar; import java.util.HashMap; +import java.util.ListIterator; /** * Models the Attendee component of a calendar event @@ -74,4 +75,13 @@ public class Attendee { return output.toString(); } + public void populateFromEntries(ListIterator iter) { + String line = iter.next(); + if (line.contains("ATTENDEE")) { + String entry = VEvent.parseTillNextAttribute(iter, line); + String[] entries = entry.split("X-NUM-GUESTS=0:mailto:"); + mEmail = entries[1]; + } + } + } diff --git a/src/com/android/calendar/icalendar/IcalendarUtils.java b/src/com/android/calendar/icalendar/IcalendarUtils.java index e179926b..efbb4583 100644 --- a/src/com/android/calendar/icalendar/IcalendarUtils.java +++ b/src/com/android/calendar/icalendar/IcalendarUtils.java @@ -4,20 +4,17 @@ package com.android.calendar.icalendar; +import android.content.ContentResolver; +import android.content.Context; +import android.net.Uri; import android.provider.CalendarContract; -import android.util.Log; import com.android.calendar.CalendarEventModel; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; +import java.io.*; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Calendar; -import java.util.Date; +import java.util.Random; import java.util.TimeZone; /** @@ -25,7 +22,21 @@ import java.util.TimeZone; */ public class IcalendarUtils { - private static int sPermittedLineLength = 75; // Line length mandated by iCalendar format + public static int sPermittedLineLength = 75; // Line length mandated by iCalendar format + private static final Random tempFileRandom = new Random(); + + public static String uncleanseString(CharSequence sequence) { + if (sequence == null) return null; + String input = sequence.toString(); + + // reintroduce new lines with the literal '\n' + input = input.replaceAll("\\\\n", "\n"); + // reintroduce semicolons and commas + input = input.replaceAll("\\\\;", ";"); + input = input.replaceAll("\\\\\\,", ","); + + return input; + } /** * ensure the string conforms to the iCalendar encoding requirements @@ -46,6 +57,99 @@ public class IcalendarUtils { return input; } + /** + * Creates an empty temporary file in the given directory using the given + * prefix and suffix as part of the file name. If {@code suffix} is null, {@code .tmp} is used. + * + *

Note that this method does not call {@link #deleteOnExit}, but see the + * documentation for that method before you call it manually. + * + * @param prefix + * the prefix to the temp file name. + * @param suffix + * the suffix to the temp file name. + * @param directory + * the location to which the temp file is to be written, or + * {@code null} for the default location for temporary files, + * which is taken from the "java.io.tmpdir" system property. It + * may be necessary to set this property to an existing, writable + * directory for this method to work properly. + * @return the temporary file. + * @throws IllegalArgumentException + * if the length of {@code prefix} is less than 3. + * @throws IOException + * if an error occurs when writing the file. + */ + public static File createTempFile(String prefix, String suffix, File directory) + throws IOException { + // Force a prefix null check first + if (prefix.length() < 3) { + throw new IllegalArgumentException("prefix must be at least 3 characters"); + } + if (suffix == null) { + suffix = ".tmp"; + } + File tmpDirFile = directory; + if (tmpDirFile == null) { + String tmpDir = System.getProperty("java.io.tmpdir", "."); + tmpDirFile = new File(tmpDir); + } + File result; + do { + result = new File(tmpDirFile, + prefix + tempFileRandom.nextInt(Integer.MAX_VALUE) + suffix); + } while (!result.createNewFile()); + return result; + } + + public static VCalendar readCalendarFromFile(Context context, Uri uri) { + ArrayList contents = getStringArrayFromFile(context, uri); + if (contents == null || contents.isEmpty()) { + return null; + } + VCalendar calendar = new VCalendar(); + calendar.populateFromString(contents); + return calendar; + } + + public static ArrayList getStringArrayFromFile(Context context, Uri uri) { + String scheme = uri.getScheme(); + InputStream inputStream = null; + if(ContentResolver.SCHEME_CONTENT.equals(scheme)) { + try { + inputStream = context.getContentResolver().openInputStream(uri); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } else if (ContentResolver.SCHEME_FILE.equals(scheme)) { + File f = new File(uri.getPath()); + try { + inputStream = new FileInputStream(f); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + + if (inputStream == null) { + return null; + } + + ArrayList result = new ArrayList(); + + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + String line; + while ((line = reader.readLine()) != null) { + result.add(line); + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + return result; + } + /** * Stringify VCalendar object and write to file * @param calendar diff --git a/src/com/android/calendar/icalendar/Organizer.java b/src/com/android/calendar/icalendar/Organizer.java index 2e039849..9f9780d5 100644 --- a/src/com/android/calendar/icalendar/Organizer.java +++ b/src/com/android/calendar/icalendar/Organizer.java @@ -39,4 +39,12 @@ public class Organizer { return output.toString(); } + public static Organizer populateFromICalString(String iCalFormattedString) { + // TODO add santiy checks + String[] organizer = iCalFormattedString.split(";"); + String[] entries = organizer[1].split(":"); + String name = entries[0].replace("CN=", ""); + String email = entries[1].replace("mailto=", ""); + return new Organizer(name, email); + } } diff --git a/src/com/android/calendar/icalendar/VCalendar.java b/src/com/android/calendar/icalendar/VCalendar.java index 00c7b81b..9f422e6f 100644 --- a/src/com/android/calendar/icalendar/VCalendar.java +++ b/src/com/android/calendar/icalendar/VCalendar.java @@ -4,8 +4,12 @@ package com.android.calendar.icalendar; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; /** * Models the Calendar/VCalendar component of the iCalendar format @@ -102,6 +106,29 @@ public class VCalendar { return output.toString(); } + public void populateFromString(ArrayList input) { + ListIterator iter = input.listIterator(); + + while (iter.hasNext()) { + String line = iter.next(); + if (line.contains("BEGIN:VEVENT")) { + // Go one previous, so VEvent, parses current line + iter.previous(); + + // Offload to vevent for parsing + VEvent event = new VEvent(); + event.populateFromEntries(iter); + mEvents.add(event); + } else if (line.contains("END:VCALENDAR")) { + break; + } + } + } + + public String getProperty(String key) { + return mProperties.get(key); + } + /** * TODO: Aggressive validation of VCalendar and all of its components to ensure they conform * to the ical specification diff --git a/src/com/android/calendar/icalendar/VEvent.java b/src/com/android/calendar/icalendar/VEvent.java index 1aff0bd4..672aa18b 100644 --- a/src/com/android/calendar/icalendar/VEvent.java +++ b/src/com/android/calendar/icalendar/VEvent.java @@ -6,6 +6,7 @@ package com.android.calendar.icalendar; import java.util.HashMap; import java.util.LinkedList; +import java.util.ListIterator; import java.util.UUID; /** @@ -173,4 +174,47 @@ public class VEvent { return sb.toString(); } + public void populateFromEntries(ListIterator iter) { + while (iter.hasNext()) { + String line = iter.next(); + if (line.contains("BEGIN:VEVENT")) { + // Continue + } else if (line.startsWith("END:EVENT")) { + break; + } else if (line.startsWith("ORGANIZER")) { + String entry = parseTillNextAttribute(iter, line); + mOrganizer = Organizer.populateFromICalString(entry); + } else if (line.startsWith("ATTENDEE")) { + // Go one previous, so VEvent, parses current line + iter.previous(); + + // Offload to Attendee for parsing + Attendee attendee = new Attendee(); + attendee.populateFromEntries(iter); + mAttendees.add(attendee); + } else if (line.contains(":")) { + String entry = parseTillNextAttribute(iter, line); + int indexOfFirstColon = entry.indexOf(":"); + String key = entry.substring(0, indexOfFirstColon); + String value = entry.substring(indexOfFirstColon + 1); + mProperties.put(key, value); + } + } + } + + public static String parseTillNextAttribute(ListIterator iter, String currentLine) { + StringBuilder parse = new StringBuilder(); + parse.append(currentLine); + while (iter.hasNext()) { + String line = iter.next(); + if (line.startsWith(" ")) { + parse.append(line.replaceFirst(" ", "")); + } else { + iter.previous(); + break; + } + } + return parse.toString(); + } + } -- cgit v1.2.3