diff --git a/app/src/androidTest/java/at/bitfire/davdroid/DateUtilsTest.java b/app/src/androidTest/java/at/bitfire/davdroid/DateUtilsTest.java
index b78fcedd..e6c2de99 100644
--- a/app/src/androidTest/java/at/bitfire/davdroid/DateUtilsTest.java
+++ b/app/src/androidTest/java/at/bitfire/davdroid/DateUtilsTest.java
@@ -10,13 +10,9 @@ package at.bitfire.davdroid;
import junit.framework.TestCase;
-import net.fortuna.ical4j.model.Date;
import net.fortuna.ical4j.model.DateList;
import net.fortuna.ical4j.model.TimeZone;
-import net.fortuna.ical4j.model.TimeZoneRegistry;
-import net.fortuna.ical4j.model.TimeZoneRegistryFactory;
import net.fortuna.ical4j.model.parameter.Value;
-import net.fortuna.ical4j.model.property.DateListProperty;
import net.fortuna.ical4j.model.property.ExDate;
import net.fortuna.ical4j.model.property.RDate;
diff --git a/app/src/androidTest/java/at/bitfire/davdroid/resource/ContactTest.java b/app/src/androidTest/java/at/bitfire/davdroid/resource/ContactTest.java
index 2a1ba153..d97b5e60 100644
--- a/app/src/androidTest/java/at/bitfire/davdroid/resource/ContactTest.java
+++ b/app/src/androidTest/java/at/bitfire/davdroid/resource/ContactTest.java
@@ -36,7 +36,7 @@ public class ContactTest extends InstrumentationTestCase {
Contact c = new Contact("test.vcf", null);
// should generate VCard 3.0 by default
- assertEquals("text/vcard", c.getMimeType());
+ assertEquals("text/vcard;charset=UTF-8", c.getMimeType());
assertTrue(new String(c.toEntity().toByteArray()).contains("VERSION:3.0"));
// now let's generate VCard 4.0
diff --git a/app/src/androidTest/java/at/bitfire/davdroid/resource/EventTest.java b/app/src/androidTest/java/at/bitfire/davdroid/resource/EventTest.java
index 68d184fa..bfccbc84 100644
--- a/app/src/androidTest/java/at/bitfire/davdroid/resource/EventTest.java
+++ b/app/src/androidTest/java/at/bitfire/davdroid/resource/EventTest.java
@@ -9,16 +9,23 @@ package at.bitfire.davdroid.resource;
import android.content.res.AssetManager;
import android.test.InstrumentationTestCase;
-import android.text.format.Time;
import net.fortuna.ical4j.data.ParserException;
+import net.fortuna.ical4j.model.Date;
+import net.fortuna.ical4j.model.DateTime;
+import net.fortuna.ical4j.model.TimeZone;
+import net.fortuna.ical4j.model.property.DtStart;
+import net.fortuna.ical4j.util.TimeZones;
import java.io.IOException;
import java.io.InputStream;
+import at.bitfire.davdroid.DateUtils;
import lombok.Cleanup;
public class EventTest extends InstrumentationTestCase {
+ protected final TimeZone tzVienna = DateUtils.tzRegistry.getTimeZone("Europe/Vienna");
+
AssetManager assetMgr;
Event eOnThatDay, eAllDay1Day, eAllDay10Days, eAllDay0Sec;
@@ -33,6 +40,21 @@ public class EventTest extends InstrumentationTestCase {
}
+ public void testGetTzID() throws Exception {
+ // DATE (without time)
+ assertEquals(TimeZones.UTC_ID, Event.getTzId(new DtStart(new Date("20150101"))));
+
+ // DATE-TIME without time zone (floating time): should be UTC (because net.fortuna.ical4j.timezone.date.floating=false)
+ assertEquals(TimeZones.UTC_ID, Event.getTzId(new DtStart(new DateTime("20150101T000000"))));
+
+ // DATE-TIME without time zone (UTC)
+ assertEquals(TimeZones.UTC_ID, Event.getTzId(new DtStart(new DateTime(1438607288000L))));
+
+ // DATE-TIME with time zone
+ assertEquals(tzVienna.getID(), Event.getTzId(new DtStart(new DateTime("20150101T000000", tzVienna))));
+ }
+
+
public void testRecurringWithException() throws Exception {
Event event = parseCalendar("recurring-with-exception1.ics");
assertTrue(event.isAllDay());
@@ -55,75 +77,31 @@ public class EventTest extends InstrumentationTestCase {
public void testStartEndTimesAllDay() throws IOException, ParserException {
// event with start date only
assertEquals(868838400000L, eOnThatDay.getDtStartInMillis());
- assertEquals(Time.TIMEZONE_UTC, eOnThatDay.getDtStartTzID());
+ assertEquals(TimeZones.UTC_ID, eOnThatDay.getDtStartTzID());
// DTEND missing in VEVENT, must have been set to DTSTART+1 day
assertEquals(868838400000L + 86400000, eOnThatDay.getDtEndInMillis());
- assertEquals(Time.TIMEZONE_UTC, eOnThatDay.getDtEndTzID());
+ assertEquals(TimeZones.UTC_ID, eOnThatDay.getDtEndTzID());
// event with start+end date for all-day event (one day)
assertEquals(868838400000L, eAllDay1Day.getDtStartInMillis());
- assertEquals(Time.TIMEZONE_UTC, eAllDay1Day.getDtStartTzID());
+ assertEquals(TimeZones.UTC_ID, eAllDay1Day.getDtStartTzID());
assertEquals(868838400000L + 86400000, eAllDay1Day.getDtEndInMillis());
- assertEquals(Time.TIMEZONE_UTC, eAllDay1Day.getDtEndTzID());
+ assertEquals(TimeZones.UTC_ID, eAllDay1Day.getDtEndTzID());
// event with start+end date for all-day event (ten days)
assertEquals(868838400000L, eAllDay10Days.getDtStartInMillis());
- assertEquals(Time.TIMEZONE_UTC, eAllDay10Days.getDtStartTzID());
+ assertEquals(TimeZones.UTC_ID, eAllDay10Days.getDtStartTzID());
assertEquals(868838400000L + 10*86400000, eAllDay10Days.getDtEndInMillis());
- assertEquals(Time.TIMEZONE_UTC, eAllDay10Days.getDtEndTzID());
+ assertEquals(TimeZones.UTC_ID, eAllDay10Days.getDtEndTzID());
// event with start+end date on some day (invalid 0 sec-event)
assertEquals(868838400000L, eAllDay0Sec.getDtStartInMillis());
- assertEquals(Time.TIMEZONE_UTC, eAllDay0Sec.getDtStartTzID());
+ assertEquals(TimeZones.UTC_ID, eAllDay0Sec.getDtStartTzID());
// DTEND invalid in VEVENT, must have been set to DTSTART+1 day
assertEquals(868838400000L + 86400000, eAllDay0Sec.getDtEndInMillis());
- assertEquals(Time.TIMEZONE_UTC, eAllDay0Sec.getDtEndTzID());
+ assertEquals(TimeZones.UTC_ID, eAllDay0Sec.getDtEndTzID());
}
- public void testTimezoneDefToTzId() {
- // test valid definition
- final String VTIMEZONE_SAMPLE = // taken from RFC 4791, 5.2.2. CALDAV:calendar-timezone Property
- "BEGIN:VCALENDAR\n" +
- "PRODID:-//Example Corp.//CalDAV Client//EN\n" +
- "VERSION:2.0\n" +
- "BEGIN:VTIMEZONE\n" +
- "TZID:US-Eastern\n" +
- "LAST-MODIFIED:19870101T000000Z\n" +
- "BEGIN:STANDARD\n" +
- "DTSTART:19671029T020000\n" +
- "RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10\n" +
- "TZOFFSETFROM:-0400\n" +
- "TZOFFSETTO:-0500\n" +
- "TZNAME:Eastern Standard Time (US & Canada)\n" +
- "END:STANDARD\n" +
- "BEGIN:DAYLIGHT\n" +
- "DTSTART:19870405T020000\n" +
- "RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4\n" +
- "TZOFFSETFROM:-0500\n" +
- "TZOFFSETTO:-0400\n" +
- "TZNAME:Eastern Daylight Time (US & Canada)\n" +
- "END:DAYLIGHT\n" +
- "END:VTIMEZONE\n" +
- "END:VCALENDAR";
- assertEquals("US-Eastern", Event.TimezoneDefToTzId(VTIMEZONE_SAMPLE));
-
- // test null value
- try {
- Event.TimezoneDefToTzId(null);
- fail();
- } catch(IllegalArgumentException e) {
- assert(true);
- }
-
- // test invalid time zone
- try {
- Event.TimezoneDefToTzId("/* invalid content */");
- fail();
- } catch(IllegalArgumentException e) {
- assert(true);
- }
- }
-
public void testUnfolding() throws IOException, InvalidResourceException {
Event e = parseCalendar("two-line-description-without-crlf.ics");
assertEquals("http://www.tgbornheim.de/index.php?sessionid=&page=&id=&sportcentergroup=&day=6", e.getDescription());
diff --git a/app/src/androidTest/java/at/bitfire/davdroid/resource/LocalCalendarTest.java b/app/src/androidTest/java/at/bitfire/davdroid/resource/LocalCalendarTest.java
index 4cfa857b..aef23aa7 100644
--- a/app/src/androidTest/java/at/bitfire/davdroid/resource/LocalCalendarTest.java
+++ b/app/src/androidTest/java/at/bitfire/davdroid/resource/LocalCalendarTest.java
@@ -25,25 +25,14 @@ import android.test.InstrumentationTestCase;
import android.util.Log;
import net.fortuna.ical4j.model.Date;
-import net.fortuna.ical4j.model.DateList;
import net.fortuna.ical4j.model.Dur;
import net.fortuna.ical4j.model.TimeZone;
-import net.fortuna.ical4j.model.TimeZoneRegistry;
-import net.fortuna.ical4j.model.TimeZoneRegistryFactory;
-import net.fortuna.ical4j.model.ValidationException;
import net.fortuna.ical4j.model.component.VAlarm;
-import net.fortuna.ical4j.model.parameter.Value;
-import net.fortuna.ical4j.model.property.DateListProperty;
import net.fortuna.ical4j.model.property.DtEnd;
import net.fortuna.ical4j.model.property.DtStart;
-import net.fortuna.ical4j.model.property.ExDate;
-import net.fortuna.ical4j.model.property.RDate;
-import net.fortuna.ical4j.util.Dates;
import java.text.ParseException;
-import java.util.ArrayList;
import java.util.Calendar;
-import java.util.List;
import at.bitfire.davdroid.DateUtils;
import lombok.Cleanup;
diff --git a/app/src/androidTest/java/at/bitfire/davdroid/resource/iCalendarTest.java b/app/src/androidTest/java/at/bitfire/davdroid/resource/iCalendarTest.java
new file mode 100644
index 00000000..b7685c80
--- /dev/null
+++ b/app/src/androidTest/java/at/bitfire/davdroid/resource/iCalendarTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Public License v3.0
+ * which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/gpl.html
+ */
+
+package at.bitfire.davdroid.resource;
+
+import junit.framework.TestCase;
+
+import net.fortuna.ical4j.data.CalendarBuilder;
+import net.fortuna.ical4j.model.Date;
+import net.fortuna.ical4j.model.TimeZone;
+import net.fortuna.ical4j.model.component.VTimeZone;
+import net.fortuna.ical4j.model.property.DtStart;
+
+import java.io.StringReader;
+
+import at.bitfire.davdroid.DateUtils;
+
+public class iCalendarTest extends TestCase {
+ protected final TimeZone tzVienna = DateUtils.tzRegistry.getTimeZone("Europe/Vienna");
+
+ public void testTimezoneDefToTzId() {
+ // test valid definition
+ assertEquals("US-Eastern", Event.TimezoneDefToTzId("BEGIN:VCALENDAR\n" +
+ "PRODID:-//Example Corp.//CalDAV Client//EN\n" +
+ "VERSION:2.0\n" +
+ "BEGIN:VTIMEZONE\n" +
+ "TZID:US-Eastern\n" +
+ "LAST-MODIFIED:19870101T000000Z\n" +
+ "BEGIN:STANDARD\n" +
+ "DTSTART:19671029T020000\n" +
+ "RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10\n" +
+ "TZOFFSETFROM:-0400\n" +
+ "TZOFFSETTO:-0500\n" +
+ "TZNAME:Eastern Standard Time (US & Canada)\n" +
+ "END:STANDARD\n" +
+ "BEGIN:DAYLIGHT\n" +
+ "DTSTART:19870405T020000\n" +
+ "RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4\n" +
+ "TZOFFSETFROM:-0500\n" +
+ "TZOFFSETTO:-0400\n" +
+ "TZNAME:Eastern Daylight Time (US & Canada)\n" +
+ "END:DAYLIGHT\n" +
+ "END:VTIMEZONE\n" +
+ "END:VCALENDAR"));
+
+ // test invalid time zone
+ assertNull(iCalendar.TimezoneDefToTzId("/* invalid content */"));
+
+ // test time zone without TZID
+ assertNull(iCalendar.TimezoneDefToTzId("BEGIN:VCALENDAR\n" +
+ "PRODID:-//Inverse inc./SOGo 2.2.10//EN\n" +
+ "VERSION:2.0\n" +
+ "END:VCALENDAR"));
+ }
+
+ public void testValidateTimeZone() throws Exception {
+ assertNotNull(tzVienna);
+
+ // date (no time zone) should be ignored
+ DtStart date = new DtStart(new Date("20150101"));
+ iCalendar.validateTimeZone(date);
+ assertNull(date.getTimeZone());
+
+ // date-time (Europe/Vienna) should be unchanged
+ DtStart dtStart = new DtStart("20150101", tzVienna);
+ iCalendar.validateTimeZone(dtStart);
+ assertEquals(tzVienna, dtStart.getTimeZone());
+
+ // time zone that is not available on Android systems should be changed to system default
+ CalendarBuilder builder = new CalendarBuilder();
+ net.fortuna.ical4j.model.Calendar cal = builder.build(new StringReader("BEGIN:VCALENDAR\n" +
+ "BEGIN:VTIMEZONE\n" +
+ "TZID:CustomTime\n" +
+ "BEGIN:STANDARD\n" +
+ "TZOFFSETFROM:-0400\n" +
+ "TZOFFSETTO:-0500\n" +
+ "DTSTART:19600101T000000\n" +
+ "END:STANDARD\n" +
+ "END:VTIMEZONE\n" +
+ "END:VCALENDAR"));
+ final TimeZone tzCustom = new TimeZone((VTimeZone)cal.getComponent(VTimeZone.VTIMEZONE));
+ dtStart = new DtStart("20150101T000000", tzCustom);
+ iCalendar.validateTimeZone(dtStart);
+
+ final TimeZone tzDefault = DateUtils.tzRegistry.getTimeZone(java.util.TimeZone.getDefault().getID());
+ assertNotNull(tzDefault);
+ assertEquals(tzDefault.getID(), dtStart.getTimeZone().getID());
+ }
+
+}
diff --git a/app/src/androidTest/java/at/bitfire/davdroid/syncadapter/DavResourceFinderTest.java b/app/src/androidTest/java/at/bitfire/davdroid/syncadapter/DavResourceFinderTest.java
index e6e64dbc..72b42a75 100644
--- a/app/src/androidTest/java/at/bitfire/davdroid/syncadapter/DavResourceFinderTest.java
+++ b/app/src/androidTest/java/at/bitfire/davdroid/syncadapter/DavResourceFinderTest.java
@@ -18,7 +18,6 @@ import at.bitfire.davdroid.TestConstants;
import at.bitfire.davdroid.resource.DavResourceFinder;
import at.bitfire.davdroid.resource.ServerInfo;
import at.bitfire.davdroid.resource.ServerInfo.ResourceInfo;
-import ezvcard.VCardVersion;
public class DavResourceFinderTest extends InstrumentationTestCase {
diff --git a/app/src/androidTest/java/at/bitfire/davdroid/webdav/WebDavResourceTest.java b/app/src/androidTest/java/at/bitfire/davdroid/webdav/WebDavResourceTest.java
index b4288ad3..7e033a96 100644
--- a/app/src/androidTest/java/at/bitfire/davdroid/webdav/WebDavResourceTest.java
+++ b/app/src/androidTest/java/at/bitfire/davdroid/webdav/WebDavResourceTest.java
@@ -73,7 +73,7 @@ public class WebDavResourceTest extends InstrumentationTestCase {
public void testPropfindCurrentUserPrincipal() throws Exception {
davCollection.propfind(HttpPropfind.Mode.CURRENT_USER_PRINCIPAL);
- assertEquals(new URI("/dav/principals/users/test"), davCollection.getCurrentUserPrincipal());
+ assertEquals(new URI("/dav/principals/users/test"), davCollection.getProperties().getCurrentUserPrincipal());
WebDavResource simpleFile = new WebDavResource(davAssets, "test.random");
try {
@@ -82,14 +82,14 @@ public class WebDavResourceTest extends InstrumentationTestCase {
} catch(DavException ex) {
}
- assertNull(simpleFile.getCurrentUserPrincipal());
+ assertNull(simpleFile.getProperties().getCurrentUserPrincipal());
}
public void testPropfindHomeSets() throws Exception {
WebDavResource dav = new WebDavResource(davCollection, "principals/users/test");
dav.propfind(HttpPropfind.Mode.HOME_SETS);
- assertEquals(new URI("/dav/addressbooks/test/"), dav.getAddressbookHomeSet());
- assertEquals(new URI("/dav/calendars/test/"), dav.getCalendarHomeSet());
+ assertEquals(new URI("/dav/addressbooks/test/"), dav.getProperties().getAddressbookHomeSet());
+ assertEquals(new URI("/dav/calendars/test/"), dav.getProperties().getCalendarHomeSet());
}
public void testPropfindAddressBooks() throws Exception {
@@ -103,34 +103,45 @@ public class WebDavResourceTest extends InstrumentationTestCase {
WebDavResource ab = dav.getMembers().get(0);
assertEquals(TestConstants.roboHydra.resolve("/dav/addressbooks/test/useless-member"), ab.getLocation());
assertEquals("useless-member", ab.getName());
- assertFalse(ab.isAddressBook());
+ assertFalse(ab.getProperties().isAddressBook());
// the second one is an address book (referenced by relative URI)
ab = dav.getMembers().get(1);
assertEquals(TestConstants.roboHydra.resolve("/dav/addressbooks/test/default.vcf/"), ab.getLocation());
assertEquals("default.vcf", ab.getName());
- assertTrue(ab.isAddressBook());
+ assertTrue(ab.getProperties().isAddressBook());
// the third one is an address book (referenced by an absolute URI)
ab = dav.getMembers().get(2);
assertEquals(new URI("https://my.server/absolute:uri/my-address-book/"), ab.getLocation());
assertEquals("my-address-book", ab.getName());
- assertTrue(ab.isAddressBook());
+ assertTrue(ab.getProperties().isAddressBook());
}
public void testPropfindCalendars() throws Exception {
WebDavResource dav = new WebDavResource(davCollection, "calendars/test");
dav.propfind(Mode.CALDAV_COLLECTIONS);
assertEquals(3, dav.getMembers().size());
- assertEquals("0xFF00FF", dav.getMembers().get(2).getColor());
+ assertEquals(new Integer(0xFFFF00FF), dav.getMembers().get(2).getProperties().getColor());
for (WebDavResource member : dav.getMembers()) {
if (member.getName().contains(".ics"))
- assertTrue(member.isCalendar());
+ assertTrue(member.getProperties().isCalendar());
else
- assertFalse(member.isCalendar());
- assertFalse(member.isAddressBook());
+ assertFalse(member.getProperties().isCalendar());
+ assertFalse(member.getProperties().isAddressBook());
}
}
+
+ public void testPropfindCollectionProperties() throws Exception {
+ WebDavResource dav = new WebDavResource(davCollection, "propfind-collection-properties");
+ dav.propfind(Mode.COLLECTION_PROPERTIES);
+ assertTrue(dav.members.isEmpty());
+ assertTrue(dav.properties.isCollection);
+ assertTrue(dav.properties.isAddressBook);
+ assertNull(dav.properties.displayName);
+ assertNull(dav.properties.color);
+ assertEquals(VCardVersion.V4_0, dav.properties.supportedVCardVersion);
+ }
public void testPropfindTrailingSlashes() throws Exception {
final String principalOK = "/principals/ok";
@@ -145,7 +156,7 @@ public class WebDavResourceTest extends InstrumentationTestCase {
for (String path : requestPaths) {
WebDavResource davSlash = new WebDavResource(davCollection, new URI(path));
davSlash.propfind(Mode.CARDDAV_COLLECTIONS);
- assertEquals(new URI(principalOK), davSlash.getCurrentUserPrincipal());
+ assertEquals(new URI(principalOK), davSlash.getProperties().getCurrentUserPrincipal());
}
}
@@ -190,7 +201,7 @@ public class WebDavResourceTest extends InstrumentationTestCase {
WebDavResource davAddressBook = new WebDavResource(davCollection, "addressbooks/default.vcf/");
davAddressBook.multiGet(DavMultiget.Type.ADDRESS_BOOK, new String[] { "1.vcf", "2:3@my%40pc.vcf" });
// queried address book has a name
- assertEquals("My Book", davAddressBook.getDisplayName());
+ assertEquals("My Book", davAddressBook.getProperties().getDisplayName());
// there are two contacts
assertEquals(2, davAddressBook.getMembers().size());
// contact file names should be unescaped (yes, it's really named ...%40pc... to check double-encoding)
diff --git a/app/src/androidTest/robohydra/plugins/dav/index.js b/app/src/androidTest/robohydra/plugins/dav/index.js
index b89b0176..f27756c6 100644
--- a/app/src/androidTest/robohydra/plugins/dav/index.js
+++ b/app/src/androidTest/robohydra/plugins/dav/index.js
@@ -81,6 +81,40 @@ exports.getBodyParts = function(conf) {
}
}
}),
+ new RoboHydraHeadDAV({
+ path: "/dav/propfind-collection-properties",
+ handler: function(req,res,next) {
+ if (req.method == "PROPFIND") {
+ res.statusCode = 207;
+ res.write('\\
+ \
+ \
+ /dav/propfind-collection-properties \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ HTTP/1.1 200 OK\
+ \
+ \
+ \
+ \
+ 0xFF00FF\
+ \
+ HTTP/1.1 404 Not Found\
+ \
+ \
+ \
+ ');
+ }
+ }
+ }),
/* principal URL */
new RoboHydraHeadDAV({
diff --git a/app/src/main/java/at/bitfire/davdroid/DAVUtils.java b/app/src/main/java/at/bitfire/davdroid/DAVUtils.java
index 70b6cc16..96884123 100644
--- a/app/src/main/java/at/bitfire/davdroid/DAVUtils.java
+++ b/app/src/main/java/at/bitfire/davdroid/DAVUtils.java
@@ -16,8 +16,11 @@ import java.util.regex.Pattern;
public class DAVUtils {
private static final String TAG = "davdroid.DAVutils";
+ public static final int calendarGreen = 0xFFC3EA6E;
+
+
public static int CalDAVtoARGBColor(String davColor) {
- int color = 0xFFC3EA6E; // fallback: "DAVdroid green"
+ int color = calendarGreen; // fallback: "DAVdroid green"
if (davColor != null) {
Pattern p = Pattern.compile("#?(\\p{XDigit}{6})(\\p{XDigit}{2})?");
Matcher m = p.matcher(davColor);
diff --git a/app/src/main/java/at/bitfire/davdroid/DateUtils.java b/app/src/main/java/at/bitfire/davdroid/DateUtils.java
index e102f464..941ae207 100644
--- a/app/src/main/java/at/bitfire/davdroid/DateUtils.java
+++ b/app/src/main/java/at/bitfire/davdroid/DateUtils.java
@@ -8,33 +8,25 @@
package at.bitfire.davdroid;
-import android.text.format.Time;
import android.util.Log;
import net.fortuna.ical4j.model.Date;
import net.fortuna.ical4j.model.DateList;
import net.fortuna.ical4j.model.DateTime;
-import net.fortuna.ical4j.model.DefaultTimeZoneRegistryFactory;
import net.fortuna.ical4j.model.TimeZone;
import net.fortuna.ical4j.model.TimeZoneRegistry;
import net.fortuna.ical4j.model.TimeZoneRegistryFactory;
import net.fortuna.ical4j.model.parameter.Value;
import net.fortuna.ical4j.model.property.DateListProperty;
-import net.fortuna.ical4j.model.property.ExDate;
-import net.fortuna.ical4j.model.property.RDate;
-import net.fortuna.ical4j.util.TimeZones;
import org.apache.commons.lang3.StringUtils;
-import java.lang.reflect.InvocationTargetException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
-import java.util.GregorianCalendar;
import java.util.LinkedList;
import java.util.List;
import java.util.SimpleTimeZone;
-import java.util.StringTokenizer;
public class DateUtils {
private final static String TAG = "davdroid.DateUtils";
@@ -49,35 +41,35 @@ public class DateUtils {
// time zones
- public static String findAndroidTimezoneID(String tzID) {
- String localTZ = null;
+ public static String findAndroidTimezoneID(String tz) {
+ String deviceTZ = null;
String availableTZs[] = SimpleTimeZone.getAvailableIDs();
// first, try to find an exact match (case insensitive)
for (String availableTZ : availableTZs)
- if (availableTZ.equalsIgnoreCase(tzID)) {
- localTZ = availableTZ;
+ if (availableTZ.equalsIgnoreCase(tz)) {
+ deviceTZ = availableTZ;
break;
}
// if that doesn't work, try to find something else that matches
- if (localTZ == null) {
+ if (deviceTZ == null) {
Log.w(TAG, "Coulnd't find time zone with matching identifiers, trying to guess");
for (String availableTZ : availableTZs)
- if (StringUtils.indexOfIgnoreCase(tzID, availableTZ) != -1) {
- localTZ = availableTZ;
+ if (StringUtils.indexOfIgnoreCase(tz, availableTZ) != -1) {
+ deviceTZ = availableTZ;
break;
}
}
// if that doesn't work, use UTC as fallback
- if (localTZ == null) {
- Log.e(TAG, "Couldn't identify time zone, using UTC as fallback");
- localTZ = Time.TIMEZONE_UTC;
+ if (deviceTZ == null) {
+ final String defaultTZ = TimeZone.getDefault().getID();
+ Log.e(TAG, "Couldn't identify time zone, using system default (" + defaultTZ + ") as fallback");
+ deviceTZ = defaultTZ;
}
- Log.d(TAG, "Assuming time zone " + localTZ + " for " + tzID);
- return localTZ;
+ return deviceTZ;
}
diff --git a/app/src/main/java/at/bitfire/davdroid/resource/DavResourceFinder.java b/app/src/main/java/at/bitfire/davdroid/resource/DavResourceFinder.java
index f46a2e48..c966a352 100644
--- a/app/src/main/java/at/bitfire/davdroid/resource/DavResourceFinder.java
+++ b/app/src/main/java/at/bitfire/davdroid/resource/DavResourceFinder.java
@@ -33,7 +33,6 @@ import at.bitfire.davdroid.webdav.DavIncapableException;
import at.bitfire.davdroid.webdav.HttpPropfind.Mode;
import at.bitfire.davdroid.webdav.NotAuthorizedException;
import at.bitfire.davdroid.webdav.WebDavResource;
-import ezvcard.VCardVersion;
public class DavResourceFinder implements Closeable {
private final static String TAG = "davdroid.ResourceFinder";
@@ -62,7 +61,7 @@ public class DavResourceFinder implements Closeable {
URI uriAddressBookHomeSet = null;
try {
principal.propfind(Mode.HOME_SETS);
- uriAddressBookHomeSet = principal.getAddressbookHomeSet();
+ uriAddressBookHomeSet = principal.getProperties().getAddressbookHomeSet();
} catch (Exception e) {
Log.i(TAG, "Couldn't find address-book home set", e);
}
@@ -80,19 +79,21 @@ public class DavResourceFinder implements Closeable {
possibleAddressBooks.addAll(homeSetAddressBooks.getMembers());
List addressBooks = new LinkedList<>();
- for (WebDavResource resource : possibleAddressBooks)
- if (resource.isAddressBook()) {
+ for (WebDavResource resource : possibleAddressBooks) {
+ final WebDavResource.Properties properties = resource.getProperties();
+ if (properties.isAddressBook()) {
Log.i(TAG, "Found address book: " + resource.getLocation().getPath());
ServerInfo.ResourceInfo info = new ServerInfo.ResourceInfo(
- ServerInfo.ResourceInfo.Type.ADDRESS_BOOK,
- resource.isReadOnly(),
- resource.getLocation().toString(),
- resource.getDisplayName(),
- resource.getDescription(), resource.getColor()
+ ServerInfo.ResourceInfo.Type.ADDRESS_BOOK,
+ properties.isReadOnly(),
+ resource.getLocation().toString(),
+ properties.getDisplayName(),
+ properties.getDescription(), properties.getColor()
);
addressBooks.add(info);
}
+ }
serverInfo.setAddressBooks(addressBooks);
} else
Log.w(TAG, "Found address-book home set, but it doesn't advertise CardDAV support");
@@ -104,7 +105,7 @@ public class DavResourceFinder implements Closeable {
URI uriCalendarHomeSet = null;
try {
principal.propfind(Mode.HOME_SETS);
- uriCalendarHomeSet = principal.getCalendarHomeSet();
+ uriCalendarHomeSet = principal.getProperties().getCalendarHomeSet();
} catch(Exception e) {
Log.i(TAG, "Couldn't find calendar home set", e);
}
@@ -124,27 +125,28 @@ public class DavResourceFinder implements Closeable {
List
calendars = new LinkedList<>(),
todoLists = new LinkedList<>();
- for (WebDavResource resource : possibleCalendars)
- if (resource.isCalendar()) {
+ for (WebDavResource resource : possibleCalendars) {
+ final WebDavResource.Properties properties = resource.getProperties();
+ if (properties.isCalendar()) {
Log.i(TAG, "Found calendar: " + resource.getLocation().getPath());
ServerInfo.ResourceInfo info = new ServerInfo.ResourceInfo(
ServerInfo.ResourceInfo.Type.CALENDAR,
- resource.isReadOnly(),
+ properties.isReadOnly(),
resource.getLocation().toString(),
- resource.getDisplayName(),
- resource.getDescription(), resource.getColor()
+ properties.getDisplayName(),
+ properties.getDescription(), properties.getColor()
);
- info.setTimezone(resource.getTimezone());
+ info.setTimezone(properties.getTimeZone());
boolean isCalendar = false,
isTodoList = false;
- if (resource.getSupportedComponents() == null) {
+ if (properties.getSupportedComponents() == null) {
// no info about supported components, assuming all components are supported
isCalendar = true;
isTodoList = true;
} else {
// CALDAV:supported-calendar-component-set available
- for (String supportedComponent : resource.getSupportedComponents())
+ for (String supportedComponent : properties.getSupportedComponents())
if ("VEVENT".equalsIgnoreCase(supportedComponent))
isCalendar = true;
else if ("VTODO".equalsIgnoreCase(supportedComponent))
@@ -162,6 +164,7 @@ public class DavResourceFinder implements Closeable {
if (isTodoList)
todoLists.add(new ServerInfo.ResourceInfo(info));
}
+ }
serverInfo.setCalendars(calendars);
serverInfo.setTodoLists(todoLists);
@@ -271,8 +274,8 @@ public class DavResourceFinder implements Closeable {
try {
WebDavResource wellKnown = new WebDavResource(base, "/.well-known/" + serviceName);
wellKnown.propfind(Mode.CURRENT_USER_PRINCIPAL);
- if (wellKnown.getCurrentUserPrincipal() != null) {
- URI principal = wellKnown.getCurrentUserPrincipal();
+ if (wellKnown.getProperties().getCurrentUserPrincipal() != null) {
+ URI principal = wellKnown.getProperties().getCurrentUserPrincipal();
Log.i(TAG, "Principal URL found from Well-Known URI: " + principal);
return new WebDavResource(wellKnown, principal);
}
@@ -293,8 +296,8 @@ public class DavResourceFinder implements Closeable {
Log.d(TAG, "Well-known service detection failed, trying initial context path " + initialURL);
try {
base.propfind(Mode.CURRENT_USER_PRINCIPAL);
- if (base.getCurrentUserPrincipal() != null) {
- URI principal = base.getCurrentUserPrincipal();
+ if (base.getProperties().getCurrentUserPrincipal() != null) {
+ URI principal = base.getProperties().getCurrentUserPrincipal();
Log.i(TAG, "Principal URL found from initial context path: " + principal);
return new WebDavResource(base, principal);
}
diff --git a/app/src/main/java/at/bitfire/davdroid/resource/Event.java b/app/src/main/java/at/bitfire/davdroid/resource/Event.java
index 0fe5fa9a..c243cd0b 100644
--- a/app/src/main/java/at/bitfire/davdroid/resource/Event.java
+++ b/app/src/main/java/at/bitfire/davdroid/resource/Event.java
@@ -17,15 +17,11 @@ import net.fortuna.ical4j.model.Component;
import net.fortuna.ical4j.model.ComponentList;
import net.fortuna.ical4j.model.Date;
import net.fortuna.ical4j.model.DateTime;
-import net.fortuna.ical4j.model.DefaultTimeZoneRegistryFactory;
import net.fortuna.ical4j.model.Property;
import net.fortuna.ical4j.model.PropertyList;
-import net.fortuna.ical4j.model.TimeZoneRegistry;
import net.fortuna.ical4j.model.ValidationException;
import net.fortuna.ical4j.model.component.VAlarm;
import net.fortuna.ical4j.model.component.VEvent;
-import net.fortuna.ical4j.model.component.VTimeZone;
-import net.fortuna.ical4j.model.parameter.Value;
import net.fortuna.ical4j.model.property.Attendee;
import net.fortuna.ical4j.model.property.Clazz;
import net.fortuna.ical4j.model.property.DateProperty;
@@ -46,14 +42,11 @@ import net.fortuna.ical4j.model.property.Summary;
import net.fortuna.ical4j.model.property.Transp;
import net.fortuna.ical4j.model.property.Uid;
import net.fortuna.ical4j.model.property.Version;
-import net.fortuna.ical4j.util.CompatibilityHints;
-import net.fortuna.ical4j.util.SimpleHostInfo;
-import net.fortuna.ical4j.util.UidGenerator;
+import net.fortuna.ical4j.util.TimeZones;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.StringReader;
import java.util.Calendar;
import java.util.HashSet;
import java.util.LinkedList;
@@ -63,7 +56,6 @@ import java.util.TimeZone;
import at.bitfire.davdroid.Constants;
import at.bitfire.davdroid.DateUtils;
-import at.bitfire.davdroid.syncadapter.DavSyncAdapter;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
@@ -156,15 +148,13 @@ public class Event extends iCalendar {
if ((dtStart = event.getStartDate()) == null || (dtEnd = event.getEndDate()) == null)
throw new InvalidResourceException("Invalid start time/end time/duration");
- if (hasTime(dtStart)) {
- validateTimeZone(dtStart);
- validateTimeZone(dtEnd);
- }
+ validateTimeZone(dtStart);
+ validateTimeZone(dtEnd);
// all-day events and "events on that day":
// * related UNIX times must be in UTC
// * must have a duration (set to one day if missing)
- if (!hasTime(dtStart) && !dtEnd.getDate().after(dtStart.getDate())) {
+ if (!isDateTime(dtStart) && !dtEnd.getDate().after(dtStart.getDate())) {
Log.i(TAG, "Repairing iCal: DTEND := DTSTART+1");
Calendar c = Calendar.getInstance(TimeZone.getTimeZone(Time.TIMEZONE_UTC));
c.setTime(dtStart.getDate());
@@ -304,8 +294,20 @@ public class Event extends iCalendar {
// time helpers
+ /**
+ * Returns the time-zone ID for a given date-time, or TIMEZONE_UTC for dates (without time).
+ * TIMEZONE_UTC is also returned for DATE-TIMEs in UTC representation.
+ * @param date DateProperty (DATE or DATE-TIME) whose time-zone information is used
+ */
+ protected static String getTzId(DateProperty date) {
+ if (isDateTime(date) && !date.isUtc() && date.getTimeZone() != null)
+ return date.getTimeZone().getID();
+ else
+ return TimeZones.UTC_ID;
+ }
+
public boolean isAllDay() {
- return !hasTime(dtStart);
+ return !isDateTime(dtStart);
}
public long getDtStartInMillis() {
diff --git a/app/src/main/java/at/bitfire/davdroid/resource/LocalAddressBook.java b/app/src/main/java/at/bitfire/davdroid/resource/LocalAddressBook.java
index c51abe75..d7d8bb62 100644
--- a/app/src/main/java/at/bitfire/davdroid/resource/LocalAddressBook.java
+++ b/app/src/main/java/at/bitfire/davdroid/resource/LocalAddressBook.java
@@ -17,7 +17,6 @@ import android.content.Entity;
import android.content.EntityIterator;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
-import android.database.DatabaseUtils;
import android.net.Uri;
import android.os.RemoteException;
import android.provider.ContactsContract;
@@ -48,7 +47,6 @@ import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
-import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
@@ -127,9 +125,9 @@ public class LocalAddressBook extends LocalCollection {
}
@Override
- public void updateMetaData(WebDavResource resource)
+ public void updateMetaData(WebDavResource.Properties properties)
{
- final VCardVersion vCardVersion = resource.getVCardVersion();
+ final VCardVersion vCardVersion = properties.getSupportedVCardVersion();
accountSettings.setAddressBookVCardVersion(vCardVersion != null ? vCardVersion : VCardVersion.V3_0);
}
diff --git a/app/src/main/java/at/bitfire/davdroid/resource/LocalCalendar.java b/app/src/main/java/at/bitfire/davdroid/resource/LocalCalendar.java
index e64ffa39..c4e16b63 100644
--- a/app/src/main/java/at/bitfire/davdroid/resource/LocalCalendar.java
+++ b/app/src/main/java/at/bitfire/davdroid/resource/LocalCalendar.java
@@ -22,7 +22,6 @@ import android.database.Cursor;
import android.database.DatabaseUtils;
import android.net.Uri;
import android.os.Build;
-import android.os.Bundle;
import android.os.RemoteException;
import android.provider.CalendarContract;
import android.provider.CalendarContract.Attendees;
@@ -42,10 +41,8 @@ import net.fortuna.ical4j.model.parameter.Cn;
import net.fortuna.ical4j.model.parameter.CuType;
import net.fortuna.ical4j.model.parameter.PartStat;
import net.fortuna.ical4j.model.parameter.Role;
-import net.fortuna.ical4j.model.parameter.Value;
import net.fortuna.ical4j.model.property.Action;
import net.fortuna.ical4j.model.property.Attendee;
-import net.fortuna.ical4j.model.property.DateListProperty;
import net.fortuna.ical4j.model.property.Description;
import net.fortuna.ical4j.model.property.Duration;
import net.fortuna.ical4j.model.property.ExDate;
@@ -61,10 +58,8 @@ import org.apache.commons.lang3.StringUtils;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.ParseException;
-import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
-import java.util.StringTokenizer;
import at.bitfire.davdroid.DAVUtils;
import at.bitfire.davdroid.DateUtils;
@@ -119,7 +114,7 @@ public class LocalCalendar extends LocalCollection {
values.put(Calendars.ACCOUNT_TYPE, account.type);
values.put(Calendars.NAME, info.getURL());
values.put(Calendars.CALENDAR_DISPLAY_NAME, info.getTitle());
- values.put(Calendars.CALENDAR_COLOR, DAVUtils.CalDAVtoARGBColor(info.getColor()));
+ values.put(Calendars.CALENDAR_COLOR, info.getColor() != null ? info.getColor() : DAVUtils.calendarGreen);
values.put(Calendars.OWNER_ACCOUNT, account.name);
values.put(Calendars.SYNC_EVENTS, 1);
values.put(Calendars.VISIBLE, 1);
@@ -139,7 +134,7 @@ public class LocalCalendar extends LocalCollection {
}
if (info.getTimezone() != null)
- values.put(Calendars.CALENDAR_TIME_ZONE, info.getTimezone());
+ values.put(Calendars.CALENDAR_TIME_ZONE, DateUtils.findAndroidTimezoneID(info.getTimezone()));
Log.i(TAG, "Inserting calendar: " + values.toString());
try {
@@ -196,16 +191,16 @@ public class LocalCalendar extends LocalCollection {
}
@Override
- public void updateMetaData(WebDavResource resource) throws LocalStorageException {
+ public void updateMetaData(WebDavResource.Properties properties) throws LocalStorageException {
ContentValues values = new ContentValues();
- final String displayName = resource.getDisplayName();
+ final String displayName = properties.getDisplayName();
if (displayName != null)
values.put(Calendars.CALENDAR_DISPLAY_NAME, displayName);
- final String color = resource.getColor();
+ final Integer color = properties.getColor();
if (color != null)
- values.put(Calendars.CALENDAR_COLOR, DAVUtils.CalDAVtoARGBColor(color));
+ values.put(Calendars.CALENDAR_COLOR, color);
try {
if (values.size() > 0)
diff --git a/app/src/main/java/at/bitfire/davdroid/resource/LocalCollection.java b/app/src/main/java/at/bitfire/davdroid/resource/LocalCollection.java
index 0583010b..af70cb23 100644
--- a/app/src/main/java/at/bitfire/davdroid/resource/LocalCollection.java
+++ b/app/src/main/java/at/bitfire/davdroid/resource/LocalCollection.java
@@ -18,7 +18,6 @@ import android.content.OperationApplicationException;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.net.Uri;
-import android.os.Bundle;
import android.os.RemoteException;
import android.provider.CalendarContract;
import android.util.Log;
@@ -26,7 +25,6 @@ import android.util.Log;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@@ -96,7 +94,7 @@ public abstract class LocalCollection {
/** gets the CTag of the collection */
abstract public String getCTag() throws LocalStorageException;
/** update locally stored collection properties (e.g. display name and color) from a WebDavResource */
- abstract public void updateMetaData(WebDavResource resource) throws LocalStorageException;
+ abstract public void updateMetaData(WebDavResource.Properties properties) throws LocalStorageException;
// content provider (= database) querying
diff --git a/app/src/main/java/at/bitfire/davdroid/resource/LocalTaskList.java b/app/src/main/java/at/bitfire/davdroid/resource/LocalTaskList.java
index 6c2250a1..3b6d30cf 100644
--- a/app/src/main/java/at/bitfire/davdroid/resource/LocalTaskList.java
+++ b/app/src/main/java/at/bitfire/davdroid/resource/LocalTaskList.java
@@ -18,7 +18,6 @@ import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
-import android.provider.CalendarContract;
import android.util.Log;
import net.fortuna.ical4j.model.Date;
@@ -36,7 +35,6 @@ import net.fortuna.ical4j.util.TimeZones;
import org.apache.commons.lang3.StringUtils;
import org.dmfs.provider.tasks.TaskContract;
-import java.util.HashMap;
import java.util.LinkedList;
import at.bitfire.davdroid.DAVUtils;
@@ -77,7 +75,7 @@ public class LocalTaskList extends LocalCollection {
values.put(TaskContract.TaskLists.ACCOUNT_TYPE, account.type);
values.put(TaskContract.TaskLists._SYNC_ID, info.getURL());
values.put(TaskContract.TaskLists.LIST_NAME, info.getTitle());
- values.put(TaskContract.TaskLists.LIST_COLOR, DAVUtils.CalDAVtoARGBColor(info.getColor()));
+ values.put(TaskContract.TaskLists.LIST_COLOR, info.getColor() != null ? info.getColor() : DAVUtils.calendarGreen);
values.put(TaskContract.TaskLists.OWNER, account.name);
values.put(TaskContract.TaskLists.ACCESS_LEVEL, 0);
values.put(TaskContract.TaskLists.SYNC_ENABLED, 1);
@@ -135,16 +133,16 @@ public class LocalTaskList extends LocalCollection {
}
@Override
- public void updateMetaData(WebDavResource resource) throws LocalStorageException {
+ public void updateMetaData(WebDavResource.Properties properties) throws LocalStorageException {
ContentValues values = new ContentValues();
- final String displayName = resource.getDisplayName();
+ final String displayName = properties.getDisplayName();
if (displayName != null)
values.put(TaskContract.TaskLists.LIST_NAME, displayName);
- final String color = resource.getColor();
+ final Integer color = properties.getColor();
if (color != null)
- values.put(TaskContract.TaskLists.LIST_COLOR, DAVUtils.CalDAVtoARGBColor(color));
+ values.put(TaskContract.TaskLists.LIST_COLOR, color);
try {
if (values.size() > 0)
diff --git a/app/src/main/java/at/bitfire/davdroid/resource/ServerInfo.java b/app/src/main/java/at/bitfire/davdroid/resource/ServerInfo.java
index a0f11e2c..7ad0547e 100644
--- a/app/src/main/java/at/bitfire/davdroid/resource/ServerInfo.java
+++ b/app/src/main/java/at/bitfire/davdroid/resource/ServerInfo.java
@@ -13,7 +13,6 @@ import java.net.URI;
import java.util.LinkedList;
import java.util.List;
-import ezvcard.VCardVersion;
import lombok.Data;
import lombok.RequiredArgsConstructor;
@@ -56,8 +55,8 @@ public class ServerInfo implements Serializable {
final String URL, // absolute URL of resource
title,
- description,
- color;
+ description;
+ final Integer color;
String timezone;
diff --git a/app/src/main/java/at/bitfire/davdroid/resource/Task.java b/app/src/main/java/at/bitfire/davdroid/resource/Task.java
index 044a4bd9..2c587c19 100644
--- a/app/src/main/java/at/bitfire/davdroid/resource/Task.java
+++ b/app/src/main/java/at/bitfire/davdroid/resource/Task.java
@@ -35,8 +35,6 @@ import net.fortuna.ical4j.model.property.Summary;
import net.fortuna.ical4j.model.property.Uid;
import net.fortuna.ical4j.model.property.Url;
import net.fortuna.ical4j.model.property.Version;
-import net.fortuna.ical4j.util.SimpleHostInfo;
-import net.fortuna.ical4j.util.UidGenerator;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -45,7 +43,6 @@ import java.net.URI;
import java.net.URISyntaxException;
import at.bitfire.davdroid.Constants;
-import at.bitfire.davdroid.syncadapter.DavSyncAdapter;
import lombok.Getter;
import lombok.Setter;
diff --git a/app/src/main/java/at/bitfire/davdroid/resource/WebDavCollection.java b/app/src/main/java/at/bitfire/davdroid/resource/WebDavCollection.java
index f22298f5..d3a9c650 100644
--- a/app/src/main/java/at/bitfire/davdroid/resource/WebDavCollection.java
+++ b/app/src/main/java/at/bitfire/davdroid/resource/WebDavCollection.java
@@ -88,7 +88,7 @@ public abstract class WebDavCollection {
List resources = new LinkedList<>();
if (collection.getMembers() != null)
for (WebDavResource member : collection.getMembers())
- resources.add(newResourceSkeleton(member.getName(), member.getETag()));
+ resources.add(newResourceSkeleton(member.getName(), member.getProperties().getETag()));
return resources.toArray(new Resource[resources.size()]);
@@ -113,7 +113,7 @@ public abstract class WebDavCollection {
throw new DavNoContentException();
for (WebDavResource member : collection.getMembers()) {
- T resource = newResourceSkeleton(member.getName(), member.getETag());
+ T resource = newResourceSkeleton(member.getName(), member.getProperties().getETag());
try {
if (member.getContent() != null) {
@Cleanup InputStream is = new ByteArrayInputStream(member.getContent());
@@ -158,13 +158,13 @@ public abstract class WebDavCollection {
// returns ETag of the created resource, if returned by server
public String add(Resource res) throws URISyntaxException, IOException, HttpException {
WebDavResource member = new WebDavResource(collection, res.getName(), res.getETag());
- member.setContentType(res.getMimeType());
+ member.getProperties().setContentType(res.getMimeType());
@Cleanup ByteArrayOutputStream os = res.toEntity();
String eTag = member.put(os.toByteArray(), PutMode.ADD_DONT_OVERWRITE);
// after a successful upload, the collection has implicitely changed, too
- collection.invalidateCTag();
+ collection.getProperties().invalidateCTag();
return eTag;
}
@@ -173,19 +173,19 @@ public abstract class WebDavCollection {
WebDavResource member = new WebDavResource(collection, res.getName(), res.getETag());
member.delete();
- collection.invalidateCTag();
+ collection.getProperties().invalidateCTag();
}
// returns ETag of the updated resource, if returned by server
public String update(Resource res) throws URISyntaxException, IOException, HttpException {
WebDavResource member = new WebDavResource(collection, res.getName(), res.getETag());
- member.setContentType(res.getMimeType());
+ member.getProperties().setContentType(res.getMimeType());
@Cleanup ByteArrayOutputStream os = res.toEntity();
String eTag = member.put(os.toByteArray(), PutMode.UPDATE_DONT_OVERWRITE);
// after a successful upload, the collection has implicitely changed, too
- collection.invalidateCTag();
+ collection.getProperties().invalidateCTag();
return eTag;
}
diff --git a/app/src/main/java/at/bitfire/davdroid/resource/iCalendar.java b/app/src/main/java/at/bitfire/davdroid/resource/iCalendar.java
index 262babb1..1176a5fb 100644
--- a/app/src/main/java/at/bitfire/davdroid/resource/iCalendar.java
+++ b/app/src/main/java/at/bitfire/davdroid/resource/iCalendar.java
@@ -8,29 +8,24 @@
package at.bitfire.davdroid.resource;
-import android.text.format.Time;
import android.util.Log;
import net.fortuna.ical4j.data.CalendarBuilder;
+import net.fortuna.ical4j.data.ParserException;
import net.fortuna.ical4j.model.DateTime;
-import net.fortuna.ical4j.model.DefaultTimeZoneRegistryFactory;
-import net.fortuna.ical4j.model.TimeZoneRegistry;
import net.fortuna.ical4j.model.component.VTimeZone;
-import net.fortuna.ical4j.model.parameter.Value;
import net.fortuna.ical4j.model.property.DateProperty;
-import net.fortuna.ical4j.model.property.DtStart;
import net.fortuna.ical4j.util.CompatibilityHints;
import net.fortuna.ical4j.util.SimpleHostInfo;
import net.fortuna.ical4j.util.UidGenerator;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
-import java.io.InputStream;
import java.io.StringReader;
+import java.util.TimeZone;
import at.bitfire.davdroid.DateUtils;
import at.bitfire.davdroid.syncadapter.DavSyncAdapter;
-import lombok.Getter;
+import lombok.NonNull;
public abstract class iCalendar extends Resource {
static private final String TAG = "DAVdroid.iCal";
@@ -72,47 +67,46 @@ public abstract class iCalendar extends Resource {
// time zone helpers
- protected static boolean hasTime(DateProperty date) {
+ protected static boolean isDateTime(DateProperty date) {
return date.getDate() instanceof DateTime;
}
- protected static String getTzId(DateProperty date) {
- if (date.isUtc() || !hasTime(date))
- return Time.TIMEZONE_UTC;
- else if (date.getTimeZone() != null)
- return date.getTimeZone().getID();
- else if (date.getParameter(Value.TZID) != null)
- return date.getParameter(Value.TZID).getValue();
-
- // fallback
- return Time.TIMEZONE_UTC;
- }
-
- /* guess matching Android timezone ID */
+ /**
+ * Ensures that a given DateProperty has a time zone with an ID that is available in Android.
+ * @param date DateProperty to validate. Values which are not DATE-TIME will be ignored.
+ */
protected static void validateTimeZone(DateProperty date) {
- if (date.isUtc() || !hasTime(date))
- return;
+ if (isDateTime(date)) {
+ final TimeZone tz = date.getTimeZone();
+ if (tz == null)
+ return;
+ final String tzID = tz.getID();
+ if (tzID == null)
+ return;
- String tzID = getTzId(date);
- if (tzID == null)
- return;
-
- String localTZ = DateUtils.findAndroidTimezoneID(tzID);
- date.setTimeZone(DateUtils.tzRegistry.getTimeZone(localTZ));
+ String deviceTzID = DateUtils.findAndroidTimezoneID(tzID);
+ if (!tzID.equals(deviceTzID))
+ date.setTimeZone(DateUtils.tzRegistry.getTimeZone(deviceTzID));
+ }
}
- public static String TimezoneDefToTzId(String timezoneDef) throws IllegalArgumentException {
+ /**
+ * Takes a string with a timezone definition and returns the time zone ID.
+ * @param timezoneDef time zone definition (VCALENDAR with VTIMEZONE component)
+ * @return time zone id (TZID) if VTIMEZONE contains a TZID,
+ * null otherwise
+ */
+ public static String TimezoneDefToTzId(@NonNull String timezoneDef) {
try {
- if (timezoneDef != null) {
- CalendarBuilder builder = new CalendarBuilder();
- net.fortuna.ical4j.model.Calendar cal = builder.build(new StringReader(timezoneDef));
- VTimeZone timezone = (VTimeZone)cal.getComponent(VTimeZone.VTIMEZONE);
+ CalendarBuilder builder = new CalendarBuilder();
+ net.fortuna.ical4j.model.Calendar cal = builder.build(new StringReader(timezoneDef));
+ VTimeZone timezone = (VTimeZone)cal.getComponent(VTimeZone.VTIMEZONE);
+ if (timezone != null && timezone.getTimeZoneId() != null)
return timezone.getTimeZoneId().getValue();
- }
- } catch (Exception ex) {
- Log.w(TAG, "Can't understand time zone definition, ignoring", ex);
+ } catch (IOException|ParserException e) {
+ Log.e(TAG, "Can't understand time zone definition", e);
}
- throw new IllegalArgumentException();
+ return null;
}
}
diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.java b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.java
index 24720ef7..be0193a4 100644
--- a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.java
+++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.java
@@ -19,8 +19,8 @@ import at.bitfire.davdroid.ArrayUtils;
import at.bitfire.davdroid.resource.LocalCollection;
import at.bitfire.davdroid.resource.LocalStorageException;
import at.bitfire.davdroid.resource.RecordNotFoundException;
-import at.bitfire.davdroid.resource.WebDavCollection;
import at.bitfire.davdroid.resource.Resource;
+import at.bitfire.davdroid.resource.WebDavCollection;
import at.bitfire.davdroid.webdav.ConflictException;
import at.bitfire.davdroid.webdav.DavException;
import at.bitfire.davdroid.webdav.HttpException;
@@ -46,8 +46,8 @@ public class SyncManager {
public void synchronize(boolean manualSync, SyncResult syncResult) throws URISyntaxException, LocalStorageException, IOException, HttpException, DavException {
// PHASE 1: fetch collection properties
remote.getProperties();
- final WebDavResource collectionResource = remote.getCollection();
- local.updateMetaData(collectionResource);
+ final WebDavResource.Properties collectionProperties = remote.getCollection().getProperties();
+ local.updateMetaData(collectionProperties);
// PHASE 2: push local changes to server
int deletedRemotely = pushDeleted(),
@@ -62,7 +62,7 @@ public class SyncManager {
}
if (!syncMembers) {
final String
- currentCTag = collectionResource.getCTag(),
+ currentCTag = collectionProperties.getCTag(),
lastCTag = local.getCTag();
Log.d(TAG, "Last local CTag = " + lastCTag + "; current remote CTag = " + currentCTag);
if (currentCTag == null || !currentCTag.equals(lastCTag))
@@ -101,7 +101,7 @@ public class SyncManager {
// update collection CTag
Log.i(TAG, "Sync complete, fetching new CTag");
- local.setCTag(collectionResource.getCTag());
+ local.setCTag(collectionProperties.getCTag());
}
diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/InstallAppsFragment.java b/app/src/main/java/at/bitfire/davdroid/ui/setup/InstallAppsFragment.java
index d584e9a0..423fc353 100644
--- a/app/src/main/java/at/bitfire/davdroid/ui/setup/InstallAppsFragment.java
+++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/InstallAppsFragment.java
@@ -9,7 +9,6 @@
package at.bitfire.davdroid.ui.setup;
import android.app.Fragment;
-import android.app.FragmentManager;
import android.os.Bundle;
import android.text.Html;
import android.text.method.LinkMovementMethod;
diff --git a/app/src/main/java/at/bitfire/davdroid/webdav/WebDavResource.java b/app/src/main/java/at/bitfire/davdroid/webdav/WebDavResource.java
index bf3b2a17..99240e26 100644
--- a/app/src/main/java/at/bitfire/davdroid/webdav/WebDavResource.java
+++ b/app/src/main/java/at/bitfire/davdroid/webdav/WebDavResource.java
@@ -41,18 +41,19 @@ import java.io.StringWriter;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
+import at.bitfire.davdroid.DAVUtils;
import at.bitfire.davdroid.URIUtils;
-import at.bitfire.davdroid.resource.Event;
+import at.bitfire.davdroid.resource.iCalendar;
import at.bitfire.davdroid.webdav.DavProp.Comp;
import ezvcard.VCardVersion;
import lombok.Cleanup;
import lombok.Getter;
+import lombok.Setter;
import lombok.ToString;
@@ -64,15 +65,6 @@ import lombok.ToString;
public class WebDavResource {
private static final String TAG = "davdroid.WebDavResource";
- public enum Property {
- CURRENT_USER_PRINCIPAL, // resource detection
- ADDRESSBOOK_HOMESET, CALENDAR_HOMESET,
- CONTENT_TYPE, READ_ONLY, // WebDAV (common)
- DISPLAY_NAME, DESCRIPTION, ETAG,
- IS_COLLECTION, CTAG, // collections
- IS_CALENDAR, COLOR, TIMEZONE, // CalDAV
- IS_ADDRESSBOOK, VCARD_VERSION // CardDAV
- }
public enum PutMode {
ADD_DONT_OVERWRITE,
UPDATE_DONT_OVERWRITE
@@ -85,11 +77,8 @@ public class WebDavResource {
protected Set capabilities = new HashSet<>(),
methods = new HashSet<>();
- // DAV properties
- protected HashMap properties = new HashMap<>();
- @Getter protected List supportedComponents;
-
// list of members (only for collections)
+ @Getter Properties properties = new Properties();
@Getter protected List members;
// content (available after GET)
@@ -151,9 +140,9 @@ public class WebDavResource {
location = parent.location.resolve(new URI(null, null, "./" + member, null));
}
- public WebDavResource(WebDavResource parent, String member, String ETag) throws URISyntaxException {
+ public WebDavResource(WebDavResource parent, String member, String eTag) throws URISyntaxException {
this(parent, member);
- properties.put(Property.ETAG, ETag);
+ properties.eTag = eTag;
}
@@ -191,76 +180,6 @@ public class WebDavResource {
}
- /* property methods */
-
- public URI getCurrentUserPrincipal() throws URISyntaxException {
- String principal = properties.get(Property.CURRENT_USER_PRINCIPAL);
- return principal != null ? URIUtils.parseURI(principal, false) : null;
- }
-
- public URI getAddressbookHomeSet() throws URISyntaxException {
- String homeset = properties.get(Property.ADDRESSBOOK_HOMESET);
- return homeset != null ? URIUtils.parseURI(homeset, false) : null;
- }
-
- public URI getCalendarHomeSet() throws URISyntaxException {
- String homeset = properties.get(Property.CALENDAR_HOMESET);
- return homeset != null ? URIUtils.parseURI(homeset, false) : null;
- }
-
- public String getContentType() {
- return properties.get(Property.CONTENT_TYPE);
- }
-
- public void setContentType(String mimeType) {
- properties.put(Property.CONTENT_TYPE, mimeType);
- }
-
- public boolean isReadOnly() {
- return properties.containsKey(Property.READ_ONLY);
- }
-
- public String getDisplayName() {
- return properties.get(Property.DISPLAY_NAME);
- }
-
- public String getDescription() {
- return properties.get(Property.DESCRIPTION);
- }
-
- public String getCTag() {
- return properties.get(Property.CTAG);
- }
- public void invalidateCTag() {
- properties.remove(Property.CTAG);
- }
-
- public String getETag() {
- return properties.get(Property.ETAG);
- }
-
- public boolean isCalendar() {
- return properties.containsKey(Property.IS_CALENDAR);
- }
-
- public String getColor() {
- return properties.get(Property.COLOR);
- }
-
- public String getTimezone() {
- return properties.get(Property.TIMEZONE);
- }
-
- public boolean isAddressBook() {
- return properties.containsKey(Property.IS_ADDRESSBOOK);
- }
-
- public VCardVersion getVCardVersion() {
- String versionStr = properties.get(Property.VCARD_VERSION);
- return (versionStr != null) ? VCardVersion.valueOfByStr(versionStr) : null;
- }
-
-
/* collection operations */
public void propfind(HttpPropfind.Mode mode) throws URISyntaxException, IOException, DavException, HttpException {
@@ -372,12 +291,12 @@ public class WebDavResource {
put.addHeader("If-None-Match", "*");
break;
case UPDATE_DONT_OVERWRITE:
- put.addHeader("If-Match", (getETag() != null) ? getETag() : "*");
+ put.addHeader("If-Match", (properties.eTag != null) ? properties.eTag : "*");
break;
}
- if (getContentType() != null)
- put.addHeader("Content-Type", getContentType());
+ if (properties.contentType != null)
+ put.addHeader("Content-Type", properties.contentType);
@Cleanup CloseableHttpResponse response = httpClient.execute(put, context);
checkResponse(response);
@@ -392,8 +311,8 @@ public class WebDavResource {
public void delete() throws URISyntaxException, IOException, HttpException {
HttpDeleteHC4 delete = new HttpDeleteHC4(location);
- if (getETag() != null)
- delete.addHeader("If-Match", getETag());
+ if (properties.eTag != null)
+ delete.addHeader("If-Match", properties.eTag);
@Cleanup CloseableHttpResponse response = httpClient.execute(delete, context);
checkResponse(response);
@@ -473,8 +392,7 @@ public class WebDavResource {
Log.d(TAG, "Processing multi-status element: " + href);
// process known properties
- HashMap properties = new HashMap<>();
- List supportedComponents = null;
+ Properties properties = new Properties();
byte[] data = null;
// in , either or must be present
@@ -488,84 +406,9 @@ public class WebDavResource {
// ignore information about missing properties etc.
if (status.getStatusCode()/100 != 1 && status.getStatusCode()/100 != 2)
continue;
+
DavProp prop = singlePropstat.prop;
-
- if (prop.currentUserPrincipal != null && prop.currentUserPrincipal.getHref() != null)
- properties.put(Property.CURRENT_USER_PRINCIPAL, prop.currentUserPrincipal.getHref().href);
-
- if (prop.currentUserPrivilegeSet != null) {
- // privilege info available
- boolean mayAll = false,
- mayBind = false,
- mayUnbind = false,
- mayWrite = false,
- mayWriteContent = false;
- for (DavProp.Privilege privilege : prop.currentUserPrivilegeSet) {
- if (privilege.getAll() != null) mayAll = true;
- if (privilege.getBind() != null) mayBind = true;
- if (privilege.getUnbind() != null) mayUnbind = true;
- if (privilege.getWrite() != null) mayWrite = true;
- if (privilege.getWriteContent() != null) mayWriteContent = true;
- }
- if (!mayAll && !mayWrite && !(mayWriteContent && mayBind && mayUnbind))
- properties.put(Property.READ_ONLY, "1");
- }
-
- if (prop.addressbookHomeSet != null && prop.addressbookHomeSet.getHref() != null)
- properties.put(Property.ADDRESSBOOK_HOMESET, URIUtils.ensureTrailingSlash(prop.addressbookHomeSet.getHref().href));
-
- if (prop.calendarHomeSet != null && prop.calendarHomeSet.getHref() != null)
- properties.put(Property.CALENDAR_HOMESET, URIUtils.ensureTrailingSlash(prop.calendarHomeSet.getHref().href));
-
- if (prop.displayname != null)
- properties.put(Property.DISPLAY_NAME, prop.displayname.getDisplayName());
-
- if (prop.resourcetype != null) {
- if (prop.resourcetype.getCollection() != null) {
- properties.put(Property.IS_COLLECTION, "1");
- // is a collection, ensure trailing slash
- href = URIUtils.ensureTrailingSlash(href);
- }
- if (prop.resourcetype.getAddressbook() != null) { // CardDAV collection properties
- properties.put(Property.IS_ADDRESSBOOK, "1");
-
- if (prop.addressbookDescription != null)
- properties.put(Property.DESCRIPTION, prop.addressbookDescription.getDescription());
- if (prop.supportedAddressData != null)
- for (DavProp.AddressDataType dataType : prop.supportedAddressData)
- if ("text/vcard".equalsIgnoreCase(dataType.getContentType()))
- // ignore "3.0" as it MUST be supported anyway
- if ("4.0".equals(dataType.getVersion()))
- properties.put(Property.VCARD_VERSION, VCardVersion.V4_0.getVersion());
- }
- if (prop.resourcetype.getCalendar() != null) { // CalDAV collection propertioes
- properties.put(Property.IS_CALENDAR, "1");
-
- if (prop.calendarDescription != null)
- properties.put(Property.DESCRIPTION, prop.calendarDescription.getDescription());
-
- if (prop.calendarColor != null)
- properties.put(Property.COLOR, prop.calendarColor.getColor());
-
- if (prop.calendarTimezone != null)
- try {
- properties.put(Property.TIMEZONE, Event.TimezoneDefToTzId(prop.calendarTimezone.getTimezone()));
- } catch(IllegalArgumentException e) {
- }
-
- if (prop.supportedCalendarComponentSet != null) {
- supportedComponents = new LinkedList<>();
- for (Comp component : prop.supportedCalendarComponentSet)
- supportedComponents.add(component.getName());
- }
- }
- }
-
- if (prop.getctag != null)
- properties.put(Property.CTAG, prop.getctag.getCTag());
-
- if (prop.getetag != null)
- properties.put(Property.ETAG, prop.getetag.getETag());
+ properties.process(prop);
if (prop.calendarData != null && prop.calendarData.ical != null)
data = prop.calendarData.ical.getBytes();
@@ -574,16 +417,16 @@ public class WebDavResource {
}
// about which resource is this response?
+ if (properties.isCollection) // ensure trailing slashs for collections
+ href = URIUtils.ensureTrailingSlash(href);
+
if (location.equals(href) || URIUtils.ensureTrailingSlash(location).equals(href)) { // about ourselves
- this.properties.putAll(properties);
- if (supportedComponents != null)
- this.supportedComponents = supportedComponents;
+ this.properties = properties;
this.content = data;
} else { // about a member
WebDavResource member = new WebDavResource(this, href);
member.properties = properties;
- member.supportedComponents = supportedComponents;
member.content = data;
members.add(member);
@@ -593,4 +436,133 @@ public class WebDavResource {
this.members = members;
}
+
+ public static class Properties {
+ // DAV properties
+ protected String
+ currentUserPrincipal,
+ addressBookHomeset,
+ calendarHomeset,
+ color;
+
+ @Getter protected String
+ displayName,
+ description,
+ timeZone,
+ eTag,
+ cTag;
+
+ @Getter @Setter protected String contentType;
+
+ @Getter protected boolean
+ readOnly,
+ isCollection,
+ isCalendar,
+ isAddressBook;
+
+ @Getter protected List supportedComponents;
+ @Getter protected VCardVersion supportedVCardVersion;
+
+ // fill from DavProp
+
+ protected void process(DavProp prop) {
+ if (prop.currentUserPrincipal != null && prop.currentUserPrincipal.getHref() != null)
+ currentUserPrincipal = prop.currentUserPrincipal.getHref().href;
+
+ if (prop.currentUserPrivilegeSet != null) {
+ // privilege info available
+ boolean mayAll = false,
+ mayBind = false,
+ mayUnbind = false,
+ mayWrite = false,
+ mayWriteContent = false;
+ for (DavProp.Privilege privilege : prop.currentUserPrivilegeSet) {
+ if (privilege.getAll() != null) mayAll = true;
+ if (privilege.getBind() != null) mayBind = true;
+ if (privilege.getUnbind() != null) mayUnbind = true;
+ if (privilege.getWrite() != null) mayWrite = true;
+ if (privilege.getWriteContent() != null) mayWriteContent = true;
+ }
+ if (!mayAll && !mayWrite && !(mayWriteContent && mayBind && mayUnbind))
+ readOnly = true;
+ }
+
+ if (prop.addressbookHomeSet != null && prop.addressbookHomeSet.getHref() != null)
+ addressBookHomeset = URIUtils.ensureTrailingSlash(prop.addressbookHomeSet.getHref().href);
+
+ if (prop.calendarHomeSet != null && prop.calendarHomeSet.getHref() != null)
+ calendarHomeset = URIUtils.ensureTrailingSlash(prop.calendarHomeSet.getHref().href);
+
+ if (prop.displayname != null)
+ displayName = prop.displayname.getDisplayName();
+
+ if (prop.resourcetype != null) {
+ if (prop.resourcetype.getCollection() != null)
+ isCollection = true;
+ if (prop.resourcetype.getAddressbook() != null) { // CardDAV collection properties
+ isAddressBook = true;
+
+ if (prop.addressbookDescription != null)
+ description = prop.addressbookDescription.getDescription();
+ if (prop.supportedAddressData != null)
+ for (DavProp.AddressDataType dataType : prop.supportedAddressData)
+ if ("text/vcard".equalsIgnoreCase(dataType.getContentType()))
+ // ignore "3.0" as it MUST be supported anyway
+ if ("4.0".equals(dataType.getVersion()))
+ supportedVCardVersion = VCardVersion.V4_0;
+ }
+ if (prop.resourcetype.getCalendar() != null) { // CalDAV collection propertioes
+ isCalendar = true;
+
+ if (prop.calendarDescription != null)
+ description = prop.calendarDescription.getDescription();
+
+ if (prop.calendarColor != null)
+ color = prop.calendarColor.getColor();
+
+ if (prop.calendarTimezone != null)
+ timeZone = prop.calendarTimezone.getTimezone();
+
+ if (prop.supportedCalendarComponentSet != null) {
+ supportedComponents = new LinkedList<>();
+ for (Comp component : prop.supportedCalendarComponentSet)
+ supportedComponents.add(component.getName());
+ }
+ }
+ }
+
+ if (prop.getctag != null)
+ cTag = prop.getctag.getCTag();
+
+ if (prop.getetag != null)
+ eTag = prop.getetag.getETag();
+ }
+
+ // getters / setters
+
+ public Integer getColor() {
+ return color != null ? DAVUtils.CalDAVtoARGBColor(color) : null;
+ }
+
+ public URI getCurrentUserPrincipal() throws URISyntaxException {
+ return currentUserPrincipal != null ? URIUtils.parseURI(currentUserPrincipal, false) : null;
+ }
+
+ public URI getAddressbookHomeSet() throws URISyntaxException {
+ return addressBookHomeset != null ? URIUtils.parseURI(addressBookHomeset, false) : null;
+ }
+
+ public URI getCalendarHomeSet() throws URISyntaxException {
+ return calendarHomeset != null ? URIUtils.parseURI(calendarHomeset, false) : null;
+ }
+
+ public String getTimeZone() {
+ return timeZone != null ? iCalendar.TimezoneDefToTzId(timeZone) : null;
+ }
+
+ public void invalidateCTag() {
+ cTag = null;
+ }
+
+ }
}