diff --git a/app/src/androidTest/assets/all-day-0sec.ics b/app/src/androidTest/assets/all-day-0sec.ics deleted file mode 100644 index 07679f29..00000000 --- a/app/src/androidTest/assets/all-day-0sec.ics +++ /dev/null @@ -1,11 +0,0 @@ -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//hacksw/handcal//NONSGML v1.0//EN -BEGIN:VEVENT -UID:all-day-0sec@example.com -DTSTAMP:20140101T000000Z -DTSTART;VALUE=DATE:19970714 -DTEND;VALUE=DATE:19970714 -SUMMARY:0 Sec Event -END:VEVENT -END:VCALENDAR \ No newline at end of file diff --git a/app/src/androidTest/assets/all-day-10days.ics b/app/src/androidTest/assets/all-day-10days.ics deleted file mode 100644 index 52e6dbdf..00000000 --- a/app/src/androidTest/assets/all-day-10days.ics +++ /dev/null @@ -1,11 +0,0 @@ -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//hacksw/handcal//NONSGML v1.0//EN -BEGIN:VEVENT -UID:all-day-10days@example.com -DTSTAMP:20140101T000000Z -DTSTART;VALUE=DATE:19970714 -DTEND;VALUE=DATE:19970724 -SUMMARY:All-Day 10 Days -END:VEVENT -END:VCALENDAR \ No newline at end of file diff --git a/app/src/androidTest/assets/all-day-1day.ics b/app/src/androidTest/assets/all-day-1day.ics deleted file mode 100644 index ead04361..00000000 --- a/app/src/androidTest/assets/all-day-1day.ics +++ /dev/null @@ -1,11 +0,0 @@ -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//hacksw/handcal//NONSGML v1.0//EN -BEGIN:VEVENT -UID:all-day-1day@example.com -DTSTAMP:20140101T000000Z -DTSTART;VALUE=DATE:19970714 -DTEND;VALUE=DATE:19970714 -SUMMARY:All-Day 1 Day -END:VEVENT -END:VCALENDAR \ No newline at end of file diff --git a/app/src/androidTest/assets/davdroid-logo-192.png b/app/src/androidTest/assets/davdroid-logo-192.png deleted file mode 100644 index 488e6fd1..00000000 Binary files a/app/src/androidTest/assets/davdroid-logo-192.png and /dev/null differ diff --git a/app/src/androidTest/assets/event-on-that-day.ics b/app/src/androidTest/assets/event-on-that-day.ics deleted file mode 100644 index 0ccbd4f3..00000000 --- a/app/src/androidTest/assets/event-on-that-day.ics +++ /dev/null @@ -1,11 +0,0 @@ -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//hacksw/handcal//NONSGML v1.0//EN -BEGIN:VEVENT -UID:event-on-that-day@example.com -DTSTAMP:19970714T170000Z -ORGANIZER;CN=John Doe:MAILTO:john.doe@example.com -DTSTART;VALUE=DATE:19970714 -SUMMARY:Bastille Day Party -END:VEVENT -END:VCALENDAR \ No newline at end of file diff --git a/app/src/androidTest/assets/impp.vcf b/app/src/androidTest/assets/impp.vcf deleted file mode 100644 index c08f9029..00000000 --- a/app/src/androidTest/assets/impp.vcf +++ /dev/null @@ -1,9 +0,0 @@ -BEGIN:VCARD -VERSION:3.0 -UID:2de59c6cc9 -PRODID:-//ownCloud//NONSGML Contacts 0.2.5//EN -REV:2013-12-08T00:04:30+00:00 -FN:test mctest -N:mctest;test;;; -IMPP;TYPE=WORK;X-SERVICE-TYPE=jabber:test-without-valid-scheme@test.tld -END:VCARD diff --git a/app/src/androidTest/assets/invalid-unknown-properties.vcf b/app/src/androidTest/assets/invalid-unknown-properties.vcf deleted file mode 100644 index 3fd77fce..00000000 --- a/app/src/androidTest/assets/invalid-unknown-properties.vcf +++ /dev/null @@ -1,5 +0,0 @@ -BEGIN:VCARD -VERSION:3.0 -FN:VCard with invalid unknown properties -X-UNKNOWN@PROPERTY:MUST-NOT_CONTAIN?OTHER*LETTERS; -END:VCARD \ No newline at end of file diff --git a/app/src/androidTest/assets/latin1.vcf b/app/src/androidTest/assets/latin1.vcf deleted file mode 100644 index 3d766427..00000000 --- a/app/src/androidTest/assets/latin1.vcf +++ /dev/null @@ -1,5 +0,0 @@ -BEGIN:VCARD -VERSION:3.0 -N:Äuçek;Özkan -FN:Özkan Äuçek -END:VCARD diff --git a/app/src/androidTest/assets/recurring-with-exception1.ics b/app/src/androidTest/assets/recurring-with-exception1.ics deleted file mode 100644 index 0382497e..00000000 --- a/app/src/androidTest/assets/recurring-with-exception1.ics +++ /dev/null @@ -1,17 +0,0 @@ -BEGIN:VCALENDAR -VERSION:2.0 -BEGIN:VEVENT -UID:fcb42e4d-bc6e-4499-97f0-6616a02da7bc -SUMMARY:Recurring event with one exception -RRULE:FREQ=DAILY;COUNT=5 -DTSTART;VALUE=DATE:20150501 -DTEND;VALUE=DATE:20150502 -END:VEVENT -BEGIN:VEVENT -UID:fcb42e4d-bc6e-4499-97f0-6616a02da7bc -RECURRENCE-ID;VALUE=DATE:20150503 -DTSTART;VALUE=DATE:20150503 -DTEND;VALUE=DATE:20150504 -SUMMARY:Another summary for the third day -END:VEVENT -END:VCALENDAR \ No newline at end of file diff --git a/app/src/androidTest/assets/reference-vcard3.vcf b/app/src/androidTest/assets/reference-vcard3.vcf deleted file mode 100644 index 2c80783a..00000000 --- a/app/src/androidTest/assets/reference-vcard3.vcf +++ /dev/null @@ -1,16 +0,0 @@ -BEGIN:VCARD -VERSION:3.0 -N:Gümp;Förrest;Mr. -FN:Förrest Gümp -ORG:Bubba Gump Shrimpß Co. -TITLE:Shrimp Man -PHOTO;VALUE=URL;TYPE=PNG:http://192.168.0.11:3000/assets/davdroid-logo-192.png -TEL;TYPE=WORK,VOICE:(111) 555-1212 -TEL;TYPE=HOME,VOICE:(404) 555-1212 -ADR;TYPE=WORK:;;100 Waters Edge;Baytown;LA;30314;United States of America -LABEL;TYPE=WORK:100 Waters Edge\nBaytown, LA 30314\nUnited States of America -ADR;TYPE=HOME:;;42 Plantation St.;Baytown;LA;30314;United States of America -LABEL;TYPE=HOME:42 Plantation St.\nBaytown, LA 30314\nUnited States of America -EMAIL;TYPE=PREF,INTERNET:forrestgump@example.com -REV:2008-04-24T19:52:43Z -END:VCARD diff --git a/app/src/androidTest/assets/test.random b/app/src/androidTest/assets/test.random deleted file mode 100644 index eb3e5b02..00000000 Binary files a/app/src/androidTest/assets/test.random and /dev/null differ diff --git a/app/src/androidTest/assets/two-line-description-without-crlf.ics b/app/src/androidTest/assets/two-line-description-without-crlf.ics deleted file mode 100644 index 03c8b62e..00000000 --- a/app/src/androidTest/assets/two-line-description-without-crlf.ics +++ /dev/null @@ -1,14 +0,0 @@ -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:Blabla -BEGIN:VEVENT -CLASS:PUBLIC -CREATED;VALUE=DATE-TIME:20131008T205713 -LAST-MODIFIED;VALUE=DATE-TIME:20131008T205740 -SUMMARY:online Anmeldung -DESCRIPTION:http://www.tgbornheim.de/index.php?sessionid=&page=&id=&sportce - ntergroup=&day=6 -UID:b99c41704b -DTSTART;VALUE=DATE-TIME;TZID=Europe/Berlin:20131019T060000 -END:VEVENT -END:VCALENDAR diff --git a/app/src/androidTest/assets/vcard3-sample1.vcf b/app/src/androidTest/assets/vcard3-sample1.vcf deleted file mode 100644 index f5c9f977..00000000 --- a/app/src/androidTest/assets/vcard3-sample1.vcf +++ /dev/null @@ -1,16 +0,0 @@ -BEGIN:VCARD -VERSION:3.0 -N:Gump;Forrest -FN:Forrest Gump -ORG:Bubba Gump Shrimp Co. -TITLE:Shrimp Man -PHOTO;VALUE=URL;TYPE=GIF:http://www.example.com/dir_photos/my_photo.gif -TEL;TYPE=WORK,VOICE:(111) 555-1212 -TEL;TYPE=HOME,VOICE:(404) 555-1212 -ADR;TYPE=WORK:;;100 Waters Edge;Baytown;LA;30314;United States of America -LABEL;TYPE=WORK:100 Waters Edge\nBaytown, LA 30314\nUnited States of America -ADR;TYPE=HOME:;;42 Plantation St.;Baytown;LA;30314;United States of America -LABEL;TYPE=HOME:42 Plantation St.\nBaytown, LA 30314\nUnited States of America -EMAIL;TYPE=PREF,INTERNET:forrestgump@example.com -REV:2008-04-24T19:52:43Z -END:VCARD \ No newline at end of file diff --git a/app/src/androidTest/assets/vienna-evolution.ics b/app/src/androidTest/assets/vienna-evolution.ics deleted file mode 100644 index 5f11911a..00000000 --- a/app/src/androidTest/assets/vienna-evolution.ics +++ /dev/null @@ -1,33 +0,0 @@ -BEGIN:VCALENDAR -PRODID:-//Ximian//NONSGML Evolution Calendar//EN -VERSION:2.0 -METHOD:PUBLISH -BEGIN:VTIMEZONE -TZID:/freeassociation.sourceforge.net/Tzfile/Europe/Vienna -X-LIC-LOCATION:Europe/Vienna -BEGIN:STANDARD -TZNAME:CET -DTSTART:19701027T030000 -RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 -TZOFFSETFROM:+0200 -TZOFFSETTO:+0100 -END:STANDARD -BEGIN:DAYLIGHT -TZNAME:CEST -DTSTART:19700331T020000 -RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3 -TZOFFSETFROM:+0100 -TZOFFSETTO:+0200 -END:DAYLIGHT -END:VTIMEZONE -BEGIN:VEVENT -UID:c252087c-7354-4722-aea9-0e7d86c01a25 -DTSTAMP:20130926T151211Z -SUMMARY:Test-Ereignis im schönen Wien -DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/Europe/Vienna:20131009T170000 -DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/Europe/Vienna:20131009T180000 -X-RADICALE-NAME:97929342-291a-434e-bf1a-fa1749bf99d0.ics -X-EVOLUTION-CALDAV-HREF:/radicale/rfc2822/default.ics/97929342-291a-434e-bf1a-fa1749bf99d0.ics -X-EVOLUTION-CALDAV-ETAG:\"-3264224243575339985\" -END:VEVENT -END:VCALENDAR diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 86085913..0a6e5a69 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,6 +20,7 @@ + diff --git a/app/src/main/java/at/bitfire/davdroid/HttpClient.java b/app/src/main/java/at/bitfire/davdroid/HttpClient.java index 2b0199ea..bfcab9a4 100644 --- a/app/src/main/java/at/bitfire/davdroid/HttpClient.java +++ b/app/src/main/java/at/bitfire/davdroid/HttpClient.java @@ -21,6 +21,8 @@ import com.squareup.okhttp.Response; import com.squareup.okhttp.internal.tls.OkHostnameVerifier; import com.squareup.okhttp.logging.HttpLoggingInterceptor; +import org.slf4j.Logger; + import java.io.IOException; import java.net.Proxy; import java.security.KeyManagementException; @@ -39,18 +41,7 @@ import de.duenndns.ssl.MemorizingTrustManager; import lombok.RequiredArgsConstructor; public class HttpClient extends OkHttpClient { - protected static final String HEADER_AUTHORIZATION = "Authorization"; - final static UserAgentInterceptor userAgentInterceptor = new UserAgentInterceptor(); - final static HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() { - @Override - public void log(String message) { - Constants.log.trace(message); - } - }); - static { - loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); - } static final String userAgent; static { @@ -58,12 +49,14 @@ public class HttpClient extends OkHttpClient { userAgent = "DAVdroid/" + BuildConfig.VERSION_NAME + " (" + date + "; dav4android) Android/" + Build.VERSION.RELEASE; } + final Logger log; final Context context; protected String username, password; - protected HttpClient(Context context) { + protected HttpClient(final Logger log, Context context) { super(); + this.log = (log != null) ? log : Constants.log; this.context = context; if (context != null) { @@ -90,12 +83,20 @@ public class HttpClient extends OkHttpClient { networkInterceptors().add(userAgentInterceptor); // enable verbose logs, if requested - if (Constants.log.isTraceEnabled()) - enableLogs(); + if (log.isTraceEnabled()) { + HttpLoggingInterceptor logger = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() { + @Override + public void log(String message) { + log.trace(message); + } + }); + logger.setLevel(HttpLoggingInterceptor.Level.BODY); + interceptors().add(logger); + } } - public HttpClient(Context context, String username, String password, boolean preemptive) { - this(context); + public HttpClient(Logger log, Context context, String username, String password, boolean preemptive) { + this(log, context); // authentication this.username = username; @@ -113,8 +114,8 @@ public class HttpClient extends OkHttpClient { * @param client user name and password from this client will be used * @param host authentication will be restricted to this host */ - public HttpClient(HttpClient client, String host) { - this(client.context); + public HttpClient(Logger log, HttpClient client, String host) { + this(log, client.context); username = client.username; password = client.password; @@ -123,12 +124,7 @@ public class HttpClient extends OkHttpClient { // for testing (mock server doesn't need auth) protected HttpClient() { - this(null, null, null, false); - } - - - protected void enableLogs() { - interceptors().add(loggingInterceptor); + this(null, null, null, null, false); } @@ -142,7 +138,6 @@ public class HttpClient extends OkHttpClient { } } - @RequiredArgsConstructor static class PreemptiveAuthenticationInterceptor implements Interceptor { final String username, password; diff --git a/app/src/main/java/at/bitfire/davdroid/log/CustomLogger.java b/app/src/main/java/at/bitfire/davdroid/log/CustomLogger.java new file mode 100644 index 00000000..3e83fa23 --- /dev/null +++ b/app/src/main/java/at/bitfire/davdroid/log/CustomLogger.java @@ -0,0 +1,348 @@ +/* + * 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.log; + +import org.slf4j.Logger; +import org.slf4j.Marker; + +/** + * A DAVdroid logger base class that wraps all calls around some standard log() calls. + * @throws UnsupportedOperationException for all methods with Marker + * arguments (as Markers are not used by DAVdroid logging). + */ +public abstract class CustomLogger implements Logger { + + private static final String + PREFIX_ERROR = "[error] ", + PREFIX_WARN = "[warn ] ", + PREFIX_INFO = "[info ] ", + PREFIX_DEBUG = "[debug] ", + PREFIX_TRACE = "[trace] "; + + protected boolean verbose; + + protected abstract void log(String prefix, String msg); + protected abstract void log(String prefix, String format, Object arg); + protected abstract void log(String prefix, String format, Object arg1, Object arg2); + protected abstract void log(String prefix, String format, Object... args); + protected abstract void log(String prefix, String msg, Throwable t); + + + // STANDARD CALLS + + @Override + public boolean isTraceEnabled() { + return verbose; + } + + @Override + public void trace(String msg) { + if (verbose) + log(PREFIX_TRACE, msg); + } + + @Override + public void trace(String format, Object arg) { + if (verbose) + log(PREFIX_TRACE, format, arg); + } + + @Override + public void trace(String format, Object arg1, Object arg2) { + if (verbose) log(PREFIX_TRACE, format, arg1, arg2); + } + + @Override + public void trace(String format, Object... arguments) { + if (verbose) + log(PREFIX_TRACE, format, arguments); + } + + @Override + public void trace(String msg, Throwable t) { + if (verbose) + log(PREFIX_TRACE, msg, t); + } + + + @Override + public boolean isDebugEnabled() { + return true; + } + + @Override + public void debug(String msg) { + log(PREFIX_DEBUG, msg); + } + + @Override + public void debug(String format, Object arg) { + log(PREFIX_DEBUG, format, arg); + } + + @Override + public void debug(String format, Object arg1, Object arg2) { + log(PREFIX_DEBUG, format, arg1, arg2); + } + + @Override + public void debug(String format, Object... arguments) { + log(PREFIX_DEBUG, format, arguments); + } + + @Override + public void debug(String msg, Throwable t) { + log(PREFIX_DEBUG, msg, t); + } + + + @Override + public boolean isInfoEnabled() { + return true; + } + + @Override + public void info(String msg) { + log(PREFIX_INFO, msg); + } + + @Override + public void info(String format, Object arg) { + log(PREFIX_INFO, format, arg); + } + + @Override + public void info(String format, Object arg1, Object arg2) { + log(PREFIX_INFO, format, arg1, arg2); + } + + @Override + public void info(String format, Object... arguments) { + log(PREFIX_INFO, format, arguments); + } + + @Override + public void info(String msg, Throwable t) { + log(PREFIX_INFO, msg, t); + } + + + @Override + public boolean isWarnEnabled() { + return true; + } + + @Override + public void warn(String msg) { + log(PREFIX_WARN, msg); + } + + @Override + public void warn(String format, Object arg) { + log(PREFIX_WARN, format, arg); + } + + @Override + public void warn(String format, Object... arguments) { + log(PREFIX_WARN, format, arguments); + } + + @Override + public void warn(String format, Object arg1, Object arg2) { + log(PREFIX_WARN, format, arg1, arg2); + } + + @Override + public void warn(String msg, Throwable t) { + log(PREFIX_WARN, msg, t); + } + + @Override + public boolean isErrorEnabled() { + return true; + } + + @Override + public void error(String msg) { + log(PREFIX_ERROR, msg); + } + + @Override + public void error(String format, Object arg) { + log(PREFIX_ERROR, format, arg); + } + + @Override + public void error(String format, Object arg1, Object arg2) { + log(PREFIX_ERROR, format, arg1, arg2); + } + + @Override + public void error(String format, Object... arguments) { + log(PREFIX_ERROR, format, arguments); + } + + @Override + public void error(String msg, Throwable t) { + log(PREFIX_ERROR, msg, t); + } + + + // CALLS WITH MARKER + + @Override + public boolean isTraceEnabled(Marker marker) { + throw new UnsupportedOperationException(); + } + + @Override + public void trace(Marker marker, String msg) { + throw new UnsupportedOperationException(); + } + + @Override + public void trace(Marker marker, String format, Object arg) { + throw new UnsupportedOperationException(); + } + + @Override + public void trace(Marker marker, String format, Object arg1, Object arg2) { + throw new UnsupportedOperationException(); + } + + @Override + public void trace(Marker marker, String format, Object... argArray) { + throw new UnsupportedOperationException(); + } + + @Override + public void trace(Marker marker, String msg, Throwable t) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isDebugEnabled(Marker marker) { + throw new UnsupportedOperationException(); + } + + @Override + public void debug(Marker marker, String msg) { + throw new UnsupportedOperationException(); + } + + @Override + public void debug(Marker marker, String format, Object arg) { + throw new UnsupportedOperationException(); + } + + @Override + public void debug(Marker marker, String format, Object arg1, Object arg2) { + throw new UnsupportedOperationException(); + } + + @Override + public void debug(Marker marker, String format, Object... arguments) { + throw new UnsupportedOperationException(); + } + + @Override + public void debug(Marker marker, String msg, Throwable t) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isInfoEnabled(Marker marker) { + throw new UnsupportedOperationException(); + } + + @Override + public void info(Marker marker, String msg) { + throw new UnsupportedOperationException(); + } + + @Override + public void info(Marker marker, String format, Object arg) { + throw new UnsupportedOperationException(); + } + + @Override + public void info(Marker marker, String format, Object arg1, Object arg2) { + throw new UnsupportedOperationException(); + } + + @Override + public void info(Marker marker, String format, Object... arguments) { + throw new UnsupportedOperationException(); + } + + @Override + public void info(Marker marker, String msg, Throwable t) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isWarnEnabled(Marker marker) { + throw new UnsupportedOperationException(); + } + + @Override + public void warn(Marker marker, String msg) { + throw new UnsupportedOperationException(); + } + + @Override + public void warn(Marker marker, String format, Object arg) { + throw new UnsupportedOperationException(); + } + + @Override + public void warn(Marker marker, String format, Object arg1, Object arg2) { + throw new UnsupportedOperationException(); + } + + @Override + public void warn(Marker marker, String format, Object... arguments) { + throw new UnsupportedOperationException(); + } + + @Override + public void warn(Marker marker, String msg, Throwable t) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isErrorEnabled(Marker marker) { + throw new UnsupportedOperationException(); + } + + @Override + public void error(Marker marker, String msg) { + throw new UnsupportedOperationException(); + } + + @Override + public void error(Marker marker, String format, Object arg) { + throw new UnsupportedOperationException(); + } + + @Override + public void error(Marker marker, String format, Object arg1, Object arg2) { + throw new UnsupportedOperationException(); + } + + @Override + public void error(Marker marker, String format, Object... arguments) { + throw new UnsupportedOperationException(); + } + + @Override + public void error(Marker marker, String msg, Throwable t) { + throw new UnsupportedOperationException(); + } +} diff --git a/app/src/main/java/at/bitfire/davdroid/log/ExternalFileLogger.java b/app/src/main/java/at/bitfire/davdroid/log/ExternalFileLogger.java new file mode 100644 index 00000000..23ba45a7 --- /dev/null +++ b/app/src/main/java/at/bitfire/davdroid/log/ExternalFileLogger.java @@ -0,0 +1,75 @@ +/* + * 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.log; + +import android.content.Context; + +import org.apache.commons.lang3.exception.ExceptionUtils; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; + +import lombok.Getter; + +public class ExternalFileLogger extends CustomLogger implements Closeable { + + @Getter protected final String name; + + protected final PrintWriter writer; + + public ExternalFileLogger(Context context, String fileName, boolean verbose) throws IOException { + this.verbose = verbose; + + File dir = context.getExternalFilesDir(null); + if (dir == null) + throw new IOException("External media not available for log creation"); + + File log = new File(dir, name = fileName); + writer = new PrintWriter(log); + } + + @Override + public void close() throws IOException { + writer.close(); + } + + @Override + protected void log(String prefix, String msg) { + writer.write(prefix + msg + "\n"); + } + + @Override + protected void log(String prefix, String format, Object arg) { + writer.write(prefix + format.replace("{}", arg.toString()) + "\n"); + } + + @Override + protected void log(String prefix, String format, Object arg1, Object arg2) { + writer.write(prefix + format.replaceFirst("\\{\\}", arg1.toString()).replaceFirst("\\{\\}", arg2.toString()) + "\n"); + } + + @Override + protected void log(String prefix, String format, Object... args) { + String message = prefix; + for (Object arg : args) + format.replaceFirst("\\{\\}", arg.toString()); + writer.write(prefix + format + "\n"); + } + + @Override + protected void log(String prefix, String msg, Throwable t) { + writer.write(prefix + msg + " - EXCEPTION:"); + t.printStackTrace(writer); + writer.write("CAUSED BY:\n"); + ExceptionUtils.printRootCauseStackTrace(t, writer); + } + +} 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 fdf7327d..4c499189 100644 --- a/app/src/main/java/at/bitfire/davdroid/resource/DavResourceFinder.java +++ b/app/src/main/java/at/bitfire/davdroid/resource/DavResourceFinder.java @@ -12,6 +12,7 @@ import android.text.TextUtils; import com.squareup.okhttp.HttpUrl; +import org.slf4j.Logger; import org.xbill.DNS.Lookup; import org.xbill.DNS.Record; import org.xbill.DNS.SRVRecord; @@ -57,7 +58,8 @@ public class DavResourceFinder { @Override public String toString() { return name; } }; - protected Context context; + protected final Logger log; + protected final Context context; protected final HttpClient httpClient; protected final ServerInfo serverInfo; @@ -67,11 +69,12 @@ public class DavResourceFinder { taskLists = new HashMap<>(); - public DavResourceFinder(Context context, ServerInfo serverInfo) { + public DavResourceFinder(Logger log, Context context, ServerInfo serverInfo) { + this.log = log; this.context = context; this.serverInfo = serverInfo; - httpClient = new HttpClient(context, serverInfo.getUserName(), serverInfo.getPassword(), serverInfo.authPreemptive); + httpClient = new HttpClient(log, context, serverInfo.getUserName(), serverInfo.getPassword(), serverInfo.authPreemptive); } public void findResources() throws URISyntaxException, IOException, HttpException, DavException { @@ -101,7 +104,7 @@ public class DavResourceFinder { 2. user-given URL has a calendar-home-set property (i.e. is a principal URL) */ Constants.log.info("Check whether user-given URL is a calendar collection and/or contains and/or has "); - DavResource davBase = new DavResource(httpClient, userURL); + DavResource davBase = new DavResource(log, httpClient, userURL); try { if (service == Service.CALDAV) { davBase.propfind(0, @@ -158,18 +161,23 @@ public class DavResourceFinder { CurrentUserPrincipal currentUserPrincipal = (CurrentUserPrincipal)davBase.properties.get(CurrentUserPrincipal.NAME); if (currentUserPrincipal != null && currentUserPrincipal.href != null) principalUrl = davBase.location.resolve(currentUserPrincipal.href); - - if (principalUrl == null) { - Constants.log.info("User-given URL doesn't contain , trying /.well-known/" + service.name); - principalUrl = getCurrentUserPrincipal(userURL.resolve("/.well-known/" + service.name)); - } } } catch(IOException|HttpException|DavException e) { - Constants.log.debug("Couldn't find ", e); + Constants.log.debug("Couldn't find at user-given URL", e); } - // try service discovery with "domain" = user-given host name - domain = baseURI.getHost(); + if (principalUrl == null) + try { + Constants.log.info("User-given URL doesn't contain , trying /.well-known/" + service.name); + principalUrl = getCurrentUserPrincipal(userURL.resolve("/.well-known/" + service.name)); + } catch(IOException|HttpException e) { + Constants.log.debug("Couldn't determine from well-known " + service + " path", e); + } + + if (principalUrl == null) + // still no principal URL, try service discovery with "domain" = user-given host name + domain = baseURI.getHost(); + } else if ("mailto".equals(baseURI.getScheme())) { String mailbox = baseURI.getSchemeSpecificPart(); @@ -190,7 +198,7 @@ public class DavResourceFinder { if (principalUrl != null) { Constants.log.info("Principal URL=" + principalUrl + ", getting "); try { - DavResource principal = new DavResource(httpClient, principalUrl); + DavResource principal = new DavResource(log, httpClient, principalUrl); if (service == Service.CALDAV) { principal.propfind(0, CalendarHomeSet.NAME); @@ -226,7 +234,7 @@ public class DavResourceFinder { if (service == Service.CALDAV) try { Constants.log.info("Listing calendar collections in home set " + url); - DavResource homeSet = new DavResource(httpClient, url); + DavResource homeSet = new DavResource(log, httpClient, url); homeSet.propfind(1, SupportedCalendarComponentSet.NAME, ResourceType.NAME, DisplayName.NAME, CurrentUserPrivilegeSet.NAME, CalendarColor.NAME, CalendarDescription.NAME, CalendarTimezone.NAME); @@ -242,7 +250,7 @@ public class DavResourceFinder { else if (service == Service.CARDDAV) try { Constants.log.info("Listing address books in home set " + url); - DavResource homeSet = new DavResource(httpClient, url); + DavResource homeSet = new DavResource(log, httpClient, url); homeSet.propfind(1, ResourceType.NAME, DisplayName.NAME, CurrentUserPrivilegeSet.NAME, AddressbookDescription.NAME); // home set should not be an address book, but some servers have only one address book and it's the home set @@ -361,45 +369,40 @@ public class DavResourceFinder { * @param service service to discover (CALDAV or CARDDAV) * @return principal URL, or null if none found */ - protected HttpUrl discoverPrincipalUrl(String domain, Service service) { + protected HttpUrl discoverPrincipalUrl(String domain, Service service) throws IOException, HttpException, DavException { String scheme = null; String fqdn = null; Integer port = null; List paths = new LinkedList<>(); // there may be multiple paths to try - try { - final String query = "_" + service.name + "s._tcp." + domain; - Constants.log.debug("Looking up SRV records for " + query); - Record[] records = new Lookup(query, Type.SRV).run(); + final String query = "_" + service.name + "s._tcp." + domain; + Constants.log.debug("Looking up SRV records for " + query); + Record[] records = new Lookup(query, Type.SRV).run(); + if (records != null && records.length >= 1) { + // choose SRV record to use (query may return multiple SRV records) + SRVRecord srv = selectSRVRecord(records); + + scheme = "https"; + fqdn = srv.getTarget().toString(true); + port = srv.getPort(); + Constants.log.info("Found " + service + " service: fqdn=" + fqdn + ", port=" + port); + + // look for TXT record too (for initial context path) + records = new Lookup(domain, Type.TXT).run(); if (records != null && records.length >= 1) { - // choose SRV record to use (query may return multiple SRV records) - SRVRecord srv = selectSRVRecord(records); - - scheme = "https"; - fqdn = srv.getTarget().toString(true); - port = srv.getPort(); - Constants.log.info("Found " + service + " service: fqdn=" + fqdn + ", port=" + port); - - // look for TXT record too (for initial context path) - records = new Lookup(domain, Type.TXT).run(); - if (records != null && records.length >= 1) { - TXTRecord txt = (TXTRecord)records[0]; - for (String segment : (String[])txt.getStrings().toArray(new String[0])) - if (segment.startsWith("path=")) { - paths.add(segment.substring(5)); - Constants.log.info("Found TXT record; initial context path=" + paths); - break; - } - } - - // if there's TXT record if it it's wrong, try well-known - paths.add("/.well-known/" + service.name); - // if this fails, too, try "/" - paths.add("/"); + TXTRecord txt = (TXTRecord)records[0]; + for (String segment : (String[])txt.getStrings().toArray(new String[0])) + if (segment.startsWith("path=")) { + paths.add(segment.substring(5)); + Constants.log.info("Found TXT record; initial context path=" + paths); + break; + } } - } catch (IOException e) { - Constants.log.debug("SRV/TXT record discovery failed", e); - return null; + + // if there's TXT record if it it's wrong, try well-known + paths.add("/.well-known/" + service.name); + // if this fails, too, try "/" + paths.add("/"); } for (String path : paths) { @@ -424,20 +427,16 @@ public class DavResourceFinder { * @param url URL to query with PROPFIND (Depth: 0) * @return current-user-principal URL, or null if none */ - protected HttpUrl getCurrentUserPrincipal(HttpUrl url) { - try { - DavResource dav = new DavResource(httpClient, url); - dav.propfind(0, CurrentUserPrincipal.NAME); - CurrentUserPrincipal currentUserPrincipal = (CurrentUserPrincipal) dav.properties.get(CurrentUserPrincipal.NAME); - if (currentUserPrincipal != null && currentUserPrincipal.href != null) { - HttpUrl principal = url.resolve(currentUserPrincipal.href); - if (principal != null) { - Constants.log.info("Found current-user-principal: " + principal); - return principal; - } + protected HttpUrl getCurrentUserPrincipal(HttpUrl url) throws IOException, HttpException, DavException { + DavResource dav = new DavResource(log, httpClient, url); + dav.propfind(0, CurrentUserPrincipal.NAME); + CurrentUserPrincipal currentUserPrincipal = (CurrentUserPrincipal) dav.properties.get(CurrentUserPrincipal.NAME); + if (currentUserPrincipal != null && currentUserPrincipal.href != null) { + HttpUrl principal = url.resolve(currentUserPrincipal.href); + if (principal != null) { + Constants.log.info("Found current-user-principal: " + principal); + return principal; } - } catch(IOException|HttpException|DavException e) { - Constants.log.debug("PROPFIND for current-user-principal on " + url + " failed", e); } return null; } diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/AccountSettings.java b/app/src/main/java/at/bitfire/davdroid/syncadapter/AccountSettings.java index 4f705a3e..2ddb100d 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/AccountSettings.java +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/AccountSettings.java @@ -47,6 +47,8 @@ public class AccountSettings { KEY_USERNAME = "user_name", KEY_AUTH_PREEMPTIVE = "auth_preemptive", + KEY_LOG_TO_EXTERNAL_FILE = "log_external_file", + KEY_LOG_VERBOSE = "log_verbose", KEY_LAST_ANDROID_VERSION = "last_android_version"; public final static long SYNC_INTERVAL_MANUALLY = -1; @@ -84,9 +86,8 @@ public class AccountSettings { showNotification(Constants.NOTIFICATION_ANDROID_VERSION_UPDATED, context.getString(R.string.settings_android_update_title), context.getString(R.string.settings_android_update_description)); - - accountManager.setUserData(account, KEY_LAST_ANDROID_VERSION, String.valueOf(Build.VERSION.SDK_INT)); } + accountManager.setUserData(account, KEY_LAST_ANDROID_VERSION, String.valueOf(Build.VERSION.SDK_INT)); } } @@ -119,18 +120,23 @@ public class AccountSettings { // authentication settings - public String getUserName() { - return accountManager.getUserData(account, KEY_USERNAME); - } - public void setUserName(String userName) { accountManager.setUserData(account, KEY_USERNAME, userName); } + public String username() { return accountManager.getUserData(account, KEY_USERNAME); } + public void username(String userName) { accountManager.setUserData(account, KEY_USERNAME, userName); } - public String getPassword() { - return accountManager.getPassword(account); - } - public void setPassword(String password) { accountManager.setPassword(account, password); } + public String password() { return accountManager.getPassword(account); } + public void password(String password) { accountManager.setPassword(account, password); } - public boolean getPreemptiveAuth() { return Boolean.parseBoolean(accountManager.getUserData(account, KEY_AUTH_PREEMPTIVE)); } - public void setPreemptiveAuth(boolean preemptive) { accountManager.setUserData(account, KEY_AUTH_PREEMPTIVE, Boolean.toString(preemptive)); } + public boolean preemptiveAuth() { return Boolean.parseBoolean(accountManager.getUserData(account, KEY_AUTH_PREEMPTIVE)); } + public void preemptiveAuth(boolean preemptive) { accountManager.setUserData(account, KEY_AUTH_PREEMPTIVE, Boolean.toString(preemptive)); } + + + // logging settings + + public boolean logToExternalFile() { return Boolean.parseBoolean(accountManager.getUserData(account, KEY_LOG_TO_EXTERNAL_FILE)); } + public void logToExternalFile(boolean newValue) { accountManager.setUserData(account, KEY_LOG_TO_EXTERNAL_FILE, Boolean.toString(newValue)); } + + public boolean logVerbose() { return Boolean.parseBoolean(accountManager.getUserData(account, KEY_LOG_VERBOSE)); } + public void logVerbose(boolean newValue) { accountManager.setUserData(account, KEY_LOG_VERBOSE, Boolean.toString(newValue)); } // sync. settings @@ -229,7 +235,7 @@ public class AccountSettings { } private void update_1_2() throws ContactsStorageException { - /* - KEY_ADDRESSBOOK_URL ("addressbook_url"),, + /* - KEY_ADDRESSBOOK_URL ("addressbook_url"), - KEY_ADDRESSBOOK_CTAG ("addressbook_ctag"), - KEY_ADDRESSBOOK_VCARD_VERSION ("addressbook_vcard_version") are not used anymore (now stored in ContactsContract.SyncState) - KEY_LAST_ANDROID_VERSION ("last_android_version") has been added @@ -237,22 +243,25 @@ public class AccountSettings { // move previous address book info to ContactsContract.SyncState @Cleanup("release") ContentProviderClient provider = context.getContentResolver().acquireContentProviderClient(ContactsContract.AUTHORITY); - if (provider != null) { - LocalAddressBook addr = new LocalAddressBook(account, provider); + if (provider == null) + throw new ContactsStorageException("Couldn't access Contacts provider"); - String url = accountManager.getUserData(account, "addressbook_url"); - if (!TextUtils.isEmpty(url)) - addr.setURL(url); - accountManager.setUserData(account, "addressbook_url", null); + LocalAddressBook addr = new LocalAddressBook(account, provider); - String cTag = accountManager.getUserData(account, "addressbook_ctag"); - if (!TextUtils.isEmpty(cTag)) - addr.setCTag(cTag); - accountManager.setUserData(account, "addressbook_ctag", null); - } + // until now, ContactsContract.Settings.UNGROUPED_VISIBLE was not set explicitly + ContentValues values = new ContentValues(); + values.put(ContactsContract.Settings.UNGROUPED_VISIBLE, 1); + addr.updateSettings(values); - // store current Android version - accountManager.setUserData(account, KEY_LAST_ANDROID_VERSION, String.valueOf(Build.VERSION.SDK_INT)); + String url = accountManager.getUserData(account, "addressbook_url"); + if (!TextUtils.isEmpty(url)) + addr.setURL(url); + accountManager.setUserData(account, "addressbook_url", null); + + String cTag = accountManager.getUserData(account, "addressbook_ctag"); + if (!TextUtils.isEmpty(cTag)) + addr.setCTag(cTag); + accountManager.setUserData(account, "addressbook_ctag", null); accountManager.setUserData(account, KEY_SETTINGS_VERSION, "2"); } 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 61446e23..c691b345 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.java +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.java @@ -74,7 +74,7 @@ public class CalendarSyncManager extends SyncManager { Thread.currentThread().setContextClassLoader(context.getClassLoader()); // required for ical4j collectionURL = HttpUrl.parse(localCalendar().getName()); - davCollection = new DavCalendar(httpClient, collectionURL); + davCollection = new DavCalendar(log, httpClient, collectionURL); } @Override @@ -90,7 +90,7 @@ public class CalendarSyncManager extends SyncManager { int color = (pColor != null && pColor.color != null) ? pColor.color : LocalCalendar.defaultColor; ContentValues values = new ContentValues(2); - Constants.log.info("Setting new calendar name \"" + displayName + "\" and color 0x" + Integer.toHexString(color)); + log.info("Setting new calendar name \"" + displayName + "\" and color 0x" + Integer.toHexString(color)); values.put(Calendars.CALENDAR_DISPLAY_NAME, displayName); values.put(Calendars.CALENDAR_COLOR, color); localCalendar().update(values); @@ -119,20 +119,20 @@ public class CalendarSyncManager extends SyncManager { remoteResources = new HashMap<>(davCollection.members.size()); for (DavResource vCard : davCollection.members) { String fileName = vCard.fileName(); - Constants.log.debug("Found remote VEVENT: " + fileName); + log.debug("Found remote VEVENT: " + fileName); remoteResources.put(fileName, vCard); } } @Override protected void downloadRemote() throws IOException, HttpException, DavException, CalendarStorageException { - Constants.log.info("Downloading " + toDownload.size() + " events (" + MAX_MULTIGET + " at once)"); + log.info("Downloading " + toDownload.size() + " events (" + MAX_MULTIGET + " at once)"); // download new/updated iCalendars from server for (DavResource[] bunch : ArrayUtils.partition(toDownload.toArray(new DavResource[toDownload.size()]), MAX_MULTIGET)) { if (Thread.interrupted()) return; - Constants.log.info("Downloading " + StringUtils.join(bunch, ", ")); + log.info("Downloading " + StringUtils.join(bunch, ", ")); if (bunch.length == 1) { // only one contact, use GET @@ -190,7 +190,7 @@ public class CalendarSyncManager extends SyncManager { try { events = Event.fromStream(stream, charset); } catch (InvalidCalendarException e) { - Constants.log.error("Received invalid iCalendar, ignoring"); + log.error("Received invalid iCalendar, ignoring"); return; } @@ -200,18 +200,18 @@ public class CalendarSyncManager extends SyncManager { // delete local event, if it exists LocalEvent localEvent = (LocalEvent)localResources.get(fileName); if (localEvent != null) { - Constants.log.info("Updating " + fileName + " in local calendar"); + log.info("Updating " + fileName + " in local calendar"); localEvent.setETag(eTag); localEvent.update(newData); syncResult.stats.numUpdates++; } else { - Constants.log.info("Adding " + fileName + " to local calendar"); + log.info("Adding " + fileName + " to local calendar"); localEvent = new LocalEvent(localCalendar(), newData, fileName, eTag); localEvent.add(); syncResult.stats.numInserts++; } } else - Constants.log.error("Received VCALENDAR with not exactly one VEVENT with UID, but without RECURRENCE-ID; ignoring " + fileName); + log.error("Received VCALENDAR with not exactly one VEVENT with UID, but without RECURRENCE-ID; ignoring " + fileName); } } 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 c7e85efe..8b76ab1f 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.java +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.java @@ -83,7 +83,7 @@ public class ContactsSyncManager extends SyncManager { if (url == null) throw new ContactsStorageException("Couldn't get address book URL"); collectionURL = HttpUrl.parse(url); - davCollection = new DavAddressBook(httpClient, collectionURL); + davCollection = new DavAddressBook(log, httpClient, collectionURL); processChangedGroups(); } @@ -98,7 +98,7 @@ public class ContactsSyncManager extends SyncManager { for (MediaType type : supportedAddressData.types) if ("text/vcard; version=4.0".equalsIgnoreCase(type.toString())) hasVCard4 = true; - Constants.log.info("Server advertises VCard/4 support: " + hasVCard4); + log.info("Server advertises VCard/4 support: " + hasVCard4); } @Override @@ -118,7 +118,7 @@ public class ContactsSyncManager extends SyncManager { davAddressBook().addressbookQuery(); } catch(HttpException e) { if (e.status/100 == 4) { - Constants.log.warn("Server error on REPORT addressbook query, falling back to PROPFIND", e); + log.warn("Server error on REPORT addressbook query, falling back to PROPFIND", e); davAddressBook().propfind(1, GetETag.NAME); } } @@ -126,14 +126,14 @@ public class ContactsSyncManager extends SyncManager { remoteResources = new HashMap<>(davCollection.members.size()); for (DavResource vCard : davCollection.members) { String fileName = vCard.fileName(); - Constants.log.debug("Found remote VCard: " + fileName); + log.debug("Found remote VCard: " + fileName); remoteResources.put(fileName, vCard); } } @Override protected void downloadRemote() throws IOException, HttpException, DavException, ContactsStorageException { - Constants.log.info("Downloading " + toDownload.size() + " contacts (" + MAX_MULTIGET + " at once)"); + log.info("Downloading " + toDownload.size() + " contacts (" + MAX_MULTIGET + " at once)"); // prepare downloader which may be used to download external resource like contact photos Contact.Downloader downloader = new ResourceDownloader(httpClient, collectionURL); @@ -143,7 +143,7 @@ public class ContactsSyncManager extends SyncManager { if (Thread.interrupted()) return; - Constants.log.info("Downloading " + StringUtils.join(bunch, ", ")); + log.info("Downloading " + StringUtils.join(bunch, ", ")); if (bunch.length == 1) { // only one contact, use GET @@ -202,7 +202,7 @@ public class ContactsSyncManager extends SyncManager { // groups with DELETED=1: remove group finally for (LocalGroup group : addressBook.getDeletedGroups()) { long groupId = group.getId(); - Constants.log.debug("Finally removing group #" + groupId); + 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); @@ -212,7 +212,7 @@ public class ContactsSyncManager extends SyncManager { // 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"); + log.debug("Marking members of modified group #" + groupId + " as dirty"); addressBook.markMembersDirty(groupId); group.clearDirty(); } @@ -226,32 +226,32 @@ public class ContactsSyncManager extends SyncManager { // update local contact, if it exists LocalContact localContact = (LocalContact)localResources.get(fileName); if (localContact != null) { - Constants.log.info("Updating " + fileName + " in local address book"); + log.info("Updating " + fileName + " in local address book"); localContact.eTag = eTag; localContact.update(newData); syncResult.stats.numUpdates++; } else { - Constants.log.info("Adding " + fileName + " to local address book"); + log.info("Adding " + fileName + " to local address book"); localContact = new LocalContact(localAddressBook(), newData, fileName, eTag); localContact.add(); syncResult.stats.numInserts++; } } else - Constants.log.error("Received VCard with not exactly one VCARD, ignoring " + fileName); + log.error("Received VCard with not exactly one VCARD, ignoring " + fileName); } // downloader helper class @RequiredArgsConstructor - private static class ResourceDownloader implements Contact.Downloader { + private class ResourceDownloader implements Contact.Downloader { final HttpClient httpClient; final HttpUrl baseUrl; @Override public byte[] download(String url, String accepts) { HttpUrl httpUrl = HttpUrl.parse(url); - HttpClient resourceClient = new HttpClient(httpClient, httpUrl.host()); + HttpClient resourceClient = new HttpClient(log, httpClient, httpUrl.host()); try { Response response = resourceClient.newCall(new Request.Builder() .get() @@ -264,10 +264,10 @@ public class ContactsSyncManager extends SyncManager { if (response.isSuccessful() && stream != null) { return IOUtils.toByteArray(stream); } else - Constants.log.error("Couldn't download external resource"); + log.error("Couldn't download external resource"); } } catch(IOException e) { - Constants.log.error("Couldn't download external resource", e); + log.error("Couldn't download external resource", e); } 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 57b599d0..ad2e8f0c 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.java +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.java @@ -1,4 +1,4 @@ -/* + /* * 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 @@ -22,6 +22,8 @@ import android.text.TextUtils; import com.squareup.okhttp.HttpUrl; import com.squareup.okhttp.RequestBody; +import org.slf4j.Logger; + import java.io.IOException; import java.util.Date; import java.util.HashMap; @@ -42,6 +44,7 @@ import at.bitfire.dav4android.property.GetETag; import at.bitfire.davdroid.Constants; import at.bitfire.davdroid.HttpClient; import at.bitfire.davdroid.R; +import at.bitfire.davdroid.log.ExternalFileLogger; import at.bitfire.davdroid.resource.LocalCollection; import at.bitfire.davdroid.resource.LocalResource; import at.bitfire.davdroid.ui.DebugInfoActivity; @@ -75,6 +78,8 @@ abstract public class SyncManager { protected final AccountSettings settings; protected LocalCollection localCollection; + protected Logger log; + protected final HttpClient httpClient; protected HttpUrl collectionURL; protected DavResource davCollection; @@ -101,9 +106,19 @@ abstract public class SyncManager { this.authority = authority; this.syncResult = syncResult; - // get account settings and generate httpClient + // get account settings and log to file (if requested) settings = new AccountSettings(context, account); - httpClient = new HttpClient(context, settings.getUserName(), settings.getPassword(), settings.getPreemptiveAuth()); + try { + if (settings.logToExternalFile()) + log = new ExternalFileLogger(context, "davdroid-SyncManager-" + account.name + "-" + authority + ".txt", settings.logVerbose()); + } catch(IOException e) { + log.error("Couldn't log to external file", e); + } + if (log == null) + log = Constants.log; + + // create HttpClient with given logger + httpClient = new HttpClient(log, context, settings.username(), settings.password(), settings.preemptiveAuth()); // dismiss previous error notifications notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); @@ -115,60 +130,60 @@ abstract public class SyncManager { public void performSync() { int syncPhase = SYNC_PHASE_PREPARE; try { - Constants.log.info("Preparing synchronization"); + log.info("Preparing synchronization"); prepare(); if (Thread.interrupted()) return; syncPhase = SYNC_PHASE_QUERY_CAPABILITIES; - Constants.log.info("Querying capabilities"); + log.info("Querying capabilities"); queryCapabilities(); syncPhase = SYNC_PHASE_PROCESS_LOCALLY_DELETED; - Constants.log.info("Processing locally deleted entries"); + log.info("Processing locally deleted entries"); processLocallyDeleted(); if (Thread.interrupted()) return; syncPhase = SYNC_PHASE_PREPARE_DIRTY; - Constants.log.info("Locally preparing dirty entries"); + log.info("Locally preparing dirty entries"); prepareDirty(); syncPhase = SYNC_PHASE_UPLOAD_DIRTY; - Constants.log.info("Uploading dirty entries"); + log.info("Uploading dirty entries"); uploadDirty(); syncPhase = SYNC_PHASE_CHECK_SYNC_STATE; - Constants.log.info("Checking sync state"); + log.info("Checking sync state"); if (checkSyncState()) { syncPhase = SYNC_PHASE_LIST_LOCAL; - Constants.log.info("Listing local entries"); + log.info("Listing local entries"); listLocal(); if (Thread.interrupted()) return; syncPhase = SYNC_PHASE_LIST_REMOTE; - Constants.log.info("Listing remote entries"); + log.info("Listing remote entries"); listRemote(); if (Thread.interrupted()) return; syncPhase = SYNC_PHASE_COMPARE_LOCAL_REMOTE; - Constants.log.info("Comparing local/remote entries"); + log.info("Comparing local/remote entries"); compareLocalRemote(); syncPhase = SYNC_PHASE_DOWNLOAD_REMOTE; - Constants.log.info("Downloading remote entries"); + log.info("Downloading remote entries"); downloadRemote(); syncPhase = SYNC_PHASE_SAVE_SYNC_STATE; - Constants.log.info("Saving sync state"); + log.info("Saving sync state"); saveSyncState(); } else - Constants.log.info("Remote collection didn't change, skipping remote sync"); + log.info("Remote collection didn't change, skipping remote sync"); } catch (IOException|ServiceUnavailableException e) { - Constants.log.error("I/O exception during sync, trying again later", e); + log.error("I/O exception during sync, trying again later", e); syncResult.stats.numIoExceptions++; if (e instanceof ServiceUnavailableException) { @@ -183,19 +198,19 @@ abstract public class SyncManager { final int messageString; if (e instanceof UnauthorizedException) { - Constants.log.error("Not authorized anymore", e); + log.error("Not authorized anymore", e); messageString = R.string.sync_error_unauthorized; syncResult.stats.numAuthExceptions++; } else if (e instanceof HttpException || e instanceof DavException) { - Constants.log.error("HTTP/DAV Exception during sync", e); + log.error("HTTP/DAV Exception during sync", e); messageString = R.string.sync_error_http_dav; syncResult.stats.numParseExceptions++; } else if (e instanceof CalendarStorageException || e instanceof ContactsStorageException) { - Constants.log.error("Couldn't access local storage", e); + log.error("Couldn't access local storage", e); messageString = R.string.sync_error_local_storage; syncResult.databaseError = true; } else { - Constants.log.error("Unknown sync error", e); + log.error("Unknown sync error", e); messageString = R.string.sync_error; syncResult.stats.numParseExceptions++; } @@ -237,6 +252,13 @@ abstract public class SyncManager { notification = builder.getNotification(); } notificationManager.notify(account.name, notificationId, notification); + } finally { + if (log instanceof ExternalFileLogger) + try { + ((ExternalFileLogger)log).close(); + } catch (IOException e) { + Constants.log.error("Couldn't close external log file", e); + } } } @@ -259,15 +281,15 @@ abstract public class SyncManager { final String fileName = local.getFileName(); if (!TextUtils.isEmpty(fileName)) { - Constants.log.info(fileName + " has been deleted locally -> deleting from server"); + log.info(fileName + " has been deleted locally -> deleting from server"); try { - new DavResource(httpClient, collectionURL.newBuilder().addPathSegment(fileName).build()) + new DavResource(log, httpClient, collectionURL.newBuilder().addPathSegment(fileName).build()) .delete(local.getETag()); } catch (IOException|HttpException e) { - Constants.log.warn("Couldn't delete " + fileName + " from server; ignoring (may be downloaded again)"); + log.warn("Couldn't delete " + fileName + " from server; ignoring (may be downloaded again)"); } } else - Constants.log.info("Removing local record #" + local.getId() + " which has been deleted locally and was never uploaded"); + log.info("Removing local record #" + local.getId() + " which has been deleted locally and was never uploaded"); local.delete(); syncResult.stats.numDeletes++; } @@ -277,7 +299,7 @@ abstract public class SyncManager { // assign file names and UIDs to new contacts so that we can use the file name as an index for (LocalResource local : localCollection.getWithoutFileName()) { String uuid = UUID.randomUUID().toString(); - Constants.log.info("Found local record #" + local.getId() + " without file name; assigning file name/UID based on " + uuid); + log.info("Found local record #" + local.getId() + " without file name; assigning file name/UID based on " + uuid); local.updateFileNameAndUID(uuid); } } @@ -296,7 +318,7 @@ abstract public class SyncManager { final String fileName = local.getFileName(); - DavResource remote = new DavResource(httpClient, collectionURL.newBuilder().addPathSegment(fileName).build()); + DavResource remote = new DavResource(log, httpClient, collectionURL.newBuilder().addPathSegment(fileName).build()); // generate entity to upload (VCard, iCal, whatever) RequestBody body = prepareUpload(local); @@ -304,25 +326,25 @@ abstract public class SyncManager { try { if (local.getETag() == null) { - Constants.log.info("Uploading new record " + fileName); + log.info("Uploading new record " + fileName); remote.put(body, null, true); } else { - Constants.log.info("Uploading locally modified record " + fileName); + log.info("Uploading locally modified record " + fileName); remote.put(body, local.getETag(), false); } } catch (ConflictException|PreconditionFailedException e) { // we can't interact with the user to resolve the conflict, so we treat 409 like 412 - Constants.log.info("Resource has been modified on the server before upload, ignoring", e); + log.info("Resource has been modified on the server before upload, ignoring", e); } String eTag = null; GetETag newETag = (GetETag) remote.properties.get(GetETag.NAME); if (newETag != null) { eTag = newETag.eTag; - Constants.log.debug("Received new ETag=" + eTag + " after uploading"); + log.debug("Received new ETag=" + eTag + " after uploading"); } else - Constants.log.debug("Didn't receive new ETag after uploading, setting to null"); + log.debug("Didn't receive new ETag after uploading, setting to null"); local.clearDirty(eTag); } @@ -343,12 +365,12 @@ abstract public class SyncManager { String localCTag = null; if (extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL)) - Constants.log.info("Manual sync, ignoring CTag"); + log.info("Manual sync, ignoring CTag"); else localCTag = localCollection.getCTag(); if (remoteCTag != null && remoteCTag.equals(localCTag)) { - Constants.log.info("Remote collection didn't change (CTag=" + remoteCTag + "), no need to query children"); + log.info("Remote collection didn't change (CTag=" + remoteCTag + "), no need to query children"); return false; } else return true; @@ -362,7 +384,7 @@ abstract public class SyncManager { LocalResource[] localList = localCollection.getAll(); localResources = new HashMap<>(localList.length); for (LocalResource resource : localList) { - Constants.log.debug("Found local resource: " + resource.getFileName()); + log.debug("Found local resource: " + resource.getFileName()); localResources.put(resource.getFileName(), resource); } } @@ -389,7 +411,7 @@ abstract public class SyncManager { for (String localName : localResources.keySet()) { DavResource remote = remoteResources.get(localName); if (remote == null) { - Constants.log.info(localName + " is not on server anymore, deleting"); + log.info(localName + " is not on server anymore, deleting"); localResources.get(localName).delete(); syncResult.stats.numDeletes++; } else { @@ -402,7 +424,7 @@ abstract public class SyncManager { if (remoteETag.equals(localETag)) syncResult.stats.numSkippedEntries++; else { - Constants.log.info(localName + " has been changed on server (current ETag=" + remoteETag + ", last known ETag=" + localETag + ")"); + log.info(localName + " has been changed on server (current ETag=" + remoteETag + ", last known ETag=" + localETag + ")"); toDownload.add(remote); } @@ -413,7 +435,7 @@ abstract public class SyncManager { // add all unseen (= remotely added) remote contacts if (!remoteResources.isEmpty()) { - Constants.log.info("New VCards have been found on the server: " + TextUtils.join(", ", remoteResources.keySet())); + log.info("New VCards have been found on the server: " + TextUtils.join(", ", remoteResources.keySet())); toDownload.addAll(remoteResources.values()); } } @@ -428,7 +450,7 @@ abstract public class SyncManager { /* Save sync state (CTag). It doesn't matter if it has changed during the sync process (for instance, because another client has uploaded changes), because this will simply cause all remote entries to be listed at the next sync. */ - Constants.log.info("Saving CTag=" + remoteCTag); + log.info("Saving CTag=" + remoteCTag); localCollection.setCTag(remoteCTag); } 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 58e4c40e..f64301f5 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncManager.java +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncManager.java @@ -79,7 +79,7 @@ public class TasksSyncManager extends SyncManager { Thread.currentThread().setContextClassLoader(context.getClassLoader()); // required for ical4j collectionURL = HttpUrl.parse(localTaskList().getSyncId()); - davCollection = new DavCalendar(httpClient, collectionURL); + davCollection = new DavCalendar(log, httpClient, collectionURL); } @Override @@ -95,7 +95,7 @@ public class TasksSyncManager extends SyncManager { int color = (pColor != null && pColor.color != null) ? pColor.color : LocalCalendar.defaultColor; ContentValues values = new ContentValues(2); - Constants.log.info("Setting new task list name \"" + displayName + "\" and color 0x" + Integer.toHexString(color)); + log.info("Setting new task list name \"" + displayName + "\" and color 0x" + Integer.toHexString(color)); values.put(TaskLists.LIST_NAME, displayName); values.put(TaskLists.LIST_COLOR, color); localTaskList().update(values); @@ -117,21 +117,21 @@ public class TasksSyncManager extends SyncManager { remoteResources = new HashMap<>(davCollection.members.size()); for (DavResource vCard : davCollection.members) { String fileName = vCard.fileName(); - Constants.log.debug("Found remote VTODO: " + fileName); + log.debug("Found remote VTODO: " + fileName); remoteResources.put(fileName, vCard); } } @Override protected void downloadRemote() throws IOException, HttpException, DavException, CalendarStorageException { - Constants.log.info("Downloading " + toDownload.size() + " tasks (" + MAX_MULTIGET + " at once)"); + log.info("Downloading " + toDownload.size() + " tasks (" + MAX_MULTIGET + " at once)"); // download new/updated iCalendars from server for (DavResource[] bunch : ArrayUtils.partition(toDownload.toArray(new DavResource[toDownload.size()]), MAX_MULTIGET)) { if (Thread.interrupted()) return; - Constants.log.info("Downloading " + StringUtils.join(bunch, ", ")); + log.info("Downloading " + StringUtils.join(bunch, ", ")); if (bunch.length == 1) { // only one contact, use GET @@ -189,7 +189,7 @@ public class TasksSyncManager extends SyncManager { try { tasks = Task.fromStream(stream, charset); } catch (InvalidCalendarException e) { - Constants.log.error("Received invalid iCalendar, ignoring", e); + log.error("Received invalid iCalendar, ignoring", e); return; } @@ -199,18 +199,18 @@ public class TasksSyncManager extends SyncManager { // update local task, if it exists LocalTask localTask = (LocalTask)localResources.get(fileName); if (localTask != null) { - Constants.log.info("Updating " + fileName + " in local tasklist"); + log.info("Updating " + fileName + " in local tasklist"); localTask.setETag(eTag); localTask.update(newData); syncResult.stats.numUpdates++; } else { - Constants.log.info("Adding " + fileName + " to local task list"); + log.info("Adding " + fileName + " to local task list"); localTask = new LocalTask(localTaskList(), newData, fileName, eTag); localTask.add(); syncResult.stats.numInserts++; } } else - Constants.log.error("Received VCALENDAR with not exactly one VTODO; ignoring " + fileName); + log.error("Received VCALENDAR with not exactly one VTODO; ignoring " + fileName); } } diff --git a/app/src/main/java/at/bitfire/davdroid/ui/settings/AccountActivity.java b/app/src/main/java/at/bitfire/davdroid/ui/settings/AccountActivity.java index f6950f1b..c2a4d8d5 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/settings/AccountActivity.java +++ b/app/src/main/java/at/bitfire/davdroid/ui/settings/AccountActivity.java @@ -26,7 +26,6 @@ public class AccountActivity extends Activity { getActionBar().setDisplayHomeAsUpEnabled(true); final FragmentManager fm = getFragmentManager(); - AccountFragment fragment = (AccountFragment)fm.findFragmentById(R.id.account_fragment); if (fragment == null) { fragment = new AccountFragment(); @@ -36,8 +35,9 @@ public class AccountActivity extends Activity { fragment.setArguments(args); getFragmentManager().beginTransaction() - .add(R.id.account_fragment, fragment) + .add(R.id.account_fragment, fragment, SettingsActivity.TAG_ACCOUNT_SETTINGS) .commit(); } } + } diff --git a/app/src/main/java/at/bitfire/davdroid/ui/settings/AccountFragment.java b/app/src/main/java/at/bitfire/davdroid/ui/settings/AccountFragment.java index d3aa45e5..73523a0c 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/settings/AccountFragment.java +++ b/app/src/main/java/at/bitfire/davdroid/ui/settings/AccountFragment.java @@ -9,8 +9,10 @@ package at.bitfire.davdroid.ui.settings; import android.accounts.Account; +import android.app.AlertDialog; +import android.app.DialogFragment; +import android.content.DialogInterface; import android.os.Bundle; -import android.preference.CheckBoxPreference; import android.preference.EditTextPreference; import android.preference.ListPreference; import android.preference.Preference; @@ -22,7 +24,6 @@ import android.provider.ContactsContract; import at.bitfire.davdroid.R; import at.bitfire.davdroid.syncadapter.AccountSettings; import at.bitfire.ical4android.TaskProvider; -import ezvcard.VCardVersion; public class AccountFragment extends PreferenceFragment { final static String ARG_ACCOUNT = "account"; @@ -36,44 +37,41 @@ public class AccountFragment extends PreferenceFragment { addPreferencesFromResource(R.xml.settings_account_prefs); account = getArguments().getParcelable(ARG_ACCOUNT); - readFromAccount(); + refresh(); } - public void readFromAccount() { + public void refresh() { final AccountSettings settings = new AccountSettings(getActivity(), account); // category: authentication final EditTextPreference prefUserName = (EditTextPreference)findPreference("username"); - prefUserName.setSummary(settings.getUserName()); - prefUserName.setText(settings.getUserName()); + prefUserName.setSummary(settings.username()); + prefUserName.setText(settings.username()); prefUserName.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { - settings.setUserName((String)newValue); - readFromAccount(); - return true; + settings.username((String) newValue); + refresh(); return false; } }); final EditTextPreference prefPassword = (EditTextPreference)findPreference("password"); - prefPassword.setText(settings.getPassword()); + prefPassword.setText(settings.password()); prefPassword.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { - settings.setPassword((String)newValue); - readFromAccount(); - return true; + settings.password((String) newValue); + refresh(); return false; } }); final SwitchPreference prefPreemptive = (SwitchPreference)findPreference("preemptive"); - prefPreemptive.setChecked(settings.getPreemptiveAuth()); + prefPreemptive.setChecked(settings.preemptiveAuth()); prefPreemptive.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { - settings.setPreemptiveAuth((Boolean)newValue); - readFromAccount(); - return true; + settings.preemptiveAuth((Boolean) newValue); + refresh(); return false; } }); @@ -90,8 +88,7 @@ public class AccountFragment extends PreferenceFragment { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { settings.setSyncInterval(ContactsContract.AUTHORITY, Long.parseLong((String) newValue)); - readFromAccount(); - return true; + refresh(); return false; } }); } else { @@ -111,8 +108,7 @@ public class AccountFragment extends PreferenceFragment { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { settings.setSyncInterval(CalendarContract.AUTHORITY, Long.parseLong((String) newValue)); - readFromAccount(); - return true; + refresh(); return false; } }); } else { @@ -132,8 +128,7 @@ public class AccountFragment extends PreferenceFragment { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { settings.setSyncInterval(TaskProvider.ProviderName.OpenTasks.authority, Long.parseLong((String) newValue)); - readFromAccount(); - return true; + refresh(); return false; } }); } else { @@ -141,5 +136,80 @@ public class AccountFragment extends PreferenceFragment { prefSyncTasks.setSummary(R.string.settings_sync_summary_not_available); } + // category: debug info + + final SwitchPreference prefLogExternalFile = (SwitchPreference)findPreference("log_external_file"); + prefLogExternalFile.setChecked(settings.logToExternalFile()); + prefLogExternalFile.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + Boolean external = (Boolean)newValue; + if (external) { + getFragmentManager().beginTransaction() + .add(LogExternalFileDialogFragment.newInstance(account), null) + .commit(); + return false; + } else { + settings.logToExternalFile(false); + refresh(); return false; + } + } + }); + + final SwitchPreference prefLogVerbose = (SwitchPreference)findPreference("log_verbose"); + prefLogVerbose.setChecked(settings.logVerbose()); + prefLogVerbose.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + settings.logVerbose((Boolean) newValue); + refresh(); return false; + } + }); + } + + + public static class LogExternalFileDialogFragment extends DialogFragment { + private static final String + KEY_ACCOUNT = "account"; + + public static LogExternalFileDialogFragment newInstance(Account account) { + Bundle args = new Bundle(); + args.putParcelable(KEY_ACCOUNT, account); + LogExternalFileDialogFragment fragment = new LogExternalFileDialogFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + public AlertDialog onCreateDialog(Bundle savedInstanceState) { + final AccountSettings settings = new AccountSettings(getActivity(), (Account)getArguments().getParcelable(KEY_ACCOUNT)); + return new AlertDialog.Builder(getActivity()) + .setTitle(R.string.settings_security_warning) + .setIcon(android.R.drawable.ic_dialog_alert) + .setMessage(R.string.settings_log_to_external_file_confirmation) + .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + settings.logToExternalFile(false); + refresh(); + } + }) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + settings.logToExternalFile(true); + refresh(); + } + }) + .create(); + } + + private void refresh() { + AccountFragment fragment = (AccountFragment)getActivity().getFragmentManager().findFragmentByTag(SettingsActivity.TAG_ACCOUNT_SETTINGS); + if (fragment != null) + fragment.refresh(); + } + } + } diff --git a/app/src/main/java/at/bitfire/davdroid/ui/settings/SettingsActivity.java b/app/src/main/java/at/bitfire/davdroid/ui/settings/SettingsActivity.java index 18a249ab..d94060b3 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/settings/SettingsActivity.java +++ b/app/src/main/java/at/bitfire/davdroid/ui/settings/SettingsActivity.java @@ -17,7 +17,7 @@ import android.os.Bundle; import at.bitfire.davdroid.R; public class SettingsActivity extends Activity { - private final static String KEY_SELECTED_ACCOUNT = "selected_account"; + public final static String TAG_ACCOUNT_SETTINGS = "account_settings"; boolean tabletLayout; @@ -44,7 +44,7 @@ public class SettingsActivity extends Activity { getFragmentManager().beginTransaction() .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) - .replace(R.id.right_pane, fragment) + .replace(R.id.right_pane, fragment, TAG_ACCOUNT_SETTINGS) .commit(); } else { // phone layout Intent intent = new Intent(getApplicationContext(), AccountActivity.class); diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/QueryServerDialogFragment.java b/app/src/main/java/at/bitfire/davdroid/ui/setup/QueryServerDialogFragment.java index da86b2a2..126a0471 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/QueryServerDialogFragment.java +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/QueryServerDialogFragment.java @@ -110,7 +110,7 @@ public class QueryServerDialogFragment extends DialogFragment implements LoaderC ); try { - DavResourceFinder finder = new DavResourceFinder(context, serverInfo); + DavResourceFinder finder = new DavResourceFinder(null, context, serverInfo); finder.findResources(); } catch (URISyntaxException e) { serverInfo.setErrorMessage(getContext().getString(R.string.exception_uri_syntax, e.getMessage())); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bd721084..f8591ffc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -154,10 +154,15 @@ Every 4 hours Once a day - Address book - VCard 4.0 support - Contacts are sent in VCard 4.0 format - Contacts are sent in VCard 3.0 format + Debugging + Potential security risk! + Log to external file + External log files will contain private data and be accessible by other apps. Turn off external logging and delete the log files after use. + Logs are written to external files (if possible) + Logs are written to ADB + Verbose logging + Log synchronization information and network traffic + Log only synchronization information Android version update Android version updates may have an impact on how DAVdroid works. If there are problems, please delete your DAVdroid accounts and add them again. diff --git a/app/src/main/res/xml/settings_account_prefs.xml b/app/src/main/res/xml/settings_account_prefs.xml index 9fc0eeed..a66b507c 100644 --- a/app/src/main/res/xml/settings_account_prefs.xml +++ b/app/src/main/res/xml/settings_account_prefs.xml @@ -59,10 +59,22 @@ - diff --git a/dav4android b/dav4android index e7218aeb..47541c16 160000 --- a/dav4android +++ b/dav4android @@ -1 +1 @@ -Subproject commit e7218aeb8ad8a96620208140bc7b86f63bf05fad +Subproject commit 47541c169b970a6991b0f2f6c589232009e258ad