Merge remote-tracking branch 'goog/master' into HEAD

Conflicts:
	Android.mk

Change-Id: Ibf661a0b8c1d198ee01998405b81bb7919c17aaf
diff --git a/Android.mk b/Android.mk
index 99aca7f..ffa7606 100644
--- a/Android.mk
+++ b/Android.mk
@@ -16,10 +16,8 @@
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := calendar-common
-LOCAL_SDK_VERSION := 15
-LOCAL_SRC_FILES := \
-    $(call all-java-files-under, src) \
-    $(call all-java-files-under, ../../../external/libphonenumber/java/src)
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # Build the test package
diff --git a/CleanSpec.mk b/CleanSpec.mk
new file mode 100644
index 0000000..da0345b
--- /dev/null
+++ b/CleanSpec.mk
@@ -0,0 +1,46 @@
+# Copyright (C) 2011 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# If you don't need to do a full clean build but would like to touch
+# a file or delete some intermediate files, add a clean step to the end
+# of the list.  These steps will only be run once, if they haven't been
+# run before.
+#
+# E.g.:
+#     $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
+#     $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
+#
+# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
+# files that are missing or have been moved.
+#
+# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
+# Use $(OUT_DIR) to refer to the "out" directory.
+#
+# If you need to re-do something that's already mentioned, just copy
+# the command and add it to the bottom of the list.  E.g., if a change
+# that you made last week required touching a file and a change you
+# made today requires touching the same file, just copy the old
+# touch step and add it to the end of the list.
+#
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
+
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/calendar-common_intermediates)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/calendar-common_intermediates)
+
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
diff --git a/README b/README
new file mode 100644
index 0000000..bfb9a28
--- /dev/null
+++ b/README
@@ -0,0 +1,5 @@
+To build and run tests:
+
+mmm -j20 frameworks/opt/calendar
+adb install -r $OUT/data/app/CalendarCommonTests.apk
+adb shell am instrument -w com.android.calendarcommon.tests/android.test.InstrumentationTestRunner
diff --git a/src/com/android/calendarcommon/Duration.java b/src/com/android/calendarcommon/Duration.java
new file mode 100644
index 0000000..70fe89d
--- /dev/null
+++ b/src/com/android/calendarcommon/Duration.java
@@ -0,0 +1,150 @@
+/*
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package com.android.calendarcommon;
+
+import java.util.Calendar;
+
+/**
+ * According to RFC2445, durations are like this:
+ *       WEEKS
+ *     | DAYS [ HOURS [ MINUTES [ SECONDS ] ] ]
+ *     | HOURS [ MINUTES [ SECONDS ] ]
+ * it doesn't specifically, say, but this sort of implies that you can't have
+ * 70 seconds.
+ */
+public class Duration
+{
+    public int sign; // 1 or -1
+    public int weeks;
+    public int days;
+    public int hours;
+    public int minutes;
+    public int seconds;
+
+    public Duration()
+    {
+        sign = 1;
+    }
+
+    /**
+     * Parse according to RFC2445 ss4.3.6.  (It's actually a little loose with
+     * its parsing, for better or for worse)
+     */
+    public void parse(String str) throws DateException
+    {
+        sign = 1;
+        weeks = 0;
+        days = 0;
+        hours = 0;
+        minutes = 0;
+        seconds = 0;
+
+        int len = str.length();
+        int index = 0;
+        char c;
+
+        if (len < 1) {
+            return ;
+        }
+
+        c = str.charAt(0);
+        if (c == '-') {
+            sign = -1;
+            index++;
+        }
+        else if (c == '+') {
+            index++;
+        }
+
+        if (len < index) {
+            return ;
+        }
+
+        c = str.charAt(index);
+        if (c != 'P') {
+            throw new DateException (
+                    "Duration.parse(str='" + str + "') expected 'P' at index="
+                    + index);
+        }
+        index++;
+        c = str.charAt(index);
+        if (c == 'T') {
+            index++;
+        }
+
+        int n = 0;
+        for (; index < len; index++) {
+            c = str.charAt(index);
+            if (c >= '0' && c <= '9') {
+                n *= 10;
+                n += ((int)(c-'0'));
+            }
+            else if (c == 'W') {
+                weeks = n;
+                n = 0;
+            }
+            else if (c == 'H') {
+                hours = n;
+                n = 0;
+            }
+            else if (c == 'M') {
+                minutes = n;
+                n = 0;
+            }
+            else if (c == 'S') {
+                seconds = n;
+                n = 0;
+            }
+            else if (c == 'D') {
+                days = n;
+                n = 0;
+            }
+            else if (c == 'T') {
+            }
+            else {
+                throw new DateException (
+                        "Duration.parse(str='" + str + "') unexpected char '"
+                        + c + "' at index=" + index);
+            }
+        }
+    }
+
+    /**
+     * Add this to the calendar provided, in place, in the calendar.
+     */
+    public void addTo(Calendar cal)
+    {
+        cal.add(Calendar.DAY_OF_MONTH, sign*weeks*7);
+        cal.add(Calendar.DAY_OF_MONTH, sign*days);
+        cal.add(Calendar.HOUR, sign*hours);
+        cal.add(Calendar.MINUTE, sign*minutes);
+        cal.add(Calendar.SECOND, sign*seconds);
+    }
+
+    public long addTo(long dt) {
+        return dt + getMillis();
+    }
+
+    public long getMillis() {
+        long factor = 1000 * sign;
+        return factor * ((7*24*60*60*weeks)
+                + (24*60*60*days)
+                + (60*60*hours)
+                + (60*minutes)
+                + seconds);
+    }
+}
diff --git a/src/com/android/calendarcommon/EventRecurrence.java b/src/com/android/calendarcommon/EventRecurrence.java
index b179071..ac03e45 100644
--- a/src/com/android/calendarcommon/EventRecurrence.java
+++ b/src/com/android/calendarcommon/EventRecurrence.java
@@ -136,7 +136,7 @@
     }
 
     /** If set, allow lower-case recurrence rule strings.  Minor performance impact. */
-    private static final boolean ALLOW_LOWER_CASE = false;
+    private static final boolean ALLOW_LOWER_CASE = true;
 
     /** If set, validate the value of UNTIL parts.  Minor performance impact. */
     private static final boolean VALIDATE_UNTIL = false;
@@ -598,6 +598,10 @@
             parts = recur.split(";");
         }
         for (String part : parts) {
+            // allow empty part (e.g., double semicolon ";;")
+            if (TextUtils.isEmpty(part)) {
+                continue;
+            }
             int equalIndex = part.indexOf('=');
             if (equalIndex <= 0) {
                 /* no '=' or no LHS */
@@ -747,14 +751,22 @@
     /** parses COUNT=[non-negative-integer] */
     private static class ParseCount extends PartParser {
         @Override public int parsePart(String value, EventRecurrence er) {
-            er.count = parseIntRange(value, 0, Integer.MAX_VALUE, true);
+            er.count = parseIntRange(value, Integer.MIN_VALUE, Integer.MAX_VALUE, true);
+            if (er.count < 0) {
+                Log.d(TAG, "Invalid Count. Forcing COUNT to 1 from " + value);
+                er.count = 1; // invalid count. assume one time recurrence.
+            }
             return PARSED_COUNT;
         }
     }
     /** parses INTERVAL=[non-negative-integer] */
     private static class ParseInterval extends PartParser {
         @Override public int parsePart(String value, EventRecurrence er) {
-            er.interval = parseIntRange(value, 1, Integer.MAX_VALUE, false);
+            er.interval = parseIntRange(value, Integer.MIN_VALUE, Integer.MAX_VALUE, true);
+            if (er.interval < 1) {
+                Log.d(TAG, "Invalid Interval. Forcing INTERVAL to 1 from " + value);
+                er.interval = 1;
+            }
             return PARSED_INTERVAL;
         }
     }
diff --git a/src/com/android/calendarcommon/ICalendar.java b/src/com/android/calendarcommon/ICalendar.java
index ab77ed8..2374706 100644
--- a/src/com/android/calendarcommon/ICalendar.java
+++ b/src/com/android/calendarcommon/ICalendar.java
@@ -59,8 +59,8 @@
     public static class Component {
 
         // components
-        private static final String BEGIN = "BEGIN";
-        private static final String END = "END";
+        static final String BEGIN = "BEGIN";
+        static final String END = "END";
         private static final String NEWLINE = "\n";
         public static final String VCALENDAR = "VCALENDAR";
         public static final String VEVENT = "VEVENT";
diff --git a/src/com/android/calendarcommon/RecurrenceSet.java b/src/com/android/calendarcommon/RecurrenceSet.java
index 3b91a1d..d8fb7cf 100644
--- a/src/com/android/calendarcommon/RecurrenceSet.java
+++ b/src/com/android/calendarcommon/RecurrenceSet.java
@@ -178,61 +178,67 @@
      */
     public static boolean populateContentValues(ICalendar.Component component,
             ContentValues values) {
-        ICalendar.Property dtstartProperty =
-                component.getFirstProperty("DTSTART");
-        String dtstart = dtstartProperty.getValue();
-        ICalendar.Parameter tzidParam =
-                dtstartProperty.getFirstParameter("TZID");
-        // NOTE: the timezone may be null, if this is a floating time.
-        String tzid = tzidParam == null ? null : tzidParam.value;
-        Time start = new Time(tzidParam == null ? Time.TIMEZONE_UTC : tzid);
-        boolean inUtc = start.parse(dtstart);
-        boolean allDay = start.allDay;
+        try {
+            ICalendar.Property dtstartProperty =
+                    component.getFirstProperty("DTSTART");
+            String dtstart = dtstartProperty.getValue();
+            ICalendar.Parameter tzidParam =
+                    dtstartProperty.getFirstParameter("TZID");
+            // NOTE: the timezone may be null, if this is a floating time.
+            String tzid = tzidParam == null ? null : tzidParam.value;
+            Time start = new Time(tzidParam == null ? Time.TIMEZONE_UTC : tzid);
+            boolean inUtc = start.parse(dtstart);
+            boolean allDay = start.allDay;
 
-        // We force TimeZone to UTC for "all day recurring events" as the server is sending no
-        // TimeZone in DTSTART for them
-        if (inUtc || allDay) {
-            tzid = Time.TIMEZONE_UTC;
-        }
+            // We force TimeZone to UTC for "all day recurring events" as the server is sending no
+            // TimeZone in DTSTART for them
+            if (inUtc || allDay) {
+                tzid = Time.TIMEZONE_UTC;
+            }
 
-        String duration = computeDuration(start, component);
-        String rrule = flattenProperties(component, "RRULE");
-        String rdate = extractDates(component.getFirstProperty("RDATE"));
-        String exrule = flattenProperties(component, "EXRULE");
-        String exdate = extractDates(component.getFirstProperty("EXDATE"));
+            String duration = computeDuration(start, component);
+            String rrule = flattenProperties(component, "RRULE");
+            String rdate = extractDates(component.getFirstProperty("RDATE"));
+            String exrule = flattenProperties(component, "EXRULE");
+            String exdate = extractDates(component.getFirstProperty("EXDATE"));
 
-        if ((TextUtils.isEmpty(dtstart))||
-                (TextUtils.isEmpty(duration))||
-                ((TextUtils.isEmpty(rrule))&&
-                        (TextUtils.isEmpty(rdate)))) {
+            if ((TextUtils.isEmpty(dtstart))||
+                    (TextUtils.isEmpty(duration))||
+                    ((TextUtils.isEmpty(rrule))&&
+                            (TextUtils.isEmpty(rdate)))) {
+                    if (false) {
+                        Log.d(TAG, "Recurrence missing DTSTART, DTEND/DURATION, "
+                                    + "or RRULE/RDATE: "
+                                    + component.toString());
+                    }
+                    return false;
+            }
+
+            if (allDay) {
+                start.timezone = Time.TIMEZONE_UTC;
+            }
+            long millis = start.toMillis(false /* use isDst */);
+            values.put(CalendarContract.Events.DTSTART, millis);
+            if (millis == -1) {
                 if (false) {
-                    Log.d(TAG, "Recurrence missing DTSTART, DTEND/DURATION, "
-                                + "or RRULE/RDATE: "
-                                + component.toString());
+                    Log.d(TAG, "DTSTART is out of range: " + component.toString());
                 }
                 return false;
-        }
-
-        if (allDay) {
-            start.timezone = Time.TIMEZONE_UTC;
-        }
-        long millis = start.toMillis(false /* use isDst */);
-        values.put(CalendarContract.Events.DTSTART, millis);
-        if (millis == -1) {
-            if (false) {
-                Log.d(TAG, "DTSTART is out of range: " + component.toString());
             }
+
+            values.put(CalendarContract.Events.RRULE, rrule);
+            values.put(CalendarContract.Events.RDATE, rdate);
+            values.put(CalendarContract.Events.EXRULE, exrule);
+            values.put(CalendarContract.Events.EXDATE, exdate);
+            values.put(CalendarContract.Events.EVENT_TIMEZONE, tzid);
+            values.put(CalendarContract.Events.DURATION, duration);
+            values.put(CalendarContract.Events.ALL_DAY, allDay ? 1 : 0);
+            return true;
+        } catch (TimeFormatException e) {
+            // Something is wrong with the format of this event
+            Log.i(TAG,"Failed to parse event: " + component.toString());
             return false;
         }
-
-        values.put(CalendarContract.Events.RRULE, rrule);
-        values.put(CalendarContract.Events.RDATE, rdate);
-        values.put(CalendarContract.Events.EXRULE, exrule);
-        values.put(CalendarContract.Events.EXDATE, exdate);
-        values.put(CalendarContract.Events.EVENT_TIMEZONE, tzid);
-        values.put(CalendarContract.Events.DURATION, duration);
-        values.put(CalendarContract.Events.ALL_DAY, allDay ? 1 : 0);
-        return true;
     }
 
     // This can be removed when the old CalendarSyncAdapter is removed.
@@ -311,14 +317,14 @@
         if (values.containsKey(CalendarContract.Events.DTSTART)) {
             dtstart = values.getAsLong(CalendarContract.Events.DTSTART);
         }
-        String duration = values.getAsString(CalendarContract.Events.DURATION);
-        String tzid = values.getAsString(CalendarContract.Events.EVENT_TIMEZONE);
-        String rruleStr = values.getAsString(CalendarContract.Events.RRULE);
-        String rdateStr = values.getAsString(CalendarContract.Events.RDATE);
-        String exruleStr = values.getAsString(CalendarContract.Events.EXRULE);
-        String exdateStr = values.getAsString(CalendarContract.Events.EXDATE);
-        Integer allDayInteger = values.getAsInteger(CalendarContract.Events.ALL_DAY);
-        boolean allDay = (null != allDayInteger) ? (allDayInteger == 1) : false;
+        final String duration = values.getAsString(CalendarContract.Events.DURATION);
+        final String tzid = values.getAsString(CalendarContract.Events.EVENT_TIMEZONE);
+        final String rruleStr = values.getAsString(CalendarContract.Events.RRULE);
+        final String rdateStr = values.getAsString(CalendarContract.Events.RDATE);
+        final String exruleStr = values.getAsString(CalendarContract.Events.EXRULE);
+        final String exdateStr = values.getAsString(CalendarContract.Events.EXDATE);
+        final Integer allDayInteger = values.getAsInteger(CalendarContract.Events.ALL_DAY);
+        final boolean allDay = (null != allDayInteger) ? (allDayInteger == 1) : false;
 
         if ((dtstart == -1) ||
             (TextUtils.isEmpty(duration))||
@@ -364,7 +370,7 @@
         return true;
     }
 
-    private static void addPropertiesForRuleStr(ICalendar.Component component,
+    public static void addPropertiesForRuleStr(ICalendar.Component component,
                                                 String propertyName,
                                                 String ruleStr) {
         if (TextUtils.isEmpty(ruleStr)) {
@@ -424,7 +430,7 @@
             foldedIcalContent).replaceAll("");
     }
 
-    private static void addPropertyForDateStr(ICalendar.Component component,
+    public static void addPropertyForDateStr(ICalendar.Component component,
                                               String propertyName,
                                               String dateStr) {
         if (TextUtils.isEmpty(dateStr)) {
diff --git a/tests/src/com/android/calendarcommon/DurationTest.java b/tests/src/com/android/calendarcommon/DurationTest.java
new file mode 100644
index 0000000..b264713
--- /dev/null
+++ b/tests/src/com/android/calendarcommon/DurationTest.java
@@ -0,0 +1,77 @@
+/* //device/content/providers/pim/DurationTest.java
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package com.android.calendarcommon;
+
+import junit.framework.TestCase;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+public class DurationTest extends TestCase {
+
+    private void verifyDuration(String str,
+            int sign, int weeks, int days, int hours,
+            int minutes, int seconds) throws DateException {
+
+        Duration duration = new Duration();
+        duration.parse(str);
+
+        assertEquals("Duration sign is not equal for " + str, sign, duration.sign);
+        assertEquals("Duration weeks is not equal for " + str, weeks, duration.weeks);
+        assertEquals("Duration days is not equal for " + str, days, duration.days);
+        assertEquals("Duration hours is not equal for " + str, hours, duration.hours);
+        assertEquals("Duration minutes is not equal for " + str, minutes, duration.minutes);
+        assertEquals("Duration seconds is not equal for " + str, seconds, duration.seconds);
+    }
+
+    @SmallTest
+    public void testParse() throws Exception {
+        verifyDuration("P7W", 1, 7, 0, 0, 0, 0);
+        verifyDuration("PT7W", 1, 7, 0, 0, 0, 0);
+        verifyDuration("-PT7W", -1, 7, 0, 0, 0, 0);
+        verifyDuration("P15DT5H0M20S", 1, 0, 15, 5, 0, 20);
+        verifyDuration("-P15DT5H0M20S", -1, 0, 15, 5, 0, 20);
+        verifyDuration("PT1H2M3S", 1, 0, 0, 1, 2, 3);
+
+        verifyDuration("", 1, 0, 0, 0, 0, 0);
+        verifyDuration("P", 1, 0, 0, 0, 0, 0);
+        verifyDuration("P0W", 1, 0, 0, 0, 0, 0);
+        verifyDuration("P0D", 1, 0, 0, 0, 0, 0);
+        verifyDuration("PT0H0M0S", 1, 0, 0, 0, 0, 0);
+        verifyDuration("P0DT0H0M0S", 1, 0, 0, 0, 0, 0);
+    }
+
+    @SmallTest
+    public void testParseInvalidStrings() throws Exception {
+        try {
+            verifyDuration(" -P15DT5H0M20S", 0, 0, 0, 0, 0, 0);
+            fail("test didn't throw an exception but we expected it to");
+        } catch (DateException e) {
+            // expected
+        }
+
+        try {
+            verifyDuration(" not even close", 0, 0, 0, 0, 0, 0);
+            fail("test didn't throw an exception but we expected it to");
+        } catch (DateException e) {
+            // expected
+        }
+    }
+}
+
+
+
diff --git a/tests/src/com/android/calendarcommon/EventRecurrenceTest.java b/tests/src/com/android/calendarcommon/EventRecurrenceTest.java
index 35777eb..efd5f1c 100644
--- a/tests/src/com/android/calendarcommon/EventRecurrenceTest.java
+++ b/tests/src/com/android/calendarcommon/EventRecurrenceTest.java
@@ -527,6 +527,135 @@
         );
     }
 
+    // INTERVAL = 0 -> Interval = 1 bug #5676414
+    public void test20() throws Exception {
+        verifyRecurType("FREQ=YEARLY;BYMONTHDAY=18;BYMONTH=10;INTERVAL=0;",
+                /* int freq */         EventRecurrence.YEARLY,
+                /* String until */     null,
+                /* int count */        0,
+                /* int interval */     1,
+                /* int[] bysecond */   null,
+                /* int[] byminute */   null,
+                /* int[] byhour */     null,
+                /* int[] byday */      null,
+                /* int[] bydayNum */   null,
+                /* int[] bymonthday */ new int[]{18},
+                /* int[] byyearday */  null,
+                /* int[] byweekno */   null,
+                /* int[] bymonth */    new int[]{10},
+                /* int[] bysetpos */   null,
+                /* int wkst */         EventRecurrence.MO
+        );
+    }
+
+    // Working case: INTERVAL=1 -> Interval = 1 bug #5676414
+    public void test21() throws Exception {
+        verifyRecurType("FREQ=WEEKLY;WKST=SU;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR",
+                /* int freq */         EventRecurrence.WEEKLY,
+                /* String until */     null,
+                /* int count */        0,
+                /* int interval */     1,
+                /* int[] bysecond */   null,
+                /* int[] byminute */   null,
+                /* int[] byhour */     null,
+                /* int[] byday */      new int[] {
+                        EventRecurrence.MO,
+                        EventRecurrence.TU,
+                        EventRecurrence.WE,
+                        EventRecurrence.TH,
+                        EventRecurrence.FR,
+                },
+                /* int[] bydayNum */   new int[]{0, 0, 0, 0, 0},
+                /* int[] bymonthday */ null,
+                /* int[] byyearday */  null,
+                /* int[] byweekno */   null,
+                /* int[] bymonth */    null,
+                /* int[] bysetpos */   null,
+                /* int wkst */         EventRecurrence.SU
+        );
+    }
+
+    // Working case: INTERVAL=2 -> Interval = 2 bug #5676414
+    public void test22() throws Exception {
+        verifyRecurType("FREQ=WEEKLY;WKST=SU;INTERVAL=2;BYDAY=MO,TU,WE,TH,FR",
+                /* int freq */         EventRecurrence.WEEKLY,
+                /* String until */     null,
+                /* int count */        0,
+                /* int interval */     2,
+                /* int[] bysecond */   null,
+                /* int[] byminute */   null,
+                /* int[] byhour */     null,
+                /* int[] byday */      new int[] {
+                        EventRecurrence.MO,
+                        EventRecurrence.TU,
+                        EventRecurrence.WE,
+                        EventRecurrence.TH,
+                        EventRecurrence.FR,
+                },
+                /* int[] bydayNum */   new int[]{0, 0, 0, 0, 0},
+                /* int[] bymonthday */ null,
+                /* int[] byyearday */  null,
+                /* int[] byweekno */   null,
+                /* int[] bymonth */    null,
+                /* int[] bysetpos */   null,
+                /* int wkst */         EventRecurrence.SU
+        );
+    }
+
+    // COUNT < 0 -> Count = 1 bug #5676414
+    public void test23() throws Exception {
+        verifyRecurType("FREQ=WEEKLY;COUNT=-20;BYDAY=MO,TU,WE,TH,FR;",
+                /* int freq */         EventRecurrence.WEEKLY,
+                /* String until */     null,
+                /* int count */        1,
+                /* int interval */     0,
+                /* int[] bysecond */   null,
+                /* int[] byminute */   null,
+                /* int[] byhour */     null,
+                /* int[] byday */      new int[] {
+                        EventRecurrence.MO,
+                        EventRecurrence.TU,
+                        EventRecurrence.WE,
+                        EventRecurrence.TH,
+                        EventRecurrence.FR,
+                },
+                /* int[] bydayNum */   new int[]{0, 0, 0, 0, 0},
+                /* int[] bymonthday */ null,
+                /* int[] byyearday */  null,
+                /* int[] byweekno */   null,
+                /* int[] bymonth */    null,
+                /* int[] bysetpos */   null,
+                /* int wkst */         EventRecurrence.MO
+        );
+    }
+
+    // Working case: COUNT=2 -> Count=2 bug #5676414
+    public void test24() throws Exception {
+        verifyRecurType("FREQ=WEEKLY;COUNT=2;BYDAY=MO,TU,WE,TH,FR;",
+                /* int freq */         EventRecurrence.WEEKLY,
+                /* String until */     null,
+                /* int count */        2,
+                /* int interval */     0,
+                /* int[] bysecond */   null,
+                /* int[] byminute */   null,
+                /* int[] byhour */     null,
+                /* int[] byday */      new int[] {
+                        EventRecurrence.MO,
+                        EventRecurrence.TU,
+                        EventRecurrence.WE,
+                        EventRecurrence.TH,
+                        EventRecurrence.FR,
+                },
+                /* int[] bydayNum */   new int[]{0, 0, 0, 0, 0},
+                /* int[] bymonthday */ null,
+                /* int[] byyearday */  null,
+                /* int[] byweekno */   null,
+                /* int[] bymonth */    null,
+                /* int[] bysetpos */   null,
+                /* int wkst */         EventRecurrence.MO
+        );
+    }
+
     // for your copying pleasure
     public void fakeTestXX() throws Exception {
         verifyRecurType("FREQ=DAILY;",
@@ -714,6 +843,11 @@
         "INTERVAL=4;FREQ=YEARLY",
         "FREQ=DAILY;X-WHATEVER=blah",
         //"freq=daily;wkst=su",                               // mixed case currently not allowed
+        "FREQ=WEEKLY;INTERVAL=2;BYDAY=Mo;;UNTIL=20120327T000000Z", // double simicolon should be allowed
+        "FREQ=MONTHLY;BYDAY=1Mo",
+        "FREQ=MONTHLY;BYDAY=2Mo,2We,4Mo,4We",
+        "FREQ=MONTHLY;WKST=SU;BYMONTHDAY=25;UNTIL=20110524",
+        "FREQ=WEEKLY;BYDAY=MO;WKST=SU;UNTIL=20111218T010000Z"
     };
 
     /** The parser must reject these. */
diff --git a/tests/src/com/android/calendarcommon/RecurrenceSetTest.java b/tests/src/com/android/calendarcommon/RecurrenceSetTest.java
index 5b29fc3..3b348b0 100644
--- a/tests/src/com/android/calendarcommon/RecurrenceSetTest.java
+++ b/tests/src/com/android/calendarcommon/RecurrenceSetTest.java
@@ -38,7 +38,7 @@
                 + "RRULE:FREQ=DAILY;UNTIL=20080222T000000Z\n"
                 + "EXDATE:20080222T120000Z";
         verifyPopulateContentValues(recurrence, "FREQ=DAILY;UNTIL=20080222T000000Z", null,
-                null, "20080222T120000Z", 1203595200000L, "America/New_York", "P43200S", 0);
+                null, "20080222T120000Z", 1203595200000L, "America/New_York", "P43200S", 0, false);
     }
 
     // Test 1 day all-day event
@@ -47,7 +47,7 @@
         String recurrence = "DTSTART;VALUE=DATE:20090821\nDTEND;VALUE=DATE:20090822\n"
                 + "RRULE:FREQ=YEARLY;WKST=SU";
         verifyPopulateContentValues(recurrence, "FREQ=YEARLY;WKST=SU", null,
-                null, null, 1250812800000L, "UTC", "P1D", 1);
+                null, null, 1250812800000L, "UTC", "P1D", 1, false);
     }
 
     // Test 2 day all-day event
@@ -56,7 +56,7 @@
         String recurrence = "DTSTART;VALUE=DATE:20090821\nDTEND;VALUE=DATE:20090823\n"
                 + "RRULE:FREQ=YEARLY;WKST=SU";
         verifyPopulateContentValues(recurrence, "FREQ=YEARLY;WKST=SU", null,
-                null, null, 1250812800000L, "UTC",  "P2D", 1);
+                null, null, 1250812800000L, "UTC",  "P2D", 1, false);
     }
 
     // Test multi-rule RRULE.
@@ -67,7 +67,7 @@
                 + "RRULE:FREQ=MONTHLY;COUNT=3\n"
                 + "DURATION:P2H";
         verifyPopulateContentValues(recurrence, "FREQ=YEARLY;WKST=SU\nFREQ=MONTHLY;COUNT=3", null,
-                null, null, 1250812800000L, "UTC", "P2H", 1 /*allDay*/);
+                null, null, 1250812800000L, "UTC", "P2H", 1 /*allDay*/, false);
         // allDay=1 just means the start time is 00:00:00 UTC.
     }
 
@@ -80,7 +80,7 @@
         verifyPopulateContentValues(recurrence, null,
                 //"TZID=America/Los_Angeles;VALUE=DATE:20110601,20110602,20110603",
                 "America/Los_Angeles;20110601,20110602,20110603", // incorrect
-                null, null, 1250841723000L, "America/Los_Angeles", "P2H", 0 /*allDay*/);
+                null, null, 1250841723000L, "America/Los_Angeles", "P2H", 0 /*allDay*/, false);
         // allDay=1 just means the start time is 00:00:00 UTC.
     }
 
@@ -91,29 +91,56 @@
                 + "DTEND;TZID=America/New_York:20090821T110000\n"
                 + "RRULE:FREQ=YEARLY\n";
         verifyPopulateContentValues(recurrence, "FREQ=YEARLY", null,
-                null, null, 1250863200000L, "America/Los_Angeles", "P3600S" /*P1H*/, 0 /*allDay*/);
+                null, null, 1250863200000L, "America/Los_Angeles", "P3600S" /*P1H*/, 0 /*allDay*/,
+                false);
         // TODO: would like to use P1H for duration
 
         String recurrence2 = "DTSTART;TZID=America/New_York:20090821T100000\n"
             + "DTEND;TZID=America/Los_Angeles:20090821T080000\n"
             + "RRULE:FREQ=YEARLY\n";
         verifyPopulateContentValues(recurrence, "FREQ=YEARLY", null,
-                null, null, 1250863200000L, "America/Los_Angeles", "P3600S" /*P1H*/, 0 /*allDay*/);
+                null, null, 1250863200000L, "America/Los_Angeles", "P3600S" /*P1H*/, 0 /*allDay*/,
+                false);
         // TODO: should we rigorously define which tzid becomes the "event timezone"?
     }
 
+    // Test a failure to parse the recurrence data
+    @SmallTest
+    public void testRecurrenceSetBadDstart() throws Exception {
+        String recurrence = "DTSTART;TZID=GMT+05:30:20080221T070000\n"
+                + "DTEND;TZID=GMT+05:30:20080221T190000\n"
+                + "RRULE:FREQ=DAILY;UNTIL=20080222T000000Z\n"
+                + "EXDATE:20080222T120000Z";
+        verifyPopulateContentValues(recurrence, "FREQ=DAILY;UNTIL=20080222T000000Z", null,
+                null, "20080222T120000Z", 1203595200000L, "America/New_York", "P43200S", 0, true);
+    }
+
+    @SmallTest
+    public void testRecurrenceSetBadRrule() throws Exception {
+        String recurrence = "DTSTART;TZID=America/New_York:20080221T070000\n"
+                + "DTEND;TZID=GMT+05:30:20080221T190000\n"
+                + "RRULE:FREQ=NEVER;UNTIL=20080222T000000Z\n"
+                + "EXDATE:20080222T120000Z";
+        verifyPopulateContentValues(recurrence, "FREQ=DAILY;UNTIL=20080222T000000Z", null,
+                null, "20080222T120000Z", 1203595200000L, "America/New_York", "P43200S", 0, true);
+    }
 
     // run populateContentValues and verify the results
     private void verifyPopulateContentValues(String recurrence, String rrule, String rdate,
-            String exrule, String exdate, long dtstart, String tzid, String duration, int allDay)
+            String exrule, String exdate, long dtstart, String tzid, String duration, int allDay,
+            boolean badFormat)
             throws ICalendar.FormatException {
         ICalendar.Component recurrenceComponent =
                 new ICalendar.Component("DUMMY", null /* parent */);
         ICalendar.parseComponent(recurrenceComponent, recurrence);
         ContentValues values = new ContentValues();
-        RecurrenceSet.populateContentValues(recurrenceComponent, values);
+        boolean result = RecurrenceSet.populateContentValues(recurrenceComponent, values);
         Log.d("KS", "values " + values);
 
+        if (badFormat) {
+            assertEquals(result, !badFormat);
+            return;
+        }
         assertEquals(rrule, values.get(android.provider.CalendarContract.Events.RRULE));
         assertEquals(rdate, values.get(android.provider.CalendarContract.Events.RDATE));
         assertEquals(exrule, values.get(android.provider.CalendarContract.Events.EXRULE));
@@ -124,4 +151,5 @@
         assertEquals(allDay,
                 (int) values.getAsInteger(android.provider.CalendarContract.Events.ALL_DAY));
     }
+
 }