19066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project/* 29066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Copyright (C) 2007 The Android Open Source Project 39066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 49066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License"); 59066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * you may not use this file except in compliance with the License. 69066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * You may obtain a copy of the License at 79066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 89066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * http://www.apache.org/licenses/LICENSE-2.0 99066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Unless required by applicable law or agreed to in writing, software 119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS, 129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * See the License for the specific language governing permissions and 149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * limitations under the License. 159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectpackage android.pim; 189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 199066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.content.ContentValues; 209066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.database.Cursor; 219066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.provider.Calendar; 229066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.text.TextUtils; 239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.text.format.Time; 249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.util.Config; 259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.util.Log; 269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.List; 2866c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglioimport java.util.regex.Pattern; 299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project/** 319066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Basic information about a recurrence, following RFC 2445 Section 4.8.5. 329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Contains the RRULEs, RDATE, EXRULEs, and EXDATE properties. 339066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectpublic class RecurrenceSet { 359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 369066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private final static String TAG = "CalendarProvider"; 379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 389066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private final static String RULE_SEPARATOR = "\n"; 3966c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio private final static String FOLDING_SEPARATOR = "\n "; 409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // TODO: make these final? 429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public EventRecurrence[] rrules = null; 439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public long[] rdates = null; 449066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public EventRecurrence[] exrules = null; 459066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public long[] exdates = null; 469066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 479066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 489066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Creates a new RecurrenceSet from information stored in the 499066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * events table in the CalendarProvider. 509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param values The values retrieved from the Events table. 519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 5224b5bdd29e202d107ffaecb66229280253dd33a2Fabrice Di Meglio public RecurrenceSet(ContentValues values) 5324b5bdd29e202d107ffaecb66229280253dd33a2Fabrice Di Meglio throws EventRecurrence.InvalidFormatException { 549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String rruleStr = values.getAsString(Calendar.Events.RRULE); 559066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String rdateStr = values.getAsString(Calendar.Events.RDATE); 569066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String exruleStr = values.getAsString(Calendar.Events.EXRULE); 579066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String exdateStr = values.getAsString(Calendar.Events.EXDATE); 589066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project init(rruleStr, rdateStr, exruleStr, exdateStr); 599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 609066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 619066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Creates a new RecurrenceSet from information stored in a database 639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * {@link Cursor} pointing to the events table in the 649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * CalendarProvider. The cursor must contain the RRULE, RDATE, EXRULE, 659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * and EXDATE columns. 669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param cursor The cursor containing the RRULE, RDATE, EXRULE, and EXDATE 689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * columns. 699066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 7024b5bdd29e202d107ffaecb66229280253dd33a2Fabrice Di Meglio public RecurrenceSet(Cursor cursor) 7124b5bdd29e202d107ffaecb66229280253dd33a2Fabrice Di Meglio throws EventRecurrence.InvalidFormatException { 729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project int rruleColumn = cursor.getColumnIndex(Calendar.Events.RRULE); 739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project int rdateColumn = cursor.getColumnIndex(Calendar.Events.RDATE); 749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project int exruleColumn = cursor.getColumnIndex(Calendar.Events.EXRULE); 759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project int exdateColumn = cursor.getColumnIndex(Calendar.Events.EXDATE); 769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String rruleStr = cursor.getString(rruleColumn); 779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String rdateStr = cursor.getString(rdateColumn); 789066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String exruleStr = cursor.getString(exruleColumn); 799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String exdateStr = cursor.getString(exdateColumn); 809066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project init(rruleStr, rdateStr, exruleStr, exdateStr); 819066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 829066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 839066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public RecurrenceSet(String rruleStr, String rdateStr, 8424b5bdd29e202d107ffaecb66229280253dd33a2Fabrice Di Meglio String exruleStr, String exdateStr) 8524b5bdd29e202d107ffaecb66229280253dd33a2Fabrice Di Meglio throws EventRecurrence.InvalidFormatException { 869066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project init(rruleStr, rdateStr, exruleStr, exdateStr); 879066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 889066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 899066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private void init(String rruleStr, String rdateStr, 9024b5bdd29e202d107ffaecb66229280253dd33a2Fabrice Di Meglio String exruleStr, String exdateStr) 9124b5bdd29e202d107ffaecb66229280253dd33a2Fabrice Di Meglio throws EventRecurrence.InvalidFormatException { 929066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (!TextUtils.isEmpty(rruleStr) || !TextUtils.isEmpty(rdateStr)) { 939066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 949066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (!TextUtils.isEmpty(rruleStr)) { 959066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String[] rruleStrs = rruleStr.split(RULE_SEPARATOR); 969066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project rrules = new EventRecurrence[rruleStrs.length]; 979066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project for (int i = 0; i < rruleStrs.length; ++i) { 989066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project EventRecurrence rrule = new EventRecurrence(); 999066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project rrule.parse(rruleStrs[i]); 1009066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project rrules[i] = rrule; 1019066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1029066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1039066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1049066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (!TextUtils.isEmpty(rdateStr)) { 1059066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project rdates = parseRecurrenceDates(rdateStr); 1069066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1079066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1089066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (!TextUtils.isEmpty(exruleStr)) { 1099066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String[] exruleStrs = exruleStr.split(RULE_SEPARATOR); 1109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project exrules = new EventRecurrence[exruleStrs.length]; 1119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project for (int i = 0; i < exruleStrs.length; ++i) { 1129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project EventRecurrence exrule = new EventRecurrence(); 1139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project exrule.parse(exruleStr); 1149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project exrules[i] = exrule; 1159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (!TextUtils.isEmpty(exdateStr)) { 1199066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project exdates = parseRecurrenceDates(exdateStr); 1209066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1219066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1229066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 1259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Returns whether or not a recurrence is defined in this RecurrenceSet. 1269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @return Whether or not a recurrence is defined in this RecurrenceSet. 1279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 1289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public boolean hasRecurrence() { 1299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return (rrules != null || rdates != null); 1309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1319066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 1339066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Parses the provided RDATE or EXDATE string into an array of longs 1349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * representing each date/time in the recurrence. 1359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param recurrence The recurrence to be parsed. 1369066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @return The list of date/times. 1379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 1389066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public static long[] parseRecurrenceDates(String recurrence) { 1399066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // TODO: use "local" time as the default. will need to handle times 1409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // that end in "z" (UTC time) explicitly at that point. 1419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String tz = Time.TIMEZONE_UTC; 1429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project int tzidx = recurrence.indexOf(";"); 1439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (tzidx != -1) { 1449066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project tz = recurrence.substring(0, tzidx); 1459066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project recurrence = recurrence.substring(tzidx + 1); 1469066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1479066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project Time time = new Time(tz); 1489066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String[] rawDates = recurrence.split(","); 1499066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project int n = rawDates.length; 1509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project long[] dates = new long[n]; 1519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project for (int i = 0; i<n; ++i) { 1529066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // The timezone is updated to UTC if the time string specified 'Z'. 1539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project time.parse(rawDates[i]); 1549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project dates[i] = time.toMillis(false /* use isDst */); 1559066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project time.timezone = tz; 1569066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1579066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return dates; 1589066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1609066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 1619066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Populates the database map of values with the appropriate RRULE, RDATE, 1629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * EXRULE, and EXDATE values extracted from the parsed iCalendar component. 1639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param component The iCalendar component containing the desired 1649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * recurrence specification. 1659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param values The db values that should be updated. 1669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @return true if the component contained the necessary information 1679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * to specify a recurrence. The required fields are DTSTART, 1689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * one of DTEND/DURATION, and one of RRULE/RDATE. Returns false if 1699066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * there was an error, including if the date is out of range. 1709066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 1719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public static boolean populateContentValues(ICalendar.Component component, 1729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project ContentValues values) { 1739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project ICalendar.Property dtstartProperty = 1749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project component.getFirstProperty("DTSTART"); 1759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String dtstart = dtstartProperty.getValue(); 1769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project ICalendar.Parameter tzidParam = 1779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project dtstartProperty.getFirstParameter("TZID"); 1789066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // NOTE: the timezone may be null, if this is a floating time. 1799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String tzid = tzidParam == null ? null : tzidParam.value; 1809066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project Time start = new Time(tzidParam == null ? Time.TIMEZONE_UTC : tzid); 1819066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project boolean inUtc = start.parse(dtstart); 1829066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project boolean allDay = start.allDay; 1839066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1848f57caf8eda5d0e5d3c0892da49aa47c30dc1c9aFabrice Di Meglio // We force TimeZone to UTC for "all day recurring events" as the server is sending no 1858f57caf8eda5d0e5d3c0892da49aa47c30dc1c9aFabrice Di Meglio // TimeZone in DTSTART for them 1868f57caf8eda5d0e5d3c0892da49aa47c30dc1c9aFabrice Di Meglio if (inUtc || allDay) { 1879066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project tzid = Time.TIMEZONE_UTC; 1889066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1899066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1909066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String duration = computeDuration(start, component); 1919066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String rrule = flattenProperties(component, "RRULE"); 1929066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String rdate = extractDates(component.getFirstProperty("RDATE")); 1939066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String exrule = flattenProperties(component, "EXRULE"); 1949066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String exdate = extractDates(component.getFirstProperty("EXDATE")); 1959066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1969066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if ((TextUtils.isEmpty(dtstart))|| 1979066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project (TextUtils.isEmpty(duration))|| 1989066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project ((TextUtils.isEmpty(rrule))&& 1999066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project (TextUtils.isEmpty(rdate)))) { 2009066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (Config.LOGD) { 2019066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project Log.d(TAG, "Recurrence missing DTSTART, DTEND/DURATION, " 2029066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project + "or RRULE/RDATE: " 2039066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project + component.toString()); 2049066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2059066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return false; 2069066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2079066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2089066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (allDay) { 2099066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // TODO: also change tzid to be UTC? that would be consistent, but 2109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // that would not reflect the original timezone value back to the 2119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // server. 2129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project start.timezone = Time.TIMEZONE_UTC; 2139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project long millis = start.toMillis(false /* use isDst */); 2159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project values.put(Calendar.Events.DTSTART, millis); 2169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (millis == -1) { 2179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (Config.LOGD) { 2189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project Log.d(TAG, "DTSTART is out of range: " + component.toString()); 2199066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2209066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return false; 2219066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2229066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project values.put(Calendar.Events.RRULE, rrule); 2249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project values.put(Calendar.Events.RDATE, rdate); 2259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project values.put(Calendar.Events.EXRULE, exrule); 2269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project values.put(Calendar.Events.EXDATE, exdate); 2279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project values.put(Calendar.Events.EVENT_TIMEZONE, tzid); 2289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project values.put(Calendar.Events.DURATION, duration); 2299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project values.put(Calendar.Events.ALL_DAY, allDay ? 1 : 0); 2309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return true; 2319066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2333b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff // This can be removed when the old CalendarSyncAdapter is removed. 2349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public static boolean populateComponent(Cursor cursor, 2359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project ICalendar.Component component) { 2369066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project int dtstartColumn = cursor.getColumnIndex(Calendar.Events.DTSTART); 2389066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project int durationColumn = cursor.getColumnIndex(Calendar.Events.DURATION); 2399066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project int tzidColumn = cursor.getColumnIndex(Calendar.Events.EVENT_TIMEZONE); 2409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project int rruleColumn = cursor.getColumnIndex(Calendar.Events.RRULE); 2419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project int rdateColumn = cursor.getColumnIndex(Calendar.Events.RDATE); 2429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project int exruleColumn = cursor.getColumnIndex(Calendar.Events.EXRULE); 2439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project int exdateColumn = cursor.getColumnIndex(Calendar.Events.EXDATE); 2449066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project int allDayColumn = cursor.getColumnIndex(Calendar.Events.ALL_DAY); 2459066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2469066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2479066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project long dtstart = -1; 2489066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (!cursor.isNull(dtstartColumn)) { 2499066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project dtstart = cursor.getLong(dtstartColumn); 2509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String duration = cursor.getString(durationColumn); 2529066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String tzid = cursor.getString(tzidColumn); 2539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String rruleStr = cursor.getString(rruleColumn); 2549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String rdateStr = cursor.getString(rdateColumn); 2559066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String exruleStr = cursor.getString(exruleColumn); 2569066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String exdateStr = cursor.getString(exdateColumn); 2579066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project boolean allDay = cursor.getInt(allDayColumn) == 1; 2589066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if ((dtstart == -1) || 2609066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project (TextUtils.isEmpty(duration))|| 2619066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project ((TextUtils.isEmpty(rruleStr))&& 2629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project (TextUtils.isEmpty(rdateStr)))) { 2639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // no recurrence. 2649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return false; 2659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project ICalendar.Property dtstartProp = new ICalendar.Property("DTSTART"); 2689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project Time dtstartTime = null; 2699066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (!TextUtils.isEmpty(tzid)) { 2709066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (!allDay) { 2719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project dtstartProp.addParameter(new ICalendar.Parameter("TZID", tzid)); 2729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project dtstartTime = new Time(tzid); 2749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } else { 2759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // use the "floating" timezone 2769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project dtstartTime = new Time(Time.TIMEZONE_UTC); 2779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2789066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project dtstartTime.set(dtstart); 2809066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // make sure the time is printed just as a date, if all day. 2819066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // TODO: android.pim.Time really should take care of this for us. 2829066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (allDay) { 2839066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project dtstartProp.addParameter(new ICalendar.Parameter("VALUE", "DATE")); 2849066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project dtstartTime.allDay = true; 2859066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project dtstartTime.hour = 0; 2869066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project dtstartTime.minute = 0; 2879066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project dtstartTime.second = 0; 2889066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2899066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2909066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project dtstartProp.setValue(dtstartTime.format2445()); 2919066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project component.addProperty(dtstartProp); 2929066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project ICalendar.Property durationProp = new ICalendar.Property("DURATION"); 2939066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project durationProp.setValue(duration); 2949066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project component.addProperty(durationProp); 2953b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff 2963b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff addPropertiesForRuleStr(component, "RRULE", rruleStr); 2973b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff addPropertyForDateStr(component, "RDATE", rdateStr); 2983b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff addPropertiesForRuleStr(component, "EXRULE", exruleStr); 2993b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff addPropertyForDateStr(component, "EXDATE", exdateStr); 3003b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff return true; 3013b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff } 3023b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff 3033b95f5378957c4e985429dfefda3975416c1a039Ken Shirriffpublic static boolean populateComponent(ContentValues values, 3043b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff ICalendar.Component component) { 3053b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff long dtstart = -1; 3063b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff if (values.containsKey(Calendar.Events.DTSTART)) { 3073b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff dtstart = values.getAsLong(Calendar.Events.DTSTART); 3083b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff } 3093b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff String duration = values.getAsString(Calendar.Events.DURATION); 3103b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff String tzid = values.getAsString(Calendar.Events.EVENT_TIMEZONE); 3113b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff String rruleStr = values.getAsString(Calendar.Events.RRULE); 3123b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff String rdateStr = values.getAsString(Calendar.Events.RDATE); 3133b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff String exruleStr = values.getAsString(Calendar.Events.EXRULE); 3143b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff String exdateStr = values.getAsString(Calendar.Events.EXDATE); 31566c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio Integer allDayInteger = values.getAsInteger(Calendar.Events.ALL_DAY); 31666c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio boolean allDay = (null != allDayInteger) ? (allDayInteger == 1) : false; 3173b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff 3183b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff if ((dtstart == -1) || 3193b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff (TextUtils.isEmpty(duration))|| 3203b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff ((TextUtils.isEmpty(rruleStr))&& 3213b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff (TextUtils.isEmpty(rdateStr)))) { 3223b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff // no recurrence. 3233b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff return false; 3243b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff } 3253b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff 3263b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff ICalendar.Property dtstartProp = new ICalendar.Property("DTSTART"); 3273b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff Time dtstartTime = null; 3283b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff if (!TextUtils.isEmpty(tzid)) { 3293b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff if (!allDay) { 3303b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff dtstartProp.addParameter(new ICalendar.Parameter("TZID", tzid)); 3313b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff } 3323b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff dtstartTime = new Time(tzid); 3333b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff } else { 3343b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff // use the "floating" timezone 3353b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff dtstartTime = new Time(Time.TIMEZONE_UTC); 3363b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff } 3373b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff 3383b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff dtstartTime.set(dtstart); 3393b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff // make sure the time is printed just as a date, if all day. 3403b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff // TODO: android.pim.Time really should take care of this for us. 3413b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff if (allDay) { 3423b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff dtstartProp.addParameter(new ICalendar.Parameter("VALUE", "DATE")); 3433b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff dtstartTime.allDay = true; 3443b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff dtstartTime.hour = 0; 3453b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff dtstartTime.minute = 0; 3463b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff dtstartTime.second = 0; 3473b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff } 3483b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff 3493b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff dtstartProp.setValue(dtstartTime.format2445()); 3503b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff component.addProperty(dtstartProp); 3513b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff ICalendar.Property durationProp = new ICalendar.Property("DURATION"); 3523b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff durationProp.setValue(duration); 3533b95f5378957c4e985429dfefda3975416c1a039Ken Shirriff component.addProperty(durationProp); 3549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 3559066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project addPropertiesForRuleStr(component, "RRULE", rruleStr); 3569066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project addPropertyForDateStr(component, "RDATE", rdateStr); 3579066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project addPropertiesForRuleStr(component, "EXRULE", exruleStr); 3589066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project addPropertyForDateStr(component, "EXDATE", exdateStr); 3599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return true; 3609066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 3619066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 3629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private static void addPropertiesForRuleStr(ICalendar.Component component, 3639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String propertyName, 3649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String ruleStr) { 3659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (TextUtils.isEmpty(ruleStr)) { 3669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return; 3679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 36866c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio String[] rrules = getRuleStrings(ruleStr); 3699066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project for (String rrule : rrules) { 3709066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project ICalendar.Property prop = new ICalendar.Property(propertyName); 3719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project prop.setValue(rrule); 3729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project component.addProperty(prop); 3739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 3749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 3759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 37666c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio private static String[] getRuleStrings(String ruleStr) { 37766c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio if (null == ruleStr) { 37866c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio return new String[0]; 37966c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio } 38066c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio String unfoldedRuleStr = unfold(ruleStr); 38166c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio String[] split = unfoldedRuleStr.split(RULE_SEPARATOR); 38266c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio int count = split.length; 38366c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio for (int n = 0; n < count; n++) { 38466c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio split[n] = fold(split[n]); 38566c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio } 38666c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio return split; 38766c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio } 38866c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio 38966c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio 39066c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio private static final Pattern IGNORABLE_ICAL_WHITESPACE_RE = 39166c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio Pattern.compile("(?:\\r\\n?|\\n)[ \t]"); 39266c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio 39366c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio private static final Pattern FOLD_RE = Pattern.compile(".{75}"); 39466c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio 39566c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio /** 39666c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio * fold and unfolds ical content lines as per RFC 2445 section 4.1. 39766c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio * 39866c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio * <h3>4.1 Content Lines</h3> 39966c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio * 40066c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio * <p>The iCalendar object is organized into individual lines of text, called 40166c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio * content lines. Content lines are delimited by a line break, which is a CRLF 40266c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio * sequence (US-ASCII decimal 13, followed by US-ASCII decimal 10). 40366c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio * 40466c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio * <p>Lines of text SHOULD NOT be longer than 75 octets, excluding the line 40566c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio * break. Long content lines SHOULD be split into a multiple line 40666c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio * representations using a line "folding" technique. That is, a long line can 40766c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio * be split between any two characters by inserting a CRLF immediately 40866c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio * followed by a single linear white space character (i.e., SPACE, US-ASCII 40966c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio * decimal 32 or HTAB, US-ASCII decimal 9). Any sequence of CRLF followed 41066c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio * immediately by a single linear white space character is ignored (i.e., 41166c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio * removed) when processing the content type. 41266c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio */ 41366c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio public static String fold(String unfoldedIcalContent) { 41466c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio return FOLD_RE.matcher(unfoldedIcalContent).replaceAll("$0\r\n "); 41566c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio } 41666c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio 41766c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio public static String unfold(String foldedIcalContent) { 41866c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio return IGNORABLE_ICAL_WHITESPACE_RE.matcher( 41966c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio foldedIcalContent).replaceAll(""); 42066c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio } 42166c5bd9065f39b93d5d279e3f7c0b81c652ab17bFabrice Di Meglio 4229066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private static void addPropertyForDateStr(ICalendar.Component component, 4239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String propertyName, 4249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String dateStr) { 4259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (TextUtils.isEmpty(dateStr)) { 4269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return; 4279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 4289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 4299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project ICalendar.Property prop = new ICalendar.Property(propertyName); 4309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String tz = null; 4319066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project int tzidx = dateStr.indexOf(";"); 4329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (tzidx != -1) { 4339066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project tz = dateStr.substring(0, tzidx); 4349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project dateStr = dateStr.substring(tzidx + 1); 4359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 4369066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (!TextUtils.isEmpty(tz)) { 4379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project prop.addParameter(new ICalendar.Parameter("TZID", tz)); 4389066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 4399066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project prop.setValue(dateStr); 4409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project component.addProperty(prop); 4419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 4429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 4439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private static String computeDuration(Time start, 4449066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project ICalendar.Component component) { 4459066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // see if a duration is defined 4469066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project ICalendar.Property durationProperty = 4479066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project component.getFirstProperty("DURATION"); 4489066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (durationProperty != null) { 4499066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // just return the duration 4509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return durationProperty.getValue(); 4519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 4529066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 4539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // must compute a duration from the DTEND 4549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project ICalendar.Property dtendProperty = 4559066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project component.getFirstProperty("DTEND"); 4569066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (dtendProperty == null) { 4579066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // no DURATION, no DTEND: 0 second duration 4589066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return "+P0S"; 4599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 4609066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project ICalendar.Parameter endTzidParameter = 4619066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project dtendProperty.getFirstParameter("TZID"); 4629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String endTzid = (endTzidParameter == null) 4639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project ? start.timezone : endTzidParameter.value; 4649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 4659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project Time end = new Time(endTzid); 4669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project end.parse(dtendProperty.getValue()); 4671ce2e2e78d473cc6c07ebdc0991a2d2829a714c7Ken Shirriff long durationMillis = end.toMillis(false /* use isDst */) 4689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project - start.toMillis(false /* use isDst */); 4699066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project long durationSeconds = (durationMillis / 1000); 4701ce2e2e78d473cc6c07ebdc0991a2d2829a714c7Ken Shirriff if (start.allDay && (durationSeconds % 86400) == 0) { 4711ce2e2e78d473cc6c07ebdc0991a2d2829a714c7Ken Shirriff return "P" + (durationSeconds / 86400) + "D"; // Server wants this instead of P86400S 4721ce2e2e78d473cc6c07ebdc0991a2d2829a714c7Ken Shirriff } else { 4731ce2e2e78d473cc6c07ebdc0991a2d2829a714c7Ken Shirriff return "P" + durationSeconds + "S"; 4741ce2e2e78d473cc6c07ebdc0991a2d2829a714c7Ken Shirriff } 4759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 4769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 4779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private static String flattenProperties(ICalendar.Component component, 4789066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String name) { 4799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project List<ICalendar.Property> properties = component.getProperties(name); 4809066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (properties == null || properties.isEmpty()) { 4819066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return null; 4829066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 4839066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 4849066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (properties.size() == 1) { 4859066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return properties.get(0).getValue(); 4869066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 4879066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 4889066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project StringBuilder sb = new StringBuilder(); 4899066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 4909066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project boolean first = true; 4919066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project for (ICalendar.Property property : component.getProperties(name)) { 4929066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (first) { 4939066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project first = false; 4949066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } else { 4959066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // TODO: use commas. our RECUR parsing should handle that 4969066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // anyway. 4979066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project sb.append(RULE_SEPARATOR); 4989066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 4999066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project sb.append(property.getValue()); 5009066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 5019066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return sb.toString(); 5029066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 5039066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 5049066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private static String extractDates(ICalendar.Property recurrence) { 5059066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (recurrence == null) { 5069066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return null; 5079066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 5089066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project ICalendar.Parameter tzidParam = 5099066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project recurrence.getFirstParameter("TZID"); 5109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (tzidParam != null) { 5119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return tzidParam.value + ";" + recurrence.getValue(); 5129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 5139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return recurrence.getValue(); 5149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 5159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project} 516