From 4ecca76a957c934776f8bb394cc6d4f3bf9dcfe3 Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Fri, 16 Oct 2015 23:06:35 +0200 Subject: [PATCH] Group support (VCard 3 CATEGORIES) with vcard4android * VCard 3-style group support (CATEGORIES) * sync error notification improvements * some tests --- app/build.gradle | 2 +- .../at/bitfire/davdroid/DateUtilsTest.java | 78 ----- .../at/bitfire/davdroid/URLUtilsTest.java | 70 ----- .../bitfire/davdroid/resource/EventTest.java | 117 ------- .../davdroid/resource/LocalCalendarTest.java | 227 -------------- .../davdroid/resource/iCalendarTest.java | 95 ------ .../syncadapter/DavResourceFinderTest.java | 58 +--- .../davdroid/webdav/DavHttpClientTest.java | 72 ----- .../webdav/DavRedirectStrategyTest.java | 83 ----- .../webdav/TlsSniSocketFactoryTest.java | 116 ------- .../davdroid/webdav/WebDavResourceTest.java | 286 ------------------ .../davdroid/resource/LocalAddressBook.java | 89 +++++- .../davdroid/resource/LocalContact.java | 58 ++++ .../bitfire/davdroid/resource/LocalGroup.java | 38 +++ .../syncadapter/CalendarSyncManager.java | 5 + .../syncadapter/ContactsSyncManager.java | 32 +- .../davdroid/syncadapter/SyncManager.java | 6 +- .../syncadapter/TasksSyncManager.java | 6 + app/src/main/res/values-cs/strings.xml | 2 +- app/src/main/res/values-de/strings.xml | 4 +- app/src/main/res/values-es/strings.xml | 2 +- app/src/main/res/values-fr/strings.xml | 2 +- app/src/main/res/values-nl/strings.xml | 2 +- app/src/main/res/values-pl/strings.xml | 2 +- app/src/main/res/values-pt/strings.xml | 2 +- app/src/main/res/values-ru/strings.xml | 2 +- app/src/main/res/values-sr/strings.xml | 2 +- app/src/main/res/values-zh-rcn/strings.xml | 2 +- app/src/main/res/values/strings.xml | 4 +- dav4android | 2 +- ical4android | 2 +- vcard4android | 2 +- 32 files changed, 251 insertions(+), 1219 deletions(-) delete mode 100644 app/src/androidTest/java/at/bitfire/davdroid/DateUtilsTest.java delete mode 100644 app/src/androidTest/java/at/bitfire/davdroid/URLUtilsTest.java delete mode 100644 app/src/androidTest/java/at/bitfire/davdroid/resource/EventTest.java delete mode 100644 app/src/androidTest/java/at/bitfire/davdroid/resource/LocalCalendarTest.java delete mode 100644 app/src/androidTest/java/at/bitfire/davdroid/resource/iCalendarTest.java delete mode 100644 app/src/androidTest/java/at/bitfire/davdroid/webdav/DavHttpClientTest.java delete mode 100644 app/src/androidTest/java/at/bitfire/davdroid/webdav/DavRedirectStrategyTest.java delete mode 100644 app/src/androidTest/java/at/bitfire/davdroid/webdav/TlsSniSocketFactoryTest.java delete mode 100644 app/src/androidTest/java/at/bitfire/davdroid/webdav/WebDavResourceTest.java create mode 100644 app/src/main/java/at/bitfire/davdroid/resource/LocalGroup.java diff --git a/app/build.gradle b/app/build.gradle index 0646c92f..799ae6a2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,7 +18,7 @@ android { targetSdkVersion 23 versionCode 76 - versionName "0.9-alpha4" + versionName "0.9-beta1" buildConfigField "java.util.Date", "buildTime", "new java.util.Date()" } diff --git a/app/src/androidTest/java/at/bitfire/davdroid/DateUtilsTest.java b/app/src/androidTest/java/at/bitfire/davdroid/DateUtilsTest.java deleted file mode 100644 index e6c2de99..00000000 --- a/app/src/androidTest/java/at/bitfire/davdroid/DateUtilsTest.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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; - -import junit.framework.TestCase; - -import net.fortuna.ical4j.model.DateList; -import net.fortuna.ical4j.model.TimeZone; -import net.fortuna.ical4j.model.parameter.Value; -import net.fortuna.ical4j.model.property.ExDate; -import net.fortuna.ical4j.model.property.RDate; - -import java.text.ParseException; -import java.util.ArrayList; -import java.util.List; - -public class DateUtilsTest extends TestCase { - private static final String tzIdVienna = "Europe/Vienna"; - - public void testRecurrenceSetsToAndroidString() throws ParseException { - // one entry without time zone (implicitly UTC) - final List list = new ArrayList<>(2); - list.add(new RDate(new DateList("20150101T103010Z,20150102T103020Z", Value.DATE_TIME))); - assertEquals("20150101T103010Z,20150102T103020Z", DateUtils.recurrenceSetsToAndroidString(list, false)); - - // two entries (previous one + this), both with time zone Vienna - list.add(new RDate(new DateList("20150103T113030,20150704T123040", Value.DATE_TIME))); - final TimeZone tz = DateUtils.tzRegistry.getTimeZone(tzIdVienna); - for (RDate rdate : list) - rdate.setTimeZone(tz); - assertEquals("20150101T103010Z,20150102T103020Z,20150103T103030Z,20150704T103040Z", DateUtils.recurrenceSetsToAndroidString(list, false)); - - // DATEs (without time) have to be converted to T000000Z for Android - list.clear(); - list.add(new RDate(new DateList("20150101,20150702", Value.DATE))); - assertEquals("20150101T000000Z,20150702T000000Z", DateUtils.recurrenceSetsToAndroidString(list, true)); - - // DATE-TIME (floating time or UTC) recurrences for all-day events have to converted to T000000Z for Android - list.clear(); - list.add(new RDate(new DateList("20150101T000000,20150702T000000Z", Value.DATE_TIME))); - assertEquals("20150101T000000Z,20150702T000000Z", DateUtils.recurrenceSetsToAndroidString(list, true)); - } - - public void testAndroidStringToRecurrenceSets() throws ParseException { - // list of UTC times - ExDate exDate = (ExDate)DateUtils.androidStringToRecurrenceSet("20150101T103010Z,20150702T103020Z", ExDate.class, false); - DateList exDates = exDate.getDates(); - assertEquals(Value.DATE_TIME, exDates.getType()); - assertTrue(exDates.isUtc()); - assertEquals(2, exDates.size()); - assertEquals(1420108210000L, exDates.get(0).getTime()); - assertEquals(1435833020000L, exDates.get(1).getTime()); - - // list of time zone times - exDate = (ExDate)DateUtils.androidStringToRecurrenceSet(tzIdVienna + ";20150101T103010,20150702T103020", ExDate.class, false); - exDates = exDate.getDates(); - assertEquals(Value.DATE_TIME, exDates.getType()); - assertEquals(DateUtils.tzRegistry.getTimeZone(tzIdVienna), exDates.getTimeZone()); - assertEquals(2, exDates.size()); - assertEquals(1420104610000L, exDates.get(0).getTime()); - assertEquals(1435825820000L, exDates.get(1).getTime()); - - // list of dates - exDate = (ExDate)DateUtils.androidStringToRecurrenceSet("20150101T103010Z,20150702T103020Z", ExDate.class, true); - exDates = exDate.getDates(); - assertEquals(Value.DATE, exDates.getType()); - assertEquals(2, exDates.size()); - assertEquals("20150101", exDates.get(0).toString()); - assertEquals("20150702", exDates.get(1).toString()); - } - -} diff --git a/app/src/androidTest/java/at/bitfire/davdroid/URLUtilsTest.java b/app/src/androidTest/java/at/bitfire/davdroid/URLUtilsTest.java deleted file mode 100644 index 1fe00b37..00000000 --- a/app/src/androidTest/java/at/bitfire/davdroid/URLUtilsTest.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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; - -import junit.framework.TestCase; - -import java.net.URI; - - -public class URLUtilsTest extends TestCase { - - /* RFC 1738 p17 HTTP URLs: - hpath = hsegment *[ "/" hsegment ] - hsegment = *[ uchar | ";" | ":" | "@" | "&" | "=" ] - uchar = unreserved | escape - unreserved = alpha | digit | safe | extra - alpha = lowalpha | hialpha - lowalpha = ... - hialpha = ... - digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | - "8" | "9" - safe = "$" | "-" | "_" | "." | "+" - extra = "!" | "*" | "'" | "(" | ")" | "," - escape = "%" hex hex - */ - - - public void testEnsureTrailingSlash() throws Exception { - assertEquals("/test/", URIUtils.ensureTrailingSlash("/test")); - assertEquals("/test/", URIUtils.ensureTrailingSlash("/test/")); - - String withoutSlash = "http://www.test.example/dav/collection", - withSlash = withoutSlash + "/"; - assertEquals(new URI(withSlash), URIUtils.ensureTrailingSlash(new URI(withoutSlash))); - assertEquals(new URI(withSlash), URIUtils.ensureTrailingSlash(new URI(withSlash))); - } - - public void testParseURI() throws Exception { - // don't escape valid characters - String validPath = "/;:@&=$-_.+!*'(),"; - assertEquals(new URI("https://www.test.example:123" + validPath), URIUtils.parseURI("https://www.test.example:123" + validPath, false)); - assertEquals(new URI(validPath), URIUtils.parseURI(validPath, true)); - - // keep literal IPv6 addresses (only in host name) - assertEquals(new URI("https://[1:2::1]/"), URIUtils.parseURI("https://[1:2::1]/", false)); - - // "~" as home directory (valid) - assertEquals(new URI("http://www.test.example/~user1/"), URIUtils.parseURI("http://www.test.example/~user1/", false)); - assertEquals(new URI("/~user1/"), URIUtils.parseURI("/%7euser1/", true)); - - // "@" in path names (valid) - assertEquals(new URI("http://www.test.example/user@server.com/"), URIUtils.parseURI("http://www.test.example/user@server.com/", false)); - assertEquals(new URI("/user@server.com/"), URIUtils.parseURI("/user%40server.com/", true)); - assertEquals(new URI("user@server.com"), URIUtils.parseURI("user%40server.com", true)); - - // ":" in path names (valid) - assertEquals(new URI("http://www.test.example/my:cal.ics"), URIUtils.parseURI("http://www.test.example/my:cal.ics", false)); - assertEquals(new URI("/my:cal.ics"), URIUtils.parseURI("/my%3Acal.ics", true)); - assertEquals(new URI(null, null, "my:cal.ics", null, null), URIUtils.parseURI("my%3Acal.ics", true)); - - // common invalid path names - assertEquals(new URI(null, null, "my cal.ics", null, null), URIUtils.parseURI("my cal.ics", true)); - assertEquals(new URI(null, null, "{1234}.vcf", null, null), URIUtils.parseURI("{1234}.vcf", true)); - } -} diff --git a/app/src/androidTest/java/at/bitfire/davdroid/resource/EventTest.java b/app/src/androidTest/java/at/bitfire/davdroid/resource/EventTest.java deleted file mode 100644 index 19bef967..00000000 --- a/app/src/androidTest/java/at/bitfire/davdroid/resource/EventTest.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * 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 android.content.res.AssetManager; -import android.test.InstrumentationTestCase; - -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; - - public void setUp() throws IOException, InvalidResourceException { - assetMgr = getInstrumentation().getContext().getResources().getAssets(); - - eOnThatDay = parseCalendar("event-on-that-day.ics"); - eAllDay1Day = parseCalendar("all-day-1day.ics"); - eAllDay10Days = parseCalendar("all-day-10days.ics"); - eAllDay0Sec = parseCalendar("all-day-0sec.ics"); - } - - - 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()); - - assertEquals(1, event.getExceptions().size()); - Event exception = event.getExceptions().get(0); - assertEquals("20150503", exception.recurrenceId.getValue()); - assertEquals("Another summary for the third day", exception.summary); - } - - public void testStartEndTimes() throws IOException, ParserException, InvalidResourceException { - // event with start+end date-time - Event eViennaEvolution = parseCalendar("vienna-evolution.ics"); - assertEquals(1381330800000L, eViennaEvolution.getDtStartInMillis()); - assertEquals("Europe/Vienna", eViennaEvolution.getDtStartTzID()); - assertEquals(1381334400000L, eViennaEvolution.getDtEndInMillis()); - assertEquals("Europe/Vienna", eViennaEvolution.getDtEndTzID()); - } - - public void testStartEndTimesAllDay() throws IOException, ParserException { - // event with start date only - assertEquals(868838400000L, eOnThatDay.getDtStartInMillis()); - assertEquals(TimeZones.UTC_ID, eOnThatDay.getDtStartTzID()); - // DTEND missing in VEVENT, must have been set to DTSTART+1 day - assertEquals(868838400000L + 86400000, eOnThatDay.getDtEndInMillis()); - assertEquals(TimeZones.UTC_ID, eOnThatDay.getDtEndTzID()); - - // event with start+end date for all-day event (one day) - assertEquals(868838400000L, eAllDay1Day.getDtStartInMillis()); - assertEquals(TimeZones.UTC_ID, eAllDay1Day.getDtStartTzID()); - assertEquals(868838400000L + 86400000, eAllDay1Day.getDtEndInMillis()); - assertEquals(TimeZones.UTC_ID, eAllDay1Day.getDtEndTzID()); - - // event with start+end date for all-day event (ten days) - assertEquals(868838400000L, eAllDay10Days.getDtStartInMillis()); - assertEquals(TimeZones.UTC_ID, eAllDay10Days.getDtStartTzID()); - assertEquals(868838400000L + 10*86400000, eAllDay10Days.getDtEndInMillis()); - assertEquals(TimeZones.UTC_ID, eAllDay10Days.getDtEndTzID()); - - // event with start+end date on some day (invalid 0 sec-event) - assertEquals(868838400000L, eAllDay0Sec.getDtStartInMillis()); - assertEquals(TimeZones.UTC_ID, eAllDay0Sec.getDtStartTzID()); - // DTEND invalid in VEVENT, must have been set to DTSTART+1 day - assertEquals(868838400000L + 86400000, eAllDay0Sec.getDtEndInMillis()); - assertEquals(TimeZones.UTC_ID, eAllDay0Sec.getDtEndTzID()); - } - - 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.description); - } - - - protected Event parseCalendar(String fname) throws IOException, InvalidResourceException { - @Cleanup InputStream in = assetMgr.open(fname, AssetManager.ACCESS_STREAMING); - Event e = new Event(fname, null); - e.parseEntity(in, null, null); - return e; - } -} diff --git a/app/src/androidTest/java/at/bitfire/davdroid/resource/LocalCalendarTest.java b/app/src/androidTest/java/at/bitfire/davdroid/resource/LocalCalendarTest.java deleted file mode 100644 index fb81ffdd..00000000 --- a/app/src/androidTest/java/at/bitfire/davdroid/resource/LocalCalendarTest.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * 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 android.Manifest; -import android.accounts.Account; -import android.annotation.TargetApi; -import android.content.ContentProviderClient; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.os.Build; -import android.os.RemoteException; -import android.provider.CalendarContract; -import android.provider.CalendarContract.Calendars; -import android.provider.CalendarContract.Events; -import android.test.InstrumentationTestCase; -import android.util.Log; - -import net.fortuna.ical4j.model.Date; -import net.fortuna.ical4j.model.Dur; -import net.fortuna.ical4j.model.TimeZone; -import net.fortuna.ical4j.model.component.VAlarm; -import net.fortuna.ical4j.model.property.DtEnd; -import net.fortuna.ical4j.model.property.DtStart; - -import java.text.ParseException; -import java.util.Calendar; - -import at.bitfire.davdroid.DateUtils; -import lombok.Cleanup; - -public class LocalCalendarTest extends InstrumentationTestCase { - - private static final String - TAG = "davdroid.test", - accountType = "at.bitfire.davdroid.test", - calendarName = "DAVdroid_Test"; - - Context targetContext; - - ContentProviderClient providerClient; - final Account testAccount = new Account(calendarName, accountType); - - Uri calendarURI; - LocalCalendar testCalendar; - - - // helpers - - private Uri syncAdapterURI(Uri uri) { - return uri.buildUpon() - .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType) - .appendQueryParameter(Calendars.ACCOUNT_NAME, accountType) - .appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true"). - build(); - } - - private long insertNewEvent() throws RemoteException { - ContentValues values = new ContentValues(); - values.put(Events.CALENDAR_ID, testCalendar.getId()); - values.put(Events.TITLE, "Test Event"); - values.put(Events.ALL_DAY, 0); - values.put(Events.DTSTART, Calendar.getInstance().getTimeInMillis()); - values.put(Events.DTEND, Calendar.getInstance().getTimeInMillis()); - values.put(Events.EVENT_TIMEZONE, "UTC"); - values.put(Events.DIRTY, 1); - return ContentUris.parseId(providerClient.insert(syncAdapterURI(Events.CONTENT_URI), values)); - } - - private void deleteEvent(long id) throws RemoteException { - providerClient.delete(syncAdapterURI(ContentUris.withAppendedId(Events.CONTENT_URI, id)), null, null); - } - - - // initialization - - @Override - @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) - protected void setUp() throws LocalStorageException, RemoteException { - targetContext = getInstrumentation().getTargetContext(); - targetContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_CALENDAR, "No privileges for managing calendars"); - - providerClient = targetContext.getContentResolver().acquireContentProviderClient(CalendarContract.AUTHORITY); - - prepareTestCalendar(); - } - - private void prepareTestCalendar() throws LocalStorageException, RemoteException { - @Cleanup Cursor cursor = providerClient.query(Calendars.CONTENT_URI, new String[] { Calendars._ID }, - Calendars.ACCOUNT_TYPE + "=? AND " + Calendars.ACCOUNT_NAME + "=?", - new String[] { testAccount.type, testAccount.name }, null); - if (cursor != null && cursor.moveToNext()) - calendarURI = ContentUris.withAppendedId(Calendars.CONTENT_URI, cursor.getLong(0)); - else { - ServerInfo.ResourceInfo info = new ServerInfo.ResourceInfo(ServerInfo.ResourceInfo.Type.CALENDAR, false, null, "Test Calendar", null, null); - calendarURI = LocalCalendar.create(testAccount, targetContext.getContentResolver(), info); - } - - Log.i(TAG, "Prepared test calendar " + calendarURI); - testCalendar = new LocalCalendar(testAccount, providerClient, ContentUris.parseId(calendarURI), null); - } - - @Override - protected void tearDown() throws RemoteException { - deleteTestCalendar(); - } - - protected void deleteTestCalendar() throws RemoteException { - Uri uri = ContentUris.withAppendedId(Calendars.CONTENT_URI, testCalendar.id); - if (providerClient.delete(uri,null,null)>0) - Log.i(TAG,"Deleted test calendar "+uri); - else - Log.e(TAG,"Couldn't delete test calendar "+uri); - } - - - // tests - - public void testBuildEntry() throws LocalStorageException, ParseException { - final String vcardName = "testBuildEntry"; - - final TimeZone tzVienna = DateUtils.tzRegistry.getTimeZone("Europe/Vienna"); - assertNotNull(tzVienna); - - // build and write event to calendar provider - Event event = new Event(vcardName, null); - event.summary = "Sample event"; - event.description = "Sample event with date/time"; - event.location = "Sample location"; - event.dtStart = new DtStart("20150501T120000", tzVienna); - event.dtEnd = new DtEnd("20150501T130000", tzVienna); - assertFalse(event.isAllDay()); - - // set an alarm one day, two hours, three minutes and four seconds before begin of event - event.getAlarms().add(new VAlarm(new Dur(-1, -2, -3, -4))); - - testCalendar.add(event); - testCalendar.commit(); - - // read and parse event from calendar provider - Event event2 = testCalendar.findByRemoteName(vcardName, true); - assertNotNull("Couldn't build and insert event", event); - // compare with original event - try { - assertEquals(event.summary, event2.summary); - assertEquals(event.description, event2.description); - assertEquals(event.location, event2.location); - assertEquals(event.dtStart, event2.dtStart); - assertFalse(event2.isAllDay()); - - assertEquals(1, event2.getAlarms().size()); - VAlarm alarm = event2.getAlarms().get(0); - assertEquals(event.summary, alarm.getDescription().getValue()); // should be built from event name - assertEquals(new Dur(0, 0, -(24*60 + 60*2 + 3), 0), alarm.getTrigger().getDuration()); // calendar provider stores trigger in minutes - } finally { - testCalendar.delete(event); - } - } - - public void testBuildAllDayEntry() throws LocalStorageException, ParseException { - final String vcardName = "testBuildAllDayEntry"; - - // build and write event to calendar provider - Event event = new Event(vcardName, null); - event.summary = "All-day event"; - event.description = "All-day event for testing"; - event.location = "Sample location testBuildAllDayEntry"; - event.dtStart = new DtStart(new Date("20150501")); - event.dtEnd = new DtEnd(new Date("20150502")); - assertTrue(event.isAllDay()); - testCalendar.add(event); - testCalendar.commit(); - - // read and parse event from calendar provider - Event event2 = testCalendar.findByRemoteName(vcardName, true); - assertNotNull("Couldn't build and insert event", event); - // compare with original event - try { - assertEquals(event.summary, event2.summary); - assertEquals(event.description, event2.description); - assertEquals(event.location, event2.location); - assertEquals(event.dtStart, event2.dtStart); - assertTrue(event2.isAllDay()); - } finally { - testCalendar.delete(event); - } - } - - public void testCTags() throws LocalStorageException { - assertNull(testCalendar.getCTag()); - - final String cTag = "just-modified"; - testCalendar.setCTag(cTag); - - assertEquals(cTag, testCalendar.getCTag()); - } - - public void testFindNew() throws LocalStorageException, RemoteException { - // at the beginning, there are no dirty events - assertTrue(testCalendar.findNew().length == 0); - assertTrue(testCalendar.findUpdated().length == 0); - - // insert a "new" event - final long id = insertNewEvent(); - try { - // there must be one "new" event now - assertTrue(testCalendar.findNew().length == 1); - assertTrue(testCalendar.findUpdated().length == 0); - - // nothing has changed, the record must still be "new" - // see issue #233 - assertTrue(testCalendar.findNew().length == 1); - assertTrue(testCalendar.findUpdated().length == 0); - } finally { - deleteEvent(id); - } - } - -} diff --git a/app/src/androidTest/java/at/bitfire/davdroid/resource/iCalendarTest.java b/app/src/androidTest/java/at/bitfire/davdroid/resource/iCalendarTest.java deleted file mode 100644 index b7685c80..00000000 --- a/app/src/androidTest/java/at/bitfire/davdroid/resource/iCalendarTest.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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 a471d191..ad68d229 100644 --- a/app/src/androidTest/java/at/bitfire/davdroid/syncadapter/DavResourceFinderTest.java +++ b/app/src/androidTest/java/at/bitfire/davdroid/syncadapter/DavResourceFinderTest.java @@ -8,72 +8,26 @@ package at.bitfire.davdroid.syncadapter; -import android.test.InstrumentationTestCase; +import junit.framework.TestCase; import java.io.IOException; -import java.net.URI; -import java.util.List; -import at.bitfire.davdroid.TestConstants; import at.bitfire.davdroid.resource.DavResourceFinder; -import at.bitfire.davdroid.resource.ServerInfo; -import at.bitfire.davdroid.resource.ServerInfo.ResourceInfo; -public class DavResourceFinderTest extends InstrumentationTestCase { +public class DavResourceFinderTest extends TestCase { DavResourceFinder finder; @Override protected void setUp() { - finder = new DavResourceFinder(getInstrumentation().getContext()); } @Override protected void tearDown() throws IOException { - } - + } - public void testFindResourcesRobohydra() throws Exception { - ServerInfo info = new ServerInfo(new URI(TestConstants.ROBOHYDRA_BASE), "test", "test", true); - finder.findResources(info); - - /*** CardDAV ***/ - List collections = info.getAddressBooks(); - // two address books - assertEquals(2, collections.size()); - ResourceInfo collection = collections.get(0); - assertEquals(TestConstants.roboHydra.resolve("/dav/addressbooks/test/default.vcf/").toString(), collection.getURL()); - assertEquals("Default Address Book", collection.getDescription()); - // second one - collection = collections.get(1); - assertEquals("https://my.server/absolute:uri/my-address-book/", collection.getURL()); - assertEquals("Absolute URI VCard Book", collection.getDescription()); - - /*** CalDAV ***/ - collections = info.getCalendars(); - assertEquals(2, collections.size()); - - ResourceInfo resource = collections.get(0); - assertEquals("Private Calendar", resource.getTitle()); - assertEquals("This is my private calendar.", resource.getDescription()); - assertFalse(resource.isReadOnly()); - - resource = collections.get(1); - assertEquals("Work Calendar", resource.getTitle()); - assertTrue(resource.isReadOnly()); - } - - - public void testGetInitialContextURL() throws Exception { - // without SRV records, but with well-known paths - ServerInfo roboHydra = new ServerInfo(new URI(TestConstants.ROBOHYDRA_BASE), "test", "test", true); - assertEquals(TestConstants.roboHydra.resolve("/"), finder.getInitialContextURL(roboHydra, "caldav")); - assertEquals(TestConstants.roboHydra.resolve("/"), finder.getInitialContextURL(roboHydra, "carddav")); - - // with SRV records and well-known paths - ServerInfo iCloud = new ServerInfo(new URI("mailto:test@icloud.com"), "", "", true); - assertEquals(new URI("https://contacts.icloud.com/"), finder.getInitialContextURL(iCloud, "carddav")); - assertEquals(new URI("https://caldav.icloud.com/"), finder.getInitialContextURL(iCloud, "caldav")); - } + public void testFindResources() { + // TODO + } } diff --git a/app/src/androidTest/java/at/bitfire/davdroid/webdav/DavHttpClientTest.java b/app/src/androidTest/java/at/bitfire/davdroid/webdav/DavHttpClientTest.java deleted file mode 100644 index eda743fa..00000000 --- a/app/src/androidTest/java/at/bitfire/davdroid/webdav/DavHttpClientTest.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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.webdav; - -import android.test.InstrumentationTestCase; - -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGetHC4; -import org.apache.http.client.methods.HttpPostHC4; -import org.apache.http.impl.client.CloseableHttpClient; - -import java.io.IOException; -import java.net.URI; - -import at.bitfire.davdroid.TestConstants; - -public class DavHttpClientTest extends InstrumentationTestCase { - final static URI testCookieURI = TestConstants.roboHydra.resolve("/dav/testCookieStore"); - - CloseableHttpClient httpClient; - - @Override - protected void setUp() throws Exception { - httpClient = DavHttpClient.create(); - } - - @Override - protected void tearDown() throws Exception { - httpClient.close(); - } - - - public void testCookies() throws IOException { - CloseableHttpResponse response = null; - - HttpGetHC4 get = new HttpGetHC4(testCookieURI); - get.setHeader("Accept", "text/xml"); - - // at first, DavHttpClient doesn't send a cookie - try { - response = httpClient.execute(get); - assertEquals(412, response.getStatusLine().getStatusCode()); - } finally { - if (response != null) - response.close(); - } - - // POST sets a cookie to DavHttpClient - try { - response = httpClient.execute(new HttpPostHC4(testCookieURI)); - assertEquals(200, response.getStatusLine().getStatusCode()); - } finally { - if (response != null) - response.close(); - } - - // and now DavHttpClient sends a cookie for GET, too - try { - response = httpClient.execute(get); - assertEquals(200, response.getStatusLine().getStatusCode()); - } finally { - if (response != null) - response.close(); - } - } -} diff --git a/app/src/androidTest/java/at/bitfire/davdroid/webdav/DavRedirectStrategyTest.java b/app/src/androidTest/java/at/bitfire/davdroid/webdav/DavRedirectStrategyTest.java deleted file mode 100644 index 28a6f9a2..00000000 --- a/app/src/androidTest/java/at/bitfire/davdroid/webdav/DavRedirectStrategyTest.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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.webdav; - -import junit.framework.TestCase; - -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpOptions; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.client.protocol.HttpClientContext; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.protocol.HttpContext; - -import java.io.IOException; - -import at.bitfire.davdroid.TestConstants; - -public class DavRedirectStrategyTest extends TestCase { - - CloseableHttpClient httpClient; - final DavRedirectStrategy strategy = DavRedirectStrategy.INSTANCE; - - @Override - protected void setUp() { - httpClient = HttpClientBuilder.create() - .useSystemProperties() - .disableRedirectHandling() - .build(); - } - - @Override - protected void tearDown() throws IOException { - httpClient.close(); - } - - - // happy cases - - public void testNonRedirection() throws Exception { - HttpUriRequest request = new HttpOptions(TestConstants.roboHydra); - HttpResponse response = httpClient.execute(request); - assertFalse(strategy.isRedirected(request, response, null)); - } - - public void testDefaultRedirection() throws Exception { - final String newLocation = "/new-location"; - - HttpContext context = HttpClientContext.create(); - HttpUriRequest request = new HttpOptions(TestConstants.roboHydra.resolve("redirect/301?to=" + newLocation)); - HttpResponse response = httpClient.execute(request, context); - assertTrue(strategy.isRedirected(request, response, context)); - - HttpUriRequest redirected = strategy.getRedirect(request, response, context); - assertEquals(TestConstants.roboHydra.resolve(newLocation), redirected.getURI()); - } - - - // error cases - - public void testMissingLocation() throws Exception { - HttpContext context = HttpClientContext.create(); - HttpUriRequest request = new HttpOptions(TestConstants.roboHydra.resolve("redirect/without-location")); - HttpResponse response = httpClient.execute(request, context); - assertFalse(strategy.isRedirected(request, response, context)); - } - - public void testRelativeLocation() throws Exception { - HttpContext context = HttpClientContext.create(); - HttpUriRequest request = new HttpOptions(TestConstants.roboHydra.resolve("redirect/relative")); - HttpResponse response = httpClient.execute(request, context); - assertTrue(strategy.isRedirected(request, response, context)); - - HttpUriRequest redirected = strategy.getRedirect(request, response, context); - assertEquals(TestConstants.roboHydra.resolve("/new/location"), redirected.getURI()); - } -} diff --git a/app/src/androidTest/java/at/bitfire/davdroid/webdav/TlsSniSocketFactoryTest.java b/app/src/androidTest/java/at/bitfire/davdroid/webdav/TlsSniSocketFactoryTest.java deleted file mode 100644 index 365118a3..00000000 --- a/app/src/androidTest/java/at/bitfire/davdroid/webdav/TlsSniSocketFactoryTest.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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.webdav; - -import android.util.Log; - -import junit.framework.TestCase; - -import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.exception.ExceptionUtils; -import org.apache.http.HttpHost; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.security.cert.CertPathValidatorException; - -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLHandshakeException; - -import lombok.Cleanup; - -public class TlsSniSocketFactoryTest extends TestCase { - private static final String TAG = "davdroid.TlsSniSocketFactoryTest"; - - final TlsSniSocketFactory factory = TlsSniSocketFactory.getSocketFactory(); - - private InetSocketAddress sampleTlsEndpoint; - - @Override - protected void setUp() { - // sni.velox.ch is used to test SNI (without SNI support, the certificate is invalid) - sampleTlsEndpoint = new InetSocketAddress("sni.velox.ch", 443); - } - - public void testCreateSocket() { - try { - @Cleanup Socket socket = factory.createSocket(null); - assertFalse(socket.isConnected()); - } catch (IOException e) { - fail(); - } - } - - public void testConnectSocket() { - try { - factory.connectSocket(1000, null, new HttpHost(sampleTlsEndpoint.getHostName()), sampleTlsEndpoint, null, null); - } catch (IOException e) { - Log.e(TAG, "I/O exception", e); - fail(); - } - } - - public void testCreateLayeredSocket() { - try { - // connect plain socket first - @Cleanup Socket plain = new Socket(); - plain.connect(sampleTlsEndpoint); - assertTrue(plain.isConnected()); - - // then create TLS socket on top of it and establish TLS Connection - @Cleanup Socket socket = factory.createLayeredSocket(plain, sampleTlsEndpoint.getHostName(), sampleTlsEndpoint.getPort(), null); - assertTrue(socket.isConnected()); - - } catch (IOException e) { - Log.e(TAG, "I/O exception", e); - fail(); - } - } - - public void testProtocolVersions() throws IOException { - String enabledProtocols[] = TlsSniSocketFactory.protocols; - // SSL (all versions) should be disabled - for (String protocol : enabledProtocols) - assertFalse(protocol.contains("SSL")); - // TLS v1+ should be enabled - assertTrue(ArrayUtils.contains(enabledProtocols, "TLSv1")); - assertTrue(ArrayUtils.contains(enabledProtocols, "TLSv1.1")); - assertTrue(ArrayUtils.contains(enabledProtocols, "TLSv1.2")); - } - - - public void testHostnameNotInCertificate() throws IOException { - try { - // host with certificate that doesn't match host name - // use the IP address as host name because IP addresses are usually not in the certificate subject - final String ipHostname = sampleTlsEndpoint.getAddress().getHostAddress(); - InetSocketAddress host = new InetSocketAddress(ipHostname, 443); - @Cleanup Socket socket = factory.connectSocket(0, null, new HttpHost(ipHostname), host, null, null); - fail(); - } catch (SSLException e) { - Log.i(TAG, "Expected exception", e); - assertFalse(ExceptionUtils.indexOfType(e, SSLException.class) == -1); - } - } - - public void testUntrustedCertificate() throws IOException { - try { - // host with certificate that is not trusted by default - InetSocketAddress host = new InetSocketAddress("cacert.org", 443); - - @Cleanup Socket socket = factory.connectSocket(0, null, new HttpHost(host.getHostName()), host, null, null); - fail(); - } catch (SSLHandshakeException e) { - Log.i(TAG, "Expected exception", e); - assertFalse(ExceptionUtils.indexOfType(e, CertPathValidatorException.class) == -1); - } - } - -} diff --git a/app/src/androidTest/java/at/bitfire/davdroid/webdav/WebDavResourceTest.java b/app/src/androidTest/java/at/bitfire/davdroid/webdav/WebDavResourceTest.java deleted file mode 100644 index 7e033a96..00000000 --- a/app/src/androidTest/java/at/bitfire/davdroid/webdav/WebDavResourceTest.java +++ /dev/null @@ -1,286 +0,0 @@ -/* - * 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.webdav; - -import android.content.res.AssetManager; -import android.test.InstrumentationTestCase; - -import org.apache.commons.io.IOUtils; -import org.apache.http.impl.client.CloseableHttpClient; - -import java.io.InputStream; -import java.net.URI; -import java.util.Arrays; - -import javax.net.ssl.SSLPeerUnverifiedException; - -import at.bitfire.davdroid.TestConstants; -import at.bitfire.davdroid.webdav.HttpPropfind.Mode; -import at.bitfire.davdroid.webdav.WebDavResource.PutMode; -import ezvcard.VCardVersion; -import lombok.Cleanup; - -// tests require running robohydra! - -public class WebDavResourceTest extends InstrumentationTestCase { - final static byte[] SAMPLE_CONTENT = new byte[] { 1, 2, 3, 4, 5 }; - - AssetManager assetMgr; - CloseableHttpClient httpClient; - - WebDavResource baseDAV; - WebDavResource davAssets, - davCollection, davNonExistingFile, davExistingFile; - - @Override - protected void setUp() throws Exception { - httpClient = DavHttpClient.create(); - - assetMgr = getInstrumentation().getContext().getResources().getAssets(); - - baseDAV = new WebDavResource(httpClient, TestConstants.roboHydra.resolve("/dav/")); - davAssets = new WebDavResource(httpClient, TestConstants.roboHydra.resolve("assets/")); - davCollection = new WebDavResource(httpClient, TestConstants.roboHydra.resolve("dav/")); - - davNonExistingFile = new WebDavResource(davCollection, "collection/new.file"); - davExistingFile = new WebDavResource(davCollection, "collection/existing.file"); - } - - @Override - protected void tearDown() throws Exception { - httpClient.close(); - } - - - /* test feature detection */ - - public void testOptions() throws Exception { - String[] davMethods = new String[] { "PROPFIND", "GET", "PUT", "DELETE", "REPORT" }, - davCapabilities = new String[] { "addressbook", "calendar-access" }; - - WebDavResource capable = new WebDavResource(baseDAV); - capable.options(); - for (String davMethod : davMethods) - assertTrue(capable.supportsMethod(davMethod)); - for (String capability : davCapabilities) - assertTrue(capable.supportsDAV(capability)); - } - - public void testPropfindCurrentUserPrincipal() throws Exception { - davCollection.propfind(HttpPropfind.Mode.CURRENT_USER_PRINCIPAL); - assertEquals(new URI("/dav/principals/users/test"), davCollection.getProperties().getCurrentUserPrincipal()); - - WebDavResource simpleFile = new WebDavResource(davAssets, "test.random"); - try { - simpleFile.propfind(HttpPropfind.Mode.CURRENT_USER_PRINCIPAL); - fail(); - - } catch(DavException ex) { - } - 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.getProperties().getAddressbookHomeSet()); - assertEquals(new URI("/dav/calendars/test/"), dav.getProperties().getCalendarHomeSet()); - } - - public void testPropfindAddressBooks() throws Exception { - WebDavResource dav = new WebDavResource(davCollection, "addressbooks/test"); - dav.propfind(HttpPropfind.Mode.CARDDAV_COLLECTIONS); - - // there should be two address books - assertEquals(3, dav.getMembers().size()); - - // the first one is not an address book and not even a collection (referenced by relative URI) - WebDavResource ab = dav.getMembers().get(0); - assertEquals(TestConstants.roboHydra.resolve("/dav/addressbooks/test/useless-member"), ab.getLocation()); - assertEquals("useless-member", ab.getName()); - 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.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.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(new Integer(0xFFFF00FF), dav.getMembers().get(2).getProperties().getColor()); - for (WebDavResource member : dav.getMembers()) { - if (member.getName().contains(".ics")) - assertTrue(member.getProperties().isCalendar()); - else - 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"; - - String requestPaths[] = { - "/dav/collection-response-with-trailing-slash", - "/dav/collection-response-with-trailing-slash/", - "/dav/collection-response-without-trailing-slash", - "/dav/collection-response-without-trailing-slash/" - }; - - for (String path : requestPaths) { - WebDavResource davSlash = new WebDavResource(davCollection, new URI(path)); - davSlash.propfind(Mode.CARDDAV_COLLECTIONS); - assertEquals(new URI(principalOK), davSlash.getProperties().getCurrentUserPrincipal()); - } - } - - public void testStrangeMemberNames() throws Exception { - // construct a WebDavResource from a base collection and a member which is an encoded URL (see https://github.com/bitfireAT/davdroid/issues/482) - WebDavResource dav = new WebDavResource(davCollection, "http%3A%2F%2Fwww.invalid.example%2Fm8%2Ffeeds%2Fcontacts%2Fmaria.mueller%2540gmail.com%2Fbase%2F5528abc5720cecc.vcf"); - dav.get("text/vcard"); - } - - - /* test normal HTTP/WebDAV */ - - public void testPropfindRedirection() throws Exception { - // PROPFIND redirection - WebDavResource redirected = new WebDavResource(baseDAV, new URI("/redirect/301?to=/dav/")); - redirected.propfind(Mode.CURRENT_USER_PRINCIPAL); - assertEquals("/dav/", redirected.getLocation().getPath()); - } - - public void testGet() throws Exception { - WebDavResource simpleFile = new WebDavResource(davAssets, "test.random"); - simpleFile.get("*/*"); - @Cleanup InputStream is = assetMgr.open("test.random", AssetManager.ACCESS_STREAMING); - byte[] expected = IOUtils.toByteArray(is); - assertTrue(Arrays.equals(expected, simpleFile.getContent())); - } - - public void testGetHttpsWithSni() throws Exception { - WebDavResource file = new WebDavResource(httpClient, new URI("https://sni.velox.ch")); - - boolean sniWorking = false; - try { - file.get("* /*"); - sniWorking = true; - } catch (SSLPeerUnverifiedException e) { - } - - assertTrue(sniWorking); - } - - public void testMultiGet() throws Exception { - 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.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) - assertEquals("1.vcf", davAddressBook.getMembers().get(0).getName()); - assertEquals("2:3@my%40pc.vcf", davAddressBook.getMembers().get(1).getName()); - // all contacts have some content - for (WebDavResource member : davAddressBook.getMembers()) - assertNotNull(member.getContent()); - } - - public void testMultiGetWith404() throws Exception { - WebDavResource davAddressBook = new WebDavResource(davCollection, "addressbooks/default-with-404.vcf/"); - try { - davAddressBook.multiGet(DavMultiget.Type.ADDRESS_BOOK, new String[]{ "notexisting" }); - fail(); - } catch(NotFoundException e) { - // addressbooks/default.vcf/notexisting doesn't exist, - // so server responds with 404 which causes a NotFoundException - } - } - - public void testPutAddDontOverwrite() throws Exception { - // should succeed on a non-existing file - assertEquals("has-just-been-created", davNonExistingFile.put(SAMPLE_CONTENT, PutMode.ADD_DONT_OVERWRITE)); - - // should fail on an existing file - try { - davExistingFile.put(SAMPLE_CONTENT, PutMode.ADD_DONT_OVERWRITE); - fail(); - } catch(PreconditionFailedException ex) { - } - } - - public void testPutUpdateDontOverwrite() throws Exception { - // should succeed on an existing file - assertEquals("has-just-been-updated", davExistingFile.put(SAMPLE_CONTENT, PutMode.UPDATE_DONT_OVERWRITE)); - - // should fail on a non-existing file (resource has been deleted on server, thus server returns 412) - try { - davNonExistingFile.put(SAMPLE_CONTENT, PutMode.UPDATE_DONT_OVERWRITE); - fail(); - } catch(PreconditionFailedException ex) { - } - - // should fail on existing file with wrong ETag (resource has changed on server, thus server returns 409) - try { - WebDavResource dav = new WebDavResource(davCollection, new URI("collection/existing.file?conflict=1")); - dav.put(SAMPLE_CONTENT, PutMode.UPDATE_DONT_OVERWRITE); - fail(); - } catch(ConflictException ex) { - } - } - - public void testDelete() throws Exception { - // should succeed on an existing file - davExistingFile.delete(); - - // should fail on a non-existing file - try { - davNonExistingFile.delete(); - fail(); - } catch (NotFoundException e) { - } - } - - - /* test CalDAV/CardDAV */ - - - /* special test */ - - public void testGetSpecialURLs() throws Exception { - WebDavResource dav = new WebDavResource(davAssets, "member-with:colon.vcf"); - try { - dav.get("*/*"); - fail(); - } catch(NotFoundException e) { - assertTrue(true); - } - } - -} 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 8d9ecf9e..10fd80f3 100644 --- a/app/src/main/java/at/bitfire/davdroid/resource/LocalAddressBook.java +++ b/app/src/main/java/at/bitfire/davdroid/resource/LocalAddressBook.java @@ -9,19 +9,28 @@ package at.bitfire.davdroid.resource; import android.accounts.Account; import android.content.ContentProviderClient; +import android.content.ContentUris; +import android.content.ContentValues; +import android.database.Cursor; import android.os.Bundle; import android.os.Parcel; +import android.os.RemoteException; import android.provider.ContactsContract; +import android.provider.ContactsContract.Data; +import android.provider.ContactsContract.Groups; +import android.provider.ContactsContract.RawContacts; +import android.provider.ContactsContract.CommonDataKinds.GroupMembership; + +import java.security.acl.Group; +import java.util.LinkedList; +import java.util.List; import at.bitfire.davdroid.Constants; import at.bitfire.vcard4android.AndroidAddressBook; import at.bitfire.vcard4android.AndroidContact; -import at.bitfire.vcard4android.AndroidContactFactory; import at.bitfire.vcard4android.AndroidGroupFactory; -import at.bitfire.vcard4android.Contact; import at.bitfire.vcard4android.ContactsStorageException; import lombok.Cleanup; -import lombok.Synchronized; public class LocalAddressBook extends AndroidAddressBook implements LocalCollection { @@ -51,7 +60,7 @@ public class LocalAddressBook extends AndroidAddressBook implements LocalCollect */ @Override public LocalContact[] getDeleted() throws ContactsStorageException { - return (LocalContact[])queryContacts(ContactsContract.RawContacts.DELETED + "!=0", null); + return (LocalContact[])queryContacts(RawContacts.DELETED + "!=0", null); } /** @@ -59,7 +68,7 @@ public class LocalAddressBook extends AndroidAddressBook implements LocalCollect */ @Override public LocalContact[] getDirty() throws ContactsStorageException { - return (LocalContact[])queryContacts(ContactsContract.RawContacts.DIRTY + "!=0", null); + return (LocalContact[])queryContacts(RawContacts.DIRTY + "!=0", null); } /** @@ -71,6 +80,76 @@ public class LocalAddressBook extends AndroidAddressBook implements LocalCollect } + // GROUPS + + /** + * Finds the first group with the given title. + * @param displayName title of the group to look for + * @return group with given title, or null if none + */ + public LocalGroup findGroupByTitle(String displayName) throws ContactsStorageException { + try { + @Cleanup Cursor cursor = provider.query(syncAdapterURI(Groups.CONTENT_URI), + new String[] { Groups._ID }, + ContactsContract.Groups.TITLE + "=?", new String[] { displayName }, null); + if (cursor != null && cursor.moveToNext()) + return new LocalGroup(this, cursor.getLong(0)); + } catch (RemoteException e) { + throw new ContactsStorageException("Couldn't find local contact group", e); + } + return null; + } + + public LocalGroup[] getDeletedGroups() throws ContactsStorageException { + List groups = new LinkedList<>(); + try { + @Cleanup Cursor cursor = provider.query(syncAdapterURI(Groups.CONTENT_URI), + new String[] { Groups._ID }, + Groups.DELETED + "!=0", null, null); + while (cursor != null && cursor.moveToNext()) + groups.add(new LocalGroup(this, cursor.getLong(0))); + } catch (RemoteException e) { + throw new ContactsStorageException("Couldn't query deleted groups", e); + } + return groups.toArray(new LocalGroup[groups.size()]); + } + + public LocalGroup[] getDirtyGroups() throws ContactsStorageException { + List groups = new LinkedList<>(); + try { + @Cleanup Cursor cursor = provider.query(syncAdapterURI(Groups.CONTENT_URI), + new String[] { Groups._ID }, + Groups.DIRTY + "!=0", null, null); + while (cursor != null && cursor.moveToNext()) + groups.add(new LocalGroup(this, cursor.getLong(0))); + } catch (RemoteException e) { + throw new ContactsStorageException("Couldn't query dirty groups", e); + } + return groups.toArray(new LocalGroup[groups.size()]); + } + + public void markMembersDirty(long groupId) throws ContactsStorageException { + ContentValues dirty = new ContentValues(1); + dirty.put(RawContacts.DIRTY, 1); + try { + // query all GroupMemberships of this groupId, mark every corresponding raw contact as DIRTY + @Cleanup Cursor cursor = provider.query(syncAdapterURI(Data.CONTENT_URI), + new String[] { GroupMembership.RAW_CONTACT_ID }, + Data.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?", + new String[] { GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupId) }, null); + while (cursor != null && cursor.moveToNext()) { + long id = cursor.getLong(0); + Constants.log.debug("Marking raw contact #" + id + " as dirty"); + provider.update(syncAdapterURI(ContentUris.withAppendedId(RawContacts.CONTENT_URI, id)), dirty, null, null); + } + } catch (RemoteException e) { + throw new ContactsStorageException("Couldn't query dirty groups", e); + } + } + + + // SYNC STATE + protected void readSyncState() throws ContactsStorageException { @Cleanup("recycle") Parcel parcel = Parcel.obtain(); byte[] raw = getSyncState(); diff --git a/app/src/main/java/at/bitfire/davdroid/resource/LocalContact.java b/app/src/main/java/at/bitfire/davdroid/resource/LocalContact.java index 22c959bc..579857d8 100644 --- a/app/src/main/java/at/bitfire/davdroid/resource/LocalContact.java +++ b/app/src/main/java/at/bitfire/davdroid/resource/LocalContact.java @@ -8,14 +8,20 @@ package at.bitfire.davdroid.resource; +import android.content.ContentProviderOperation; import android.content.ContentValues; import android.os.RemoteException; import android.provider.ContactsContract; +import android.provider.ContactsContract.CommonDataKinds.GroupMembership; + +import java.io.FileNotFoundException; import at.bitfire.davdroid.BuildConfig; +import at.bitfire.davdroid.Constants; import at.bitfire.vcard4android.AndroidAddressBook; import at.bitfire.vcard4android.AndroidContact; import at.bitfire.vcard4android.AndroidContactFactory; +import at.bitfire.vcard4android.BatchOperation; import at.bitfire.vcard4android.Contact; import at.bitfire.vcard4android.ContactsStorageException; import ezvcard.Ezvcard; @@ -62,6 +68,58 @@ public class LocalContact extends AndroidContact implements LocalResource { } + // group support + + @Override + protected void populateGroupMembership(ContentValues row) { + if (row.containsKey(GroupMembership.GROUP_ROW_ID)) { + long groupId = row.getAsLong(GroupMembership.GROUP_ROW_ID); + + // fetch group + LocalGroup group = new LocalGroup(addressBook, groupId); + try { + Contact groupInfo = group.getContact(); + + // add to CATEGORIES + contact.getCategories().add(groupInfo.displayName); + } catch (FileNotFoundException|ContactsStorageException e) { + Constants.log.warn("Couldn't find assigned group #" + groupId + ", ignoring membership", e); + } + } + } + + @Override + protected void insertGroupMemberships(BatchOperation batch) throws ContactsStorageException { + for (String category : contact.getCategories()) { + // Is there already a category with this display name? + LocalGroup group = ((LocalAddressBook)addressBook).findGroupByTitle(category); + + if (group == null) { + // no, we have to create the group before inserting the membership + + Contact groupInfo = new Contact(); + groupInfo.displayName = category; + group = new LocalGroup(addressBook, groupInfo); + group.create(); + } + + Long groupId = group.getId(); + if (groupId != null) { + ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(dataSyncURI()); + if (id == null) + builder.withValueBackReference(GroupMembership.RAW_CONTACT_ID, 0); + else + builder.withValue(GroupMembership.RAW_CONTACT_ID, id); + builder .withValue(GroupMembership.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE) + .withValue(GroupMembership.GROUP_ROW_ID, groupId); + batch.enqueue(builder.build()); + } + } + } + + + // factory + static class Factory extends AndroidContactFactory { static final Factory INSTANCE = new Factory(); diff --git a/app/src/main/java/at/bitfire/davdroid/resource/LocalGroup.java b/app/src/main/java/at/bitfire/davdroid/resource/LocalGroup.java new file mode 100644 index 00000000..03cb62d6 --- /dev/null +++ b/app/src/main/java/at/bitfire/davdroid/resource/LocalGroup.java @@ -0,0 +1,38 @@ +/* + * 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 android.content.ContentUris; +import android.content.ContentValues; +import android.net.Uri; +import android.os.RemoteException; +import android.provider.ContactsContract; + +import at.bitfire.vcard4android.AndroidAddressBook; +import at.bitfire.vcard4android.AndroidGroup; +import at.bitfire.vcard4android.Contact; +import at.bitfire.vcard4android.ContactsStorageException; + +public class LocalGroup extends AndroidGroup { + + public LocalGroup(AndroidAddressBook addressBook, long id) { + super(addressBook, id); + } + + public LocalGroup(AndroidAddressBook addressBook, Contact contact) { + super(addressBook, contact); + } + + public void clearDirty() throws ContactsStorageException { + ContentValues values = new ContentValues(1); + values.put(ContactsContract.Groups.DIRTY, 0); + update(values); + } + +} diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.java b/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.java index a8c60ce5..e08f5720 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.java +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.java @@ -44,6 +44,7 @@ import at.bitfire.dav4android.property.GetContentType; import at.bitfire.dav4android.property.GetETag; import at.bitfire.davdroid.ArrayUtils; import at.bitfire.davdroid.Constants; +import at.bitfire.davdroid.R; import at.bitfire.davdroid.resource.LocalCalendar; import at.bitfire.davdroid.resource.LocalEvent; import at.bitfire.davdroid.resource.LocalResource; @@ -63,6 +64,10 @@ public class CalendarSyncManager extends SyncManager { localCollection = calendar; } + @Override + protected String getSyncErrorTitle() { + return context.getString(R.string.sync_error_calendar, account.name); + } @Override protected void prepare() { diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.java b/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.java index 48d84573..bb1e4e9d 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.java +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.java @@ -23,7 +23,6 @@ import com.squareup.okhttp.ResponseBody; import org.apache.commons.codec.Charsets; import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.exception.ExceptionUtils; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -45,8 +44,10 @@ import at.bitfire.dav4android.property.SupportedAddressData; import at.bitfire.davdroid.ArrayUtils; import at.bitfire.davdroid.Constants; import at.bitfire.davdroid.HttpClient; +import at.bitfire.davdroid.R; import at.bitfire.davdroid.resource.LocalAddressBook; import at.bitfire.davdroid.resource.LocalContact; +import at.bitfire.davdroid.resource.LocalGroup; import at.bitfire.davdroid.resource.LocalResource; import at.bitfire.vcard4android.Contact; import at.bitfire.vcard4android.ContactsStorageException; @@ -67,6 +68,11 @@ public class ContactsSyncManager extends SyncManager { this.provider = provider; } + @Override + protected String getSyncErrorTitle() { + return context.getString(R.string.sync_error_contacts, account.name); + } + @Override protected void prepare() throws ContactsStorageException { @@ -78,6 +84,8 @@ public class ContactsSyncManager extends SyncManager { throw new ContactsStorageException("Couldn't get address book URL"); collectionURL = HttpUrl.parse(url); davCollection = new DavAddressBook(httpClient, collectionURL); + + processChangedGroups(); } @Override @@ -185,6 +193,28 @@ public class ContactsSyncManager extends SyncManager { private DavAddressBook davAddressBook() { return (DavAddressBook)davCollection; } private LocalAddressBook localAddressBook() { return (LocalAddressBook)localCollection; } + private void processChangedGroups() throws ContactsStorageException { + LocalAddressBook addressBook = localAddressBook(); + + // groups with DELETED=1: remove group finally + for (LocalGroup group : addressBook.getDeletedGroups()) { + long groupId = group.getId(); + Constants.log.debug("Finally removing group #" + groupId); + // remove group memberships, but not as sync adapter (should marks contacts as DIRTY) + // NOTE: doesn't work that way because Contact Provider removes the group memberships even for DELETED groups + // addressBook.removeGroupMemberships(groupId, false); + group.delete(); + } + + // groups with DIRTY=1: mark all memberships as dirty, then clean DIRTY flag of group + for (LocalGroup group : addressBook.getDirtyGroups()) { + long groupId = group.getId(); + Constants.log.debug("Marking members of modified group #" + groupId + " as dirty"); + addressBook.markMembersDirty(groupId); + group.clearDirty(); + } + } + private void processVCard(String fileName, String eTag, InputStream stream, Charset charset, Contact.Downloader downloader) throws IOException, ContactsStorageException { Contact contacts[] = Contact.fromStream(stream, charset, downloader); if (contacts != null && contacts.length == 1) { 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 cd41c672..37675bb4 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.java +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.java @@ -107,6 +107,8 @@ abstract public class SyncManager { notificationManager.cancel(account.name, this.notificationId = notificationId); } + protected abstract String getSyncErrorTitle(); + public void performSync() { int syncPhase = SYNC_PHASE_PREPARE; try { @@ -201,8 +203,8 @@ abstract public class SyncManager { Notification.Builder builder = new Notification.Builder(context); Notification notification; builder .setSmallIcon(R.drawable.ic_launcher) - .setContentTitle(context.getString(R.string.sync_error_title, account.name)) - .setContentIntent(PendingIntent.getActivity(context, 0, detailsIntent, PendingIntent.FLAG_CANCEL_CURRENT)); + .setContentTitle(getSyncErrorTitle()) + .setContentIntent(PendingIntent.getActivity(context, notificationId, detailsIntent, PendingIntent.FLAG_UPDATE_CURRENT)); if (Build.VERSION.SDK_INT >= 20) builder.setLocalOnly(true); diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncManager.java b/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncManager.java index dcbf85dc..d198c550 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncManager.java +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncManager.java @@ -44,6 +44,7 @@ import at.bitfire.dav4android.property.GetContentType; import at.bitfire.dav4android.property.GetETag; import at.bitfire.davdroid.ArrayUtils; import at.bitfire.davdroid.Constants; +import at.bitfire.davdroid.R; import at.bitfire.davdroid.resource.LocalCalendar; import at.bitfire.davdroid.resource.LocalResource; import at.bitfire.davdroid.resource.LocalTask; @@ -67,6 +68,11 @@ public class TasksSyncManager extends SyncManager { localCollection = taskList; } + @Override + protected String getSyncErrorTitle() { + return context.getString(R.string.sync_error_tasks, account.name); + } + @Override protected void prepare() { diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index a92d2b66..8eea4401 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -187,6 +187,6 @@ "Použijte svou emailovou adresu jako jméno účtu. Android bude používat tuto hodnotu jako jméno ORGANIZÁTORA událostí které vytvoříte. Nelze mít dva účty se stejným jménem. pouze pro čtení - Synchronizace selhala + Synchronizace selhala \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 9a20476a..c4910074 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -182,7 +182,9 @@ "Verwenden Sie Ihre Email-Adresse als Kontoname, da Android den Kontonamen als ORGANIZER-Feld in Terminen benutzt. Sie können keine zwei Konten mit dem gleichen Namen anlegen. schreibgeschützt - Synchronisierung von %s fehlgeschlagen + Kalender-Synchronisierung fehlgeschlagen (%s) + Adressbuch-Synchronisierung fehlgeschlagen (%s) + Aufgaben-Synchronisierung fehlgeschlagen (%s) Fehler beim %s Serverfehler beim %s Datenbank-Fehler beim %s diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 3dd0b279..15f09957 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -106,5 +106,5 @@ DAVdroid JB Workaround.

"ORGANIZADOR de tus eventos; se necesita si se usa información de los asistentes" "Usa tu dirección de correo electrónico como nombre de cuenta porque Android usará el nombre de cuenta como campo de ORGANIZADOR para los eventos que crees. No puedes tener dos cuentas con el mismo nombre. solo lectura -La sincronización ha fallado +La sincronización ha fallado diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 671ffeee..6a5747a1 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -151,7 +151,7 @@ Choisir les listes de tâches à synchroniser : Listes de tâches Sauter - La synchronisation a échoué + La synchronisation a échoué Un certificate dans le chemin n\'est pas sûr. Lire la FAQ pour plus d\'information. diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 0f4dd32a..7848dce0 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -125,6 +125,6 @@ "Gebruik uw e-mailadres als accountnaam omdat Android de accountnaam zal gebruiken als ORGANIZER veld voor afspraken die u maakt. Je kunt geen twee accounts met dezelfde naam gebruiken. Alleen lezen - Synchronisatie fout + Synchronisatie fout diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 94fd7119..4c8c2eb2 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -174,6 +174,6 @@ "Używaj adresu e-mail jako nazwy konta, ponieważ Android będzie używał nazwy konta do pola ORGANIZATORA dla wydarzeń stworzonych przez ciebie. Nie możesz posidać dwóch konta z taką samą nazwą. tylko do odczytu - Synchronizacja nie powiodła się + Synchronizacja nie powiodła się diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index b200c115..e551a863 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -113,5 +113,5 @@ "ORGANIZADOR de seus evento; requerido se você usa informações dos participantes" "Use seu endereço de e-mail como nome da conta porque o Android irá usar esta informação com ORGANIZADOR para eventos criados. Você não pode ter duas contas com o mesmo nome. apenas leitura - Falha na sincronização + Falha na sincronização diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index c9d2be6e..dd8890a2 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -123,6 +123,6 @@ "Используйте свой адрес email как имя аккаунта, так как Android будет использовать это имя как поле ОРГАНИЗАТОР для событий которые вы будете создавать. Вы не можете использовать одинаковое имя для разных аккаунтов. только чтение - Ошибка синхронизации + Ошибка синхронизации diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index e79c225c..b29e15bd 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -130,6 +130,6 @@ "Користите вашу е-адресу за назив налога јер Андроид користи назив налога за поље ОРГАНИЗАТОР за догађаје које направите. Не можете имати два налога истог назива. само-за-читање - Синхронизација није успела + Синхронизација није успела diff --git a/app/src/main/res/values-zh-rcn/strings.xml b/app/src/main/res/values-zh-rcn/strings.xml index 8816b2f3..e6cdd822 100644 --- a/app/src/main/res/values-zh-rcn/strings.xml +++ b/app/src/main/res/values-zh-rcn/strings.xml @@ -134,6 +134,6 @@ "请使用您的 E-mail 地址作为账户名,因为 Android 会将帐户名用于您创建的日程的参与者 (ORGANIZER) 项。您不能有两个重名的账户。 只读 - 同步失败 + 同步失败 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5e90267f..bd721084 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -197,7 +197,9 @@ Debug info - Synchronization of %s failed + Calendar synchronization failed (%s) + Address book synchronization failed (%s) + Task synchronization failed (%s) Error while %s Server error while %s Database error while %s diff --git a/dav4android b/dav4android index 9f722654..2083d075 160000 --- a/dav4android +++ b/dav4android @@ -1 +1 @@ -Subproject commit 9f722654e355e6e82b8b6e39a515b250feb111a9 +Subproject commit 2083d075d3b4a4b9ac0a930af1d019547d7dcf07 diff --git a/ical4android b/ical4android index 662c48c4..f2e48b97 160000 --- a/ical4android +++ b/ical4android @@ -1 +1 @@ -Subproject commit 662c48c40ad66e9a77f4f4792587dc04ecc4a74e +Subproject commit f2e48b97281064e27197953c178e8613a9413ed7 diff --git a/vcard4android b/vcard4android index 18b26fe4..38a01b7f 160000 --- a/vcard4android +++ b/vcard4android @@ -1 +1 @@ -Subproject commit 18b26fe48896b2683b1e04e8df4464f5ceacf8f3 +Subproject commit 38a01b7f18a81bb46ee33e2bae62a20d1239c17b