Merge "Prepare for removal of legacy-test from default targets" am: 524cce0089 am: f40b38da25 am: 6db7eaee89
am: 886d7d1277

Change-Id: I9fc9b526e1ce01ecf07c04d2f12e6f5c59c732f4
diff --git a/src/com/android/calendarcommon2/Duration.java b/src/com/android/calendarcommon2/Duration.java
index dad7f74..b7ad412 100644
--- a/src/com/android/calendarcommon2/Duration.java
+++ b/src/com/android/calendarcommon2/Duration.java
@@ -70,7 +70,7 @@
             index++;
         }
 
-        if (len < index) {
+        if (len <= index) {
             return ;
         }
 
@@ -81,6 +81,9 @@
                     + index);
         }
         index++;
+        if (len <= index) {
+            return;
+        }
         c = str.charAt(index);
         if (c == 'T') {
             index++;
diff --git a/src/com/android/calendarcommon2/RecurrenceSet.java b/src/com/android/calendarcommon2/RecurrenceSet.java
index 1185a1a..86e6a2d 100644
--- a/src/com/android/calendarcommon2/RecurrenceSet.java
+++ b/src/com/android/calendarcommon2/RecurrenceSet.java
@@ -91,45 +91,43 @@
                       String exruleStr, String exdateStr)
             throws EventRecurrence.InvalidFormatException {
         if (!TextUtils.isEmpty(rruleStr) || !TextUtils.isEmpty(rdateStr)) {
+            rrules = parseMultiLineRecurrenceRules(rruleStr);
+            rdates = parseMultiLineRecurrenceDates(rdateStr);
+            exrules = parseMultiLineRecurrenceRules(exruleStr);
+            exdates = parseMultiLineRecurrenceDates(exdateStr);
+        }
+    }
 
-            if (!TextUtils.isEmpty(rruleStr)) {
-                String[] rruleStrs = rruleStr.split(RULE_SEPARATOR);
-                rrules = new EventRecurrence[rruleStrs.length];
-                for (int i = 0; i < rruleStrs.length; ++i) {
-                    EventRecurrence rrule = new EventRecurrence();
-                    rrule.parse(rruleStrs[i]);
-                    rrules[i] = rrule;
-                }
-            }
+    private EventRecurrence[] parseMultiLineRecurrenceRules(String ruleStr) {
+        if (TextUtils.isEmpty(ruleStr)) {
+            return null;
+        }
+        String[] ruleStrs = ruleStr.split(RULE_SEPARATOR);
+        final EventRecurrence[] rules = new EventRecurrence[ruleStrs.length];
+        for (int i = 0; i < ruleStrs.length; ++i) {
+            EventRecurrence rule = new EventRecurrence();
+            rule.parse(ruleStrs[i]);
+            rules[i] = rule;
+        }
+        return rules;
+    }
 
-            if (!TextUtils.isEmpty(rdateStr)) {
-                rdates = parseRecurrenceDates(rdateStr);
-            }
-
-            if (!TextUtils.isEmpty(exruleStr)) {
-                String[] exruleStrs = exruleStr.split(RULE_SEPARATOR);
-                exrules = new EventRecurrence[exruleStrs.length];
-                for (int i = 0; i < exruleStrs.length; ++i) {
-                    EventRecurrence exrule = new EventRecurrence();
-                    exrule.parse(exruleStr);
-                    exrules[i] = exrule;
-                }
-            }
-
-            if (!TextUtils.isEmpty(exdateStr)) {
-                final List<Long> list = new ArrayList<Long>();
-                for (String exdate : exdateStr.split(RULE_SEPARATOR)) {
-                    final long[] dates = parseRecurrenceDates(exdate);
-                    for (long date : dates) {
-                        list.add(date);
-                    }
-                }
-                exdates = new long[list.size()];
-                for (int i = 0, n = list.size(); i < n; i++) {
-                    exdates[i] = list.get(i);
-                }
+    private long[] parseMultiLineRecurrenceDates(String dateStr) {
+        if (TextUtils.isEmpty(dateStr)) {
+            return null;
+        }
+        final List<Long> list = new ArrayList<>();
+        for (String date : dateStr.split(RULE_SEPARATOR)) {
+            final long[] parsedDates = parseRecurrenceDates(date);
+            for (long parsedDate : parsedDates) {
+                list.add(parsedDate);
             }
         }
+        final long[] result = new long[list.size()];
+        for (int i = 0, n = list.size(); i < n; i++) {
+            result[i] = list.get(i);
+        }
+        return result;
     }
 
     /**
diff --git a/tests/src/com/android/calendarcommon2/RecurrenceSetTest.java b/tests/src/com/android/calendarcommon2/RecurrenceSetTest.java
index f65c780..ac4f89f 100644
--- a/tests/src/com/android/calendarcommon2/RecurrenceSetTest.java
+++ b/tests/src/com/android/calendarcommon2/RecurrenceSetTest.java
@@ -25,6 +25,9 @@
 import android.provider.CalendarContract;
 import junit.framework.TestCase;
 
+import java.util.Arrays;
+import java.util.List;
+
 /**
  * Test some pim.RecurrenceSet functionality.
  */
@@ -37,8 +40,12 @@
                 + "DTEND;TZID=America/New_York:20080221T190000\n"
                 + "RRULE:FREQ=DAILY;UNTIL=20080222T000000Z\n"
                 + "EXDATE:20080222T120000Z";
-        verifyPopulateContentValues(recurrence, "FREQ=DAILY;UNTIL=20080222T000000Z", null,
+        final ContentValues values = verifyPopulateContentValues(recurrence,
+                "FREQ=DAILY;UNTIL=20080222T000000Z", null,
                 null, "20080222T120000Z", 1203595200000L, "America/New_York", "P43200S", 0, false);
+        verifyRecurrenceSetInitialization(new RecurrenceSet(values),
+                new String[] {"FREQ=DAILY;UNTIL=20080222T000000Z"}, null,
+                null, new Long[] {1203681600000L});
     }
 
     // Test 1 day all-day event
@@ -46,8 +53,10 @@
     public void testRecurrenceSet1() throws Exception {
         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, false);
+        final ContentValues values = verifyPopulateContentValues(recurrence,
+                "FREQ=YEARLY;WKST=SU", null, null, null, 1250812800000L, "UTC", "P1D", 1, false);
+        verifyRecurrenceSetInitialization(new RecurrenceSet(values),
+                new String[] {"FREQ=YEARLY;WKST=SU"}, null, null, null);
     }
 
     // Test 2 day all-day event
@@ -55,8 +64,10 @@
     public void testRecurrenceSet2() throws Exception {
         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, false);
+        final ContentValues values = verifyPopulateContentValues(recurrence,
+                "FREQ=YEARLY;WKST=SU", null, null, null, 1250812800000L, "UTC",  "P2D", 1, false);
+        verifyRecurrenceSetInitialization(new RecurrenceSet(values),
+                new String[] {"FREQ=YEARLY;WKST=SU"}, null, null, null);
     }
 
     // Test multi-rule RRULE.
@@ -66,9 +77,13 @@
                 + "RRULE:FREQ=YEARLY;WKST=SU\n"
                 + "RRULE:FREQ=MONTHLY;COUNT=3\n"
                 + "DURATION:P2H";
-        verifyPopulateContentValues(recurrence, "FREQ=YEARLY;WKST=SU\nFREQ=MONTHLY;COUNT=3", null,
+        final ContentValues values = verifyPopulateContentValues(recurrence,
+                "FREQ=YEARLY;WKST=SU\nFREQ=MONTHLY;COUNT=3", null,
                 null, null, 1250812800000L, "UTC", "P2H", 1 /*allDay*/, false);
         // allDay=1 just means the start time is 00:00:00 UTC.
+        verifyRecurrenceSetInitialization(new RecurrenceSet(values),
+                new String[] {"FREQ=YEARLY;WKST=SU", "FREQ=MONTHLY;COUNT=3"},
+                null, null, null);
     }
 
     // Test RDATE with VALUE=DATE.
@@ -77,11 +92,13 @@
         String recurrence = "DTSTART;TZID=America/Los_Angeles:20090821T010203\n"
                 + "RDATE;TZID=America/Los_Angeles;VALUE=DATE:20110601,20110602,20110603\n"
                 + "DURATION:P2H";
-        verifyPopulateContentValues(recurrence, null,
+        final ContentValues values = 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*/, false);
         // allDay=1 just means the start time is 00:00:00 UTC.
+        verifyRecurrenceSetInitialization(new RecurrenceSet(values),
+                null, new Long[] {1306911600000L, 1306998000000L, 1307084400000L}, null, null);
     }
 
     // Check generation of duration from events in different time zones.
@@ -90,18 +107,57 @@
         String recurrence = "DTSTART;TZID=America/Los_Angeles:20090821T070000\n"
                 + "DTEND;TZID=America/New_York:20090821T110000\n"
                 + "RRULE:FREQ=YEARLY\n";
-        verifyPopulateContentValues(recurrence, "FREQ=YEARLY", null,
+        final ContentValues values = verifyPopulateContentValues(recurrence, "FREQ=YEARLY", null,
                 null, null, 1250863200000L, "America/Los_Angeles", "P3600S" /*P1H*/, 0 /*allDay*/,
                 false);
         // TODO: would like to use P1H for duration
+        verifyRecurrenceSetInitialization(new RecurrenceSet(values),
+                new String[] {"FREQ=YEARLY"}, null, null, null);
 
         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*/,
+        final ContentValues values2 = verifyPopulateContentValues(recurrence2, "FREQ=YEARLY", null,
+                null, null, 1250863200000L, "America/New_York", "P3600S" /*P1H*/, 0 /*allDay*/,
                 false);
         // TODO: should we rigorously define which tzid becomes the "event timezone"?
+        verifyRecurrenceSetInitialization(new RecurrenceSet(values2),
+                new String[] {"FREQ=YEARLY"}, null, null, null);
+    }
+
+    // Test multi-rule EXRULE.
+    @SmallTest
+    public void testRecurrenceSet6() throws Exception {
+        final String recurrence = "DTSTART;VALUE=DATE:20090821\n"
+                + "RRULE:FREQ=YEARLY;WKST=SU\n"
+                + "RRULE:FREQ=MONTHLY;COUNT=6\n"
+                + "EXRULE:FREQ=YEARLY;INTERVAL=4\n"
+                + "EXRULE:FREQ=MONTHLY;INTERVAL=2\n"
+                + "EXDATE:20120821\n"
+                + "DURATION:P2H";
+        final ContentValues values = verifyPopulateContentValues(recurrence,
+                "FREQ=YEARLY;WKST=SU\nFREQ=MONTHLY;COUNT=6", null,
+                "FREQ=YEARLY;INTERVAL=4\nFREQ=MONTHLY;INTERVAL=2",
+                "20120821", 1250812800000L, "UTC", "P2H", 1, false);
+        verifyRecurrenceSetInitialization(new RecurrenceSet(values),
+                new String[] {"FREQ=YEARLY;WKST=SU", "FREQ=MONTHLY;COUNT=6"}, null,
+                new String[] {"FREQ=YEARLY;INTERVAL=4", "FREQ=MONTHLY;INTERVAL=2"},
+                new Long[] {1345507200000L});
+    }
+
+    // Test multi-rule RDATE and EXDATE.
+    @SmallTest
+    public void testRecurrentSet7() throws Exception {
+        final RecurrenceSet rs = new RecurrenceSet(
+                "FREQ=YEARLY;WKST=SU",
+                "America/Los_Angeles;20110601,20110602\n20110603T120000Z",
+                "FREQ=YEARLY;INTERVAL=4",
+                "America/New_York;20120601,20120602\n20120603T120000Z");
+        verifyRecurrenceSetInitialization(rs,
+                new String[] {"FREQ=YEARLY;WKST=SU"},
+                new Long[] {1306911600000L, 1306998000000L, 1307102400000L},
+                new String[] {"FREQ=YEARLY;INTERVAL=4"},
+                new Long[] {1338523200000L, 1338609600000L, 1338724800000L});
     }
 
     // Test a failure to parse the recurrence data
@@ -125,8 +181,60 @@
                 null, "20080222T120000Z", 1203595200000L, "America/New_York", "P43200S", 0, true);
     }
 
+    private void verifyRecurrenceSetInitialization(RecurrenceSet rs,
+            String[] expectedRruleStrs, Long[] expectedRdates,
+            String[] expectedExruleStrs, Long[] expectedExdates) {
+        verify(convertToEventRecurrences(expectedRruleStrs), rs.rrules);
+        verify(expectedRdates, convertToLong(rs.rdates));
+        verify(convertToEventRecurrences(expectedExruleStrs), rs.exrules);
+        verify(expectedExdates, convertToLong(rs.exdates));
+    }
+
+    private EventRecurrence[] convertToEventRecurrences(String[] ruleStrs) {
+        if (ruleStrs == null) {
+            return null;
+        }
+        final EventRecurrence[] rules = new EventRecurrence[ruleStrs.length];
+        for (int i = 0; i < ruleStrs.length; ++i) {
+            rules[i] = new EventRecurrence();
+            rules[i].parse(ruleStrs[i]);
+        }
+        return rules;
+    }
+
+    private Long[] convertToLong(long[] primitives) {
+        if (primitives == null) {
+            return null;
+        }
+        final Long[] datesLong = new Long[primitives.length];
+        for (int i = 0; i < primitives.length; ++i) {
+            datesLong[i] = primitives[i];
+        }
+        return datesLong;
+    }
+
+    private void verify(Object[] expected, Object[] actual) {
+        if (actual == null && expected == null) {
+            return;
+        }
+        assertNotNull("actual result is null but expected is not. Expected: "
+                + Arrays.toString(expected), actual);
+        assertNotNull("expected result is null but actual is not. Actual: "
+                + Arrays.toString(actual), expected);
+        assertEquals("Expected and actual are not of same size."
+                + "Expected: " + Arrays.toString(expected) + " Actual: " + Arrays.toString(actual),
+                        expected.length, actual.length);
+        List<Object> actualList = Arrays.asList(actual);
+        for (int i = 0; i < expected.length; ++i) {
+            if (!actualList.contains(expected[i])) {
+                fail("Expected: " + expected[i] + " but not found in Actual: "
+                        + Arrays.toString(actual));
+            }
+        }
+    }
+
     // run populateContentValues and verify the results
-    private void verifyPopulateContentValues(String recurrence, String rrule, String rdate,
+    private ContentValues verifyPopulateContentValues(String recurrence, String rrule, String rdate,
             String exrule, String exdate, long dtstart, String tzid, String duration, int allDay,
             boolean badFormat)
             throws ICalendar.FormatException {
@@ -139,7 +247,7 @@
 
         if (badFormat) {
             assertEquals(result, !badFormat);
-            return;
+            return null;
         }
         assertEquals(rrule, values.get(android.provider.CalendarContract.Events.RRULE));
         assertEquals(rdate, values.get(android.provider.CalendarContract.Events.RDATE));
@@ -150,6 +258,7 @@
         assertEquals(duration, values.get(android.provider.CalendarContract.Events.DURATION));
         assertEquals(allDay,
                 (int) values.getAsInteger(android.provider.CalendarContract.Events.ALL_DAY));
+        return values;
     }
 
 }