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));
}
+
}