10d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden/* 20d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Copyright (C) 2007 The Android Open Source Project 30d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * 40d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Licensed under the Apache License, Version 2.0 (the "License"); 50d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * you may not use this file except in compliance with the License. 60d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * You may obtain a copy of the License at 70d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * 80d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * http://www.apache.org/licenses/LICENSE-2.0 90d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * 100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Unless required by applicable law or agreed to in writing, software 110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * distributed under the License is distributed on an "AS IS" BASIS, 120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * See the License for the specific language governing permissions and 140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * limitations under the License. 150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1706b3293d5af3454a39681cfd659271551354b8a0Michael Chanpackage com.android.calendarcommon2; 180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenimport android.content.ContentValues; 200d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenimport android.database.Cursor; 210d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenimport android.provider.CalendarContract; 220d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenimport android.text.TextUtils; 230d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenimport android.text.format.Time; 240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenimport android.util.Log; 25341231a27ba891e90ded672e54817ff011317931Alon Albertimport android.util.TimeFormatException; 260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 27e017a53a1a90ac1c62c1de549d63138bd8237f7dAlon Albertimport java.util.ArrayList; 280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenimport java.util.List; 290d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenimport java.util.regex.Pattern; 300d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden/** 320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Basic information about a recurrence, following RFC 2445 Section 4.8.5. 330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Contains the RRULEs, RDATE, EXRULEs, and EXDATE properties. 340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenpublic class RecurrenceSet { 360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 37e29edf9b71fbec8a7c7f0b523ca105a377632989Andy McFadden private final static String TAG = "RecurrenceSet"; 380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 390d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private final static String RULE_SEPARATOR = "\n"; 400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private final static String FOLDING_SEPARATOR = "\n "; 410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 420d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // TODO: make these final? 430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public EventRecurrence[] rrules = null; 440d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public long[] rdates = null; 450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public EventRecurrence[] exrules = null; 460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public long[] exdates = null; 470d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 480d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Creates a new RecurrenceSet from information stored in the 500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * events table in the CalendarProvider. 510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param values The values retrieved from the Events table. 520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public RecurrenceSet(ContentValues values) 540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden throws EventRecurrence.InvalidFormatException { 550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String rruleStr = values.getAsString(CalendarContract.Events.RRULE); 560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String rdateStr = values.getAsString(CalendarContract.Events.RDATE); 570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String exruleStr = values.getAsString(CalendarContract.Events.EXRULE); 580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String exdateStr = values.getAsString(CalendarContract.Events.EXDATE); 590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden init(rruleStr, rdateStr, exruleStr, exdateStr); 600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 620d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 630d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Creates a new RecurrenceSet from information stored in a database 640d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * {@link Cursor} pointing to the events table in the 650d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * CalendarProvider. The cursor must contain the RRULE, RDATE, EXRULE, 660d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * and EXDATE columns. 670d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * 680d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param cursor The cursor containing the RRULE, RDATE, EXRULE, and EXDATE 690d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * columns. 700d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public RecurrenceSet(Cursor cursor) 720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden throws EventRecurrence.InvalidFormatException { 730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int rruleColumn = cursor.getColumnIndex(CalendarContract.Events.RRULE); 740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int rdateColumn = cursor.getColumnIndex(CalendarContract.Events.RDATE); 750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int exruleColumn = cursor.getColumnIndex(CalendarContract.Events.EXRULE); 760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int exdateColumn = cursor.getColumnIndex(CalendarContract.Events.EXDATE); 770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String rruleStr = cursor.getString(rruleColumn); 780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String rdateStr = cursor.getString(rdateColumn); 790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String exruleStr = cursor.getString(exruleColumn); 800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String exdateStr = cursor.getString(exdateColumn); 810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden init(rruleStr, rdateStr, exruleStr, exdateStr); 820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public RecurrenceSet(String rruleStr, String rdateStr, 850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String exruleStr, String exdateStr) 860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden throws EventRecurrence.InvalidFormatException { 870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden init(rruleStr, rdateStr, exruleStr, exdateStr); 880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private void init(String rruleStr, String rdateStr, 910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String exruleStr, String exdateStr) 920d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden throws EventRecurrence.InvalidFormatException { 930d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (!TextUtils.isEmpty(rruleStr) || !TextUtils.isEmpty(rdateStr)) { 943f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka rrules = parseMultiLineRecurrenceRules(rruleStr); 953f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka rdates = parseMultiLineRecurrenceDates(rdateStr); 963f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka exrules = parseMultiLineRecurrenceRules(exruleStr); 973f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka exdates = parseMultiLineRecurrenceDates(exdateStr); 983f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka } 993f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka } 1000d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1013f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka private EventRecurrence[] parseMultiLineRecurrenceRules(String ruleStr) { 1023f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka if (TextUtils.isEmpty(ruleStr)) { 1033f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka return null; 1043f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka } 1053f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka String[] ruleStrs = ruleStr.split(RULE_SEPARATOR); 1063f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka final EventRecurrence[] rules = new EventRecurrence[ruleStrs.length]; 1073f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka for (int i = 0; i < ruleStrs.length; ++i) { 1083f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka EventRecurrence rule = new EventRecurrence(); 1093f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka rule.parse(ruleStrs[i]); 1103f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka rules[i] = rule; 1113f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka } 1123f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka return rules; 1133f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka } 1140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1153f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka private long[] parseMultiLineRecurrenceDates(String dateStr) { 1163f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka if (TextUtils.isEmpty(dateStr)) { 1173f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka return null; 1183f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka } 1193f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka final List<Long> list = new ArrayList<>(); 1203f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka for (String date : dateStr.split(RULE_SEPARATOR)) { 1213f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka final long[] parsedDates = parseRecurrenceDates(date); 1223f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka for (long parsedDate : parsedDates) { 1233f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka list.add(parsedDate); 1240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1263f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka final long[] result = new long[list.size()]; 1273f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka for (int i = 0, n = list.size(); i < n; i++) { 1283f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka result[i] = list.get(i); 1293f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka } 1303f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka return result; 1310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 1340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Returns whether or not a recurrence is defined in this RecurrenceSet. 1350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @return Whether or not a recurrence is defined in this RecurrenceSet. 1360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 1370d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public boolean hasRecurrence() { 1380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return (rrules != null || rdates != null); 1390d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 1420d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Parses the provided RDATE or EXDATE string into an array of longs 1430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * representing each date/time in the recurrence. 1440d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param recurrence The recurrence to be parsed. 1450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @return The list of date/times. 1460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 147341231a27ba891e90ded672e54817ff011317931Alon Albert public static long[] parseRecurrenceDates(String recurrence) 148341231a27ba891e90ded672e54817ff011317931Alon Albert throws EventRecurrence.InvalidFormatException{ 1490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // TODO: use "local" time as the default. will need to handle times 1500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // that end in "z" (UTC time) explicitly at that point. 1510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String tz = Time.TIMEZONE_UTC; 1520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int tzidx = recurrence.indexOf(";"); 1530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (tzidx != -1) { 1540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden tz = recurrence.substring(0, tzidx); 1550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden recurrence = recurrence.substring(tzidx + 1); 1560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden Time time = new Time(tz); 1580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String[] rawDates = recurrence.split(","); 1590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int n = rawDates.length; 1600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden long[] dates = new long[n]; 1610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden for (int i = 0; i<n; ++i) { 1620d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // The timezone is updated to UTC if the time string specified 'Z'. 163341231a27ba891e90ded672e54817ff011317931Alon Albert try { 164341231a27ba891e90ded672e54817ff011317931Alon Albert time.parse(rawDates[i]); 165341231a27ba891e90ded672e54817ff011317931Alon Albert } catch (TimeFormatException e) { 166341231a27ba891e90ded672e54817ff011317931Alon Albert throw new EventRecurrence.InvalidFormatException( 167341231a27ba891e90ded672e54817ff011317931Alon Albert "TimeFormatException thrown when parsing time " + rawDates[i] 168341231a27ba891e90ded672e54817ff011317931Alon Albert + " in recurrence " + recurrence); 169341231a27ba891e90ded672e54817ff011317931Alon Albert 170341231a27ba891e90ded672e54817ff011317931Alon Albert } 1710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dates[i] = time.toMillis(false /* use isDst */); 1720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden time.timezone = tz; 1730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return dates; 1750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 1780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Populates the database map of values with the appropriate RRULE, RDATE, 1790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * EXRULE, and EXDATE values extracted from the parsed iCalendar component. 1800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param component The iCalendar component containing the desired 1810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * recurrence specification. 1820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param values The db values that should be updated. 1830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @return true if the component contained the necessary information 1840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * to specify a recurrence. The required fields are DTSTART, 1850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * one of DTEND/DURATION, and one of RRULE/RDATE. Returns false if 1860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * there was an error, including if the date is out of range. 1870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 1880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static boolean populateContentValues(ICalendar.Component component, 1890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ContentValues values) { 190bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson try { 191bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson ICalendar.Property dtstartProperty = 192bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson component.getFirstProperty("DTSTART"); 193bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson String dtstart = dtstartProperty.getValue(); 194bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson ICalendar.Parameter tzidParam = 195bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson dtstartProperty.getFirstParameter("TZID"); 196bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson // NOTE: the timezone may be null, if this is a floating time. 197bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson String tzid = tzidParam == null ? null : tzidParam.value; 198bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson Time start = new Time(tzidParam == null ? Time.TIMEZONE_UTC : tzid); 199bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson boolean inUtc = start.parse(dtstart); 200bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson boolean allDay = start.allDay; 201bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson 202bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson // We force TimeZone to UTC for "all day recurring events" as the server is sending no 203bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson // TimeZone in DTSTART for them 204bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson if (inUtc || allDay) { 205bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson tzid = Time.TIMEZONE_UTC; 206bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson } 207e29edf9b71fbec8a7c7f0b523ca105a377632989Andy McFadden 208bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson String duration = computeDuration(start, component); 209bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson String rrule = flattenProperties(component, "RRULE"); 210bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson String rdate = extractDates(component.getFirstProperty("RDATE")); 211bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson String exrule = flattenProperties(component, "EXRULE"); 212bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson String exdate = extractDates(component.getFirstProperty("EXDATE")); 213bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson 214bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson if ((TextUtils.isEmpty(dtstart))|| 215bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson (TextUtils.isEmpty(duration))|| 216bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson ((TextUtils.isEmpty(rrule))&& 217bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson (TextUtils.isEmpty(rdate)))) { 218bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson if (false) { 219bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson Log.d(TAG, "Recurrence missing DTSTART, DTEND/DURATION, " 220bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson + "or RRULE/RDATE: " 221bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson + component.toString()); 222bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson } 223bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson return false; 224bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson } 2250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 226bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson if (allDay) { 227bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson start.timezone = Time.TIMEZONE_UTC; 228bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson } 229bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson long millis = start.toMillis(false /* use isDst */); 230bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson values.put(CalendarContract.Events.DTSTART, millis); 231bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson if (millis == -1) { 2320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (false) { 233bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson Log.d(TAG, "DTSTART is out of range: " + component.toString()); 2340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return false; 2360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 237bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson 238bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson values.put(CalendarContract.Events.RRULE, rrule); 239bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson values.put(CalendarContract.Events.RDATE, rdate); 240bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson values.put(CalendarContract.Events.EXRULE, exrule); 241bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson values.put(CalendarContract.Events.EXDATE, exdate); 242bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson values.put(CalendarContract.Events.EVENT_TIMEZONE, tzid); 243bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson values.put(CalendarContract.Events.DURATION, duration); 244bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson values.put(CalendarContract.Events.ALL_DAY, allDay ? 1 : 0); 245bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson return true; 246bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson } catch (TimeFormatException e) { 247bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson // Something is wrong with the format of this event 248bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson Log.i(TAG,"Failed to parse event: " + component.toString()); 2490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return false; 2500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // This can be removed when the old CalendarSyncAdapter is removed. 2540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static boolean populateComponent(Cursor cursor, 2550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Component component) { 256e29edf9b71fbec8a7c7f0b523ca105a377632989Andy McFadden 2570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int dtstartColumn = cursor.getColumnIndex(CalendarContract.Events.DTSTART); 2580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int durationColumn = cursor.getColumnIndex(CalendarContract.Events.DURATION); 2590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int tzidColumn = cursor.getColumnIndex(CalendarContract.Events.EVENT_TIMEZONE); 2600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int rruleColumn = cursor.getColumnIndex(CalendarContract.Events.RRULE); 2610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int rdateColumn = cursor.getColumnIndex(CalendarContract.Events.RDATE); 2620d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int exruleColumn = cursor.getColumnIndex(CalendarContract.Events.EXRULE); 2630d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int exdateColumn = cursor.getColumnIndex(CalendarContract.Events.EXDATE); 2640d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int allDayColumn = cursor.getColumnIndex(CalendarContract.Events.ALL_DAY); 2650d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2660d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2670d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden long dtstart = -1; 2680d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (!cursor.isNull(dtstartColumn)) { 2690d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstart = cursor.getLong(dtstartColumn); 2700d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String duration = cursor.getString(durationColumn); 2720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String tzid = cursor.getString(tzidColumn); 2730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String rruleStr = cursor.getString(rruleColumn); 2740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String rdateStr = cursor.getString(rdateColumn); 2750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String exruleStr = cursor.getString(exruleColumn); 2760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String exdateStr = cursor.getString(exdateColumn); 2770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden boolean allDay = cursor.getInt(allDayColumn) == 1; 2780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if ((dtstart == -1) || 2800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden (TextUtils.isEmpty(duration))|| 2810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ((TextUtils.isEmpty(rruleStr))&& 2820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden (TextUtils.isEmpty(rdateStr)))) { 2830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // no recurrence. 2840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return false; 2850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Property dtstartProp = new ICalendar.Property("DTSTART"); 2880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden Time dtstartTime = null; 2890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (!TextUtils.isEmpty(tzid)) { 2900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (!allDay) { 2910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartProp.addParameter(new ICalendar.Parameter("TZID", tzid)); 2920d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2930d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime = new Time(tzid); 2940d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } else { 2950d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // use the "floating" timezone 2960d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime = new Time(Time.TIMEZONE_UTC); 2970d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 298e29edf9b71fbec8a7c7f0b523ca105a377632989Andy McFadden 2990d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime.set(dtstart); 3000d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // make sure the time is printed just as a date, if all day. 3010d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // TODO: android.pim.Time really should take care of this for us. 3020d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (allDay) { 3030d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartProp.addParameter(new ICalendar.Parameter("VALUE", "DATE")); 3040d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime.allDay = true; 3050d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime.hour = 0; 3060d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime.minute = 0; 3070d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime.second = 0; 3080d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3090d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartProp.setValue(dtstartTime.format2445()); 3110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden component.addProperty(dtstartProp); 3120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Property durationProp = new ICalendar.Property("DURATION"); 3130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden durationProp.setValue(duration); 3140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden component.addProperty(durationProp); 3150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden addPropertiesForRuleStr(component, "RRULE", rruleStr); 3170d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden addPropertyForDateStr(component, "RDATE", rdateStr); 3180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden addPropertiesForRuleStr(component, "EXRULE", exruleStr); 3190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden addPropertyForDateStr(component, "EXDATE", exdateStr); 3200d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return true; 3210d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3220d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3230d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenpublic static boolean populateComponent(ContentValues values, 3240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Component component) { 3250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden long dtstart = -1; 3260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (values.containsKey(CalendarContract.Events.DTSTART)) { 3270d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstart = values.getAsLong(CalendarContract.Events.DTSTART); 3280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3291546ea7c0d8791a690af9326904f1472321c95dfAlon Albert final String duration = values.getAsString(CalendarContract.Events.DURATION); 3301546ea7c0d8791a690af9326904f1472321c95dfAlon Albert final String tzid = values.getAsString(CalendarContract.Events.EVENT_TIMEZONE); 3311546ea7c0d8791a690af9326904f1472321c95dfAlon Albert final String rruleStr = values.getAsString(CalendarContract.Events.RRULE); 3321546ea7c0d8791a690af9326904f1472321c95dfAlon Albert final String rdateStr = values.getAsString(CalendarContract.Events.RDATE); 3331546ea7c0d8791a690af9326904f1472321c95dfAlon Albert final String exruleStr = values.getAsString(CalendarContract.Events.EXRULE); 3341546ea7c0d8791a690af9326904f1472321c95dfAlon Albert final String exdateStr = values.getAsString(CalendarContract.Events.EXDATE); 3351546ea7c0d8791a690af9326904f1472321c95dfAlon Albert final Integer allDayInteger = values.getAsInteger(CalendarContract.Events.ALL_DAY); 3361546ea7c0d8791a690af9326904f1472321c95dfAlon Albert final boolean allDay = (null != allDayInteger) ? (allDayInteger == 1) : false; 3370d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if ((dtstart == -1) || 3390d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden (TextUtils.isEmpty(duration))|| 3400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ((TextUtils.isEmpty(rruleStr))&& 3410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden (TextUtils.isEmpty(rdateStr)))) { 3420d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // no recurrence. 3430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return false; 3440d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Property dtstartProp = new ICalendar.Property("DTSTART"); 3470d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden Time dtstartTime = null; 3480d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (!TextUtils.isEmpty(tzid)) { 3490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (!allDay) { 3500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartProp.addParameter(new ICalendar.Parameter("TZID", tzid)); 3510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime = new Time(tzid); 3530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } else { 3540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // use the "floating" timezone 3550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime = new Time(Time.TIMEZONE_UTC); 3560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime.set(dtstart); 3590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // make sure the time is printed just as a date, if all day. 3600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // TODO: android.pim.Time really should take care of this for us. 3610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (allDay) { 3620d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartProp.addParameter(new ICalendar.Parameter("VALUE", "DATE")); 3630d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime.allDay = true; 3640d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime.hour = 0; 3650d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime.minute = 0; 3660d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime.second = 0; 3670d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3680d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3690d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartProp.setValue(dtstartTime.format2445()); 3700d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden component.addProperty(dtstartProp); 3710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Property durationProp = new ICalendar.Property("DURATION"); 3720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden durationProp.setValue(duration); 3730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden component.addProperty(durationProp); 3740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden addPropertiesForRuleStr(component, "RRULE", rruleStr); 3760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden addPropertyForDateStr(component, "RDATE", rdateStr); 3770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden addPropertiesForRuleStr(component, "EXRULE", exruleStr); 3780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden addPropertyForDateStr(component, "EXDATE", exdateStr); 3790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return true; 3800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3821546ea7c0d8791a690af9326904f1472321c95dfAlon Albert public static void addPropertiesForRuleStr(ICalendar.Component component, 3830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String propertyName, 3840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String ruleStr) { 3850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (TextUtils.isEmpty(ruleStr)) { 3860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return; 3870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String[] rrules = getRuleStrings(ruleStr); 3890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden for (String rrule : rrules) { 3900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Property prop = new ICalendar.Property(propertyName); 3910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden prop.setValue(rrule); 3920d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden component.addProperty(prop); 3930d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3940d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3950d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3960d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private static String[] getRuleStrings(String ruleStr) { 3970d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (null == ruleStr) { 3980d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return new String[0]; 3990d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4000d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String unfoldedRuleStr = unfold(ruleStr); 4010d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String[] split = unfoldedRuleStr.split(RULE_SEPARATOR); 4020d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int count = split.length; 4030d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden for (int n = 0; n < count; n++) { 4040d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden split[n] = fold(split[n]); 4050d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4060d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return split; 4070d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4080d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4090d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private static final Pattern IGNORABLE_ICAL_WHITESPACE_RE = 4110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden Pattern.compile("(?:\\r\\n?|\\n)[ \t]"); 4120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private static final Pattern FOLD_RE = Pattern.compile(".{75}"); 4140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 4160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * fold and unfolds ical content lines as per RFC 2445 section 4.1. 4170d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * 4180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * <h3>4.1 Content Lines</h3> 4190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * 4200d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * <p>The iCalendar object is organized into individual lines of text, called 4210d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * content lines. Content lines are delimited by a line break, which is a CRLF 4220d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * sequence (US-ASCII decimal 13, followed by US-ASCII decimal 10). 4230d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * 4240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * <p>Lines of text SHOULD NOT be longer than 75 octets, excluding the line 4250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * break. Long content lines SHOULD be split into a multiple line 4260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * representations using a line "folding" technique. That is, a long line can 4270d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * be split between any two characters by inserting a CRLF immediately 4280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * followed by a single linear white space character (i.e., SPACE, US-ASCII 4290d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * decimal 32 or HTAB, US-ASCII decimal 9). Any sequence of CRLF followed 4300d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * immediately by a single linear white space character is ignored (i.e., 4310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * removed) when processing the content type. 4320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 4330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static String fold(String unfoldedIcalContent) { 4340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return FOLD_RE.matcher(unfoldedIcalContent).replaceAll("$0\r\n "); 4350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4370d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static String unfold(String foldedIcalContent) { 4380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return IGNORABLE_ICAL_WHITESPACE_RE.matcher( 4390d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden foldedIcalContent).replaceAll(""); 4400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4421546ea7c0d8791a690af9326904f1472321c95dfAlon Albert public static void addPropertyForDateStr(ICalendar.Component component, 4430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String propertyName, 4440d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String dateStr) { 4450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (TextUtils.isEmpty(dateStr)) { 4460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return; 4470d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4480d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Property prop = new ICalendar.Property(propertyName); 4500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String tz = null; 4510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int tzidx = dateStr.indexOf(";"); 4520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (tzidx != -1) { 4530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden tz = dateStr.substring(0, tzidx); 4540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dateStr = dateStr.substring(tzidx + 1); 4550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (!TextUtils.isEmpty(tz)) { 4570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden prop.addParameter(new ICalendar.Parameter("TZID", tz)); 4580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden prop.setValue(dateStr); 4600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden component.addProperty(prop); 4610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 462e29edf9b71fbec8a7c7f0b523ca105a377632989Andy McFadden 4630d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private static String computeDuration(Time start, 4640d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Component component) { 4650d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // see if a duration is defined 4660d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Property durationProperty = 4670d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden component.getFirstProperty("DURATION"); 4680d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (durationProperty != null) { 4690d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // just return the duration 4700d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return durationProperty.getValue(); 4710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // must compute a duration from the DTEND 4740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Property dtendProperty = 4750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden component.getFirstProperty("DTEND"); 4760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (dtendProperty == null) { 4770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // no DURATION, no DTEND: 0 second duration 4780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return "+P0S"; 4790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Parameter endTzidParameter = 4810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtendProperty.getFirstParameter("TZID"); 4820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String endTzid = (endTzidParameter == null) 4830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ? start.timezone : endTzidParameter.value; 4840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden Time end = new Time(endTzid); 4860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden end.parse(dtendProperty.getValue()); 4870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden long durationMillis = end.toMillis(false /* use isDst */) 4880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden - start.toMillis(false /* use isDst */); 4890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden long durationSeconds = (durationMillis / 1000); 4900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (start.allDay && (durationSeconds % 86400) == 0) { 4910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return "P" + (durationSeconds / 86400) + "D"; // Server wants this instead of P86400S 4920d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } else { 4930d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return "P" + durationSeconds + "S"; 4940d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4950d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4960d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4970d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private static String flattenProperties(ICalendar.Component component, 4980d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String name) { 4990d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden List<ICalendar.Property> properties = component.getProperties(name); 5000d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (properties == null || properties.isEmpty()) { 5010d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return null; 5020d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5030d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 5040d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (properties.size() == 1) { 5050d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return properties.get(0).getValue(); 5060d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5070d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 5080d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden StringBuilder sb = new StringBuilder(); 5090d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 5100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden boolean first = true; 5110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden for (ICalendar.Property property : component.getProperties(name)) { 5120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (first) { 5130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden first = false; 5140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } else { 5150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // TODO: use commas. our RECUR parsing should handle that 5160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // anyway. 5170d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden sb.append(RULE_SEPARATOR); 5180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden sb.append(property.getValue()); 5200d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5210d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return sb.toString(); 5220d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5230d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 5240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private static String extractDates(ICalendar.Property recurrence) { 5250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (recurrence == null) { 5260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return null; 5270d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Parameter tzidParam = 5290d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden recurrence.getFirstParameter("TZID"); 5300d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (tzidParam != null) { 5310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return tzidParam.value + ";" + recurrence.getValue(); 5320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return recurrence.getValue(); 5340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden} 536