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