1
0
mirror of https://github.com/etesync/android synced 2024-11-29 11:28:19 +00:00

Synchronization logging to external file

* use ExternalFileLogger to log synchronization, if enabled in Settings
* new settings: log to external file / log verbose
* DavResource: check for well-known even if service type of user-given URL can't be determined
* remove oblsete testing assets
This commit is contained in:
Ricki Hirner 2015-10-18 16:20:26 +02:00
parent dd50f10c58
commit 58f05986c9
No known key found for this signature in database
GPG Key ID: C4A212CF0B2B4566
31 changed files with 754 additions and 377 deletions

View File

@ -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

View File

@ -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

View File

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,5 +0,0 @@
BEGIN:VCARD
VERSION:3.0
N:Äuçek;Özkan
FN:Özkan Äuçek
END:VCARD

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -20,6 +20,7 @@
<uses-permission android:name="android.permission.WRITE_CALENDAR" /> <uses-permission android:name="android.permission.WRITE_CALENDAR" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" /> <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" /> <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18" />
<uses-permission android:name="org.dmfs.permission.READ_TASKS" /> <uses-permission android:name="org.dmfs.permission.READ_TASKS" />
<uses-permission android:name="org.dmfs.permission.WRITE_TASKS" /> <uses-permission android:name="org.dmfs.permission.WRITE_TASKS" />

View File

@ -21,6 +21,8 @@ import com.squareup.okhttp.Response;
import com.squareup.okhttp.internal.tls.OkHostnameVerifier; import com.squareup.okhttp.internal.tls.OkHostnameVerifier;
import com.squareup.okhttp.logging.HttpLoggingInterceptor; import com.squareup.okhttp.logging.HttpLoggingInterceptor;
import org.slf4j.Logger;
import java.io.IOException; import java.io.IOException;
import java.net.Proxy; import java.net.Proxy;
import java.security.KeyManagementException; import java.security.KeyManagementException;
@ -39,18 +41,7 @@ import de.duenndns.ssl.MemorizingTrustManager;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
public class HttpClient extends OkHttpClient { public class HttpClient extends OkHttpClient {
protected static final String HEADER_AUTHORIZATION = "Authorization";
final static UserAgentInterceptor userAgentInterceptor = new UserAgentInterceptor(); 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 final String userAgent;
static { static {
@ -58,12 +49,14 @@ public class HttpClient extends OkHttpClient {
userAgent = "DAVdroid/" + BuildConfig.VERSION_NAME + " (" + date + "; dav4android) Android/" + Build.VERSION.RELEASE; userAgent = "DAVdroid/" + BuildConfig.VERSION_NAME + " (" + date + "; dav4android) Android/" + Build.VERSION.RELEASE;
} }
final Logger log;
final Context context; final Context context;
protected String username, password; protected String username, password;
protected HttpClient(Context context) { protected HttpClient(final Logger log, Context context) {
super(); super();
this.log = (log != null) ? log : Constants.log;
this.context = context; this.context = context;
if (context != null) { if (context != null) {
@ -90,12 +83,20 @@ public class HttpClient extends OkHttpClient {
networkInterceptors().add(userAgentInterceptor); networkInterceptors().add(userAgentInterceptor);
// enable verbose logs, if requested // enable verbose logs, if requested
if (Constants.log.isTraceEnabled()) if (log.isTraceEnabled()) {
enableLogs(); 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) { public HttpClient(Logger log, Context context, String username, String password, boolean preemptive) {
this(context); this(log, context);
// authentication // authentication
this.username = username; 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 client user name and password from this client will be used
* @param host authentication will be restricted to this host * @param host authentication will be restricted to this host
*/ */
public HttpClient(HttpClient client, String host) { public HttpClient(Logger log, HttpClient client, String host) {
this(client.context); this(log, client.context);
username = client.username; username = client.username;
password = client.password; password = client.password;
@ -123,12 +124,7 @@ public class HttpClient extends OkHttpClient {
// for testing (mock server doesn't need auth) // for testing (mock server doesn't need auth)
protected HttpClient() { protected HttpClient() {
this(null, null, null, false); this(null, null, null, null, false);
}
protected void enableLogs() {
interceptors().add(loggingInterceptor);
} }
@ -142,7 +138,6 @@ public class HttpClient extends OkHttpClient {
} }
} }
@RequiredArgsConstructor @RequiredArgsConstructor
static class PreemptiveAuthenticationInterceptor implements Interceptor { static class PreemptiveAuthenticationInterceptor implements Interceptor {
final String username, password; final String username, password;

View File

@ -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();
}
}

View File

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

View File

@ -12,6 +12,7 @@ import android.text.TextUtils;
import com.squareup.okhttp.HttpUrl; import com.squareup.okhttp.HttpUrl;
import org.slf4j.Logger;
import org.xbill.DNS.Lookup; import org.xbill.DNS.Lookup;
import org.xbill.DNS.Record; import org.xbill.DNS.Record;
import org.xbill.DNS.SRVRecord; import org.xbill.DNS.SRVRecord;
@ -57,7 +58,8 @@ public class DavResourceFinder {
@Override public String toString() { return name; } @Override public String toString() { return name; }
}; };
protected Context context; protected final Logger log;
protected final Context context;
protected final HttpClient httpClient; protected final HttpClient httpClient;
protected final ServerInfo serverInfo; protected final ServerInfo serverInfo;
@ -67,11 +69,12 @@ public class DavResourceFinder {
taskLists = new HashMap<>(); taskLists = new HashMap<>();
public DavResourceFinder(Context context, ServerInfo serverInfo) { public DavResourceFinder(Logger log, Context context, ServerInfo serverInfo) {
this.log = log;
this.context = context; this.context = context;
this.serverInfo = serverInfo; 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 { 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) 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 <calendar-home-set> and/or has <current-user-principal>"); Constants.log.info("Check whether user-given URL is a calendar collection and/or contains <calendar-home-set> and/or has <current-user-principal>");
DavResource davBase = new DavResource(httpClient, userURL); DavResource davBase = new DavResource(log, httpClient, userURL);
try { try {
if (service == Service.CALDAV) { if (service == Service.CALDAV) {
davBase.propfind(0, davBase.propfind(0,
@ -158,18 +161,23 @@ public class DavResourceFinder {
CurrentUserPrincipal currentUserPrincipal = (CurrentUserPrincipal)davBase.properties.get(CurrentUserPrincipal.NAME); CurrentUserPrincipal currentUserPrincipal = (CurrentUserPrincipal)davBase.properties.get(CurrentUserPrincipal.NAME);
if (currentUserPrincipal != null && currentUserPrincipal.href != null) if (currentUserPrincipal != null && currentUserPrincipal.href != null)
principalUrl = davBase.location.resolve(currentUserPrincipal.href); principalUrl = davBase.location.resolve(currentUserPrincipal.href);
if (principalUrl == null) {
Constants.log.info("User-given URL doesn't contain <current-user-principal>, trying /.well-known/" + service.name);
principalUrl = getCurrentUserPrincipal(userURL.resolve("/.well-known/" + service.name));
}
} }
} catch(IOException|HttpException|DavException e) { } catch(IOException|HttpException|DavException e) {
Constants.log.debug("Couldn't find <current-user-principal>", e); Constants.log.debug("Couldn't find <current-user-principal> at user-given URL", e);
} }
// try service discovery with "domain" = user-given host name if (principalUrl == null)
domain = baseURI.getHost(); try {
Constants.log.info("User-given URL doesn't contain <current-user-principal>, trying /.well-known/" + service.name);
principalUrl = getCurrentUserPrincipal(userURL.resolve("/.well-known/" + service.name));
} catch(IOException|HttpException e) {
Constants.log.debug("Couldn't determine <current-user-principal> 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())) { } else if ("mailto".equals(baseURI.getScheme())) {
String mailbox = baseURI.getSchemeSpecificPart(); String mailbox = baseURI.getSchemeSpecificPart();
@ -190,7 +198,7 @@ public class DavResourceFinder {
if (principalUrl != null) { if (principalUrl != null) {
Constants.log.info("Principal URL=" + principalUrl + ", getting <calendar-home-set>"); Constants.log.info("Principal URL=" + principalUrl + ", getting <calendar-home-set>");
try { try {
DavResource principal = new DavResource(httpClient, principalUrl); DavResource principal = new DavResource(log, httpClient, principalUrl);
if (service == Service.CALDAV) { if (service == Service.CALDAV) {
principal.propfind(0, CalendarHomeSet.NAME); principal.propfind(0, CalendarHomeSet.NAME);
@ -226,7 +234,7 @@ public class DavResourceFinder {
if (service == Service.CALDAV) if (service == Service.CALDAV)
try { try {
Constants.log.info("Listing calendar collections in home set " + url); 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, homeSet.propfind(1, SupportedCalendarComponentSet.NAME, ResourceType.NAME, DisplayName.NAME, CurrentUserPrivilegeSet.NAME,
CalendarColor.NAME, CalendarDescription.NAME, CalendarTimezone.NAME); CalendarColor.NAME, CalendarDescription.NAME, CalendarTimezone.NAME);
@ -242,7 +250,7 @@ public class DavResourceFinder {
else if (service == Service.CARDDAV) else if (service == Service.CARDDAV)
try { try {
Constants.log.info("Listing address books in home set " + url); 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); 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 // 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) * @param service service to discover (CALDAV or CARDDAV)
* @return principal URL, or null if none found * @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 scheme = null;
String fqdn = null; String fqdn = null;
Integer port = null; Integer port = null;
List<String> paths = new LinkedList<>(); // there may be multiple paths to try List<String> paths = new LinkedList<>(); // there may be multiple paths to try
try { final String query = "_" + service.name + "s._tcp." + domain;
final String query = "_" + service.name + "s._tcp." + domain; Constants.log.debug("Looking up SRV records for " + query);
Constants.log.debug("Looking up SRV records for " + query); Record[] records = new Lookup(query, Type.SRV).run();
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) { if (records != null && records.length >= 1) {
// choose SRV record to use (query may return multiple SRV records) TXTRecord txt = (TXTRecord)records[0];
SRVRecord srv = selectSRVRecord(records); for (String segment : (String[])txt.getStrings().toArray(new String[0]))
if (segment.startsWith("path=")) {
scheme = "https"; paths.add(segment.substring(5));
fqdn = srv.getTarget().toString(true); Constants.log.info("Found TXT record; initial context path=" + paths);
port = srv.getPort(); break;
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("/");
} }
} catch (IOException e) {
Constants.log.debug("SRV/TXT record discovery failed", e); // if there's TXT record if it it's wrong, try well-known
return null; paths.add("/.well-known/" + service.name);
// if this fails, too, try "/"
paths.add("/");
} }
for (String path : paths) { for (String path : paths) {
@ -424,20 +427,16 @@ public class DavResourceFinder {
* @param url URL to query with PROPFIND (Depth: 0) * @param url URL to query with PROPFIND (Depth: 0)
* @return current-user-principal URL, or null if none * @return current-user-principal URL, or null if none
*/ */
protected HttpUrl getCurrentUserPrincipal(HttpUrl url) { protected HttpUrl getCurrentUserPrincipal(HttpUrl url) throws IOException, HttpException, DavException {
try { DavResource dav = new DavResource(log, httpClient, url);
DavResource dav = new DavResource(httpClient, url); dav.propfind(0, CurrentUserPrincipal.NAME);
dav.propfind(0, CurrentUserPrincipal.NAME); CurrentUserPrincipal currentUserPrincipal = (CurrentUserPrincipal) dav.properties.get(CurrentUserPrincipal.NAME);
CurrentUserPrincipal currentUserPrincipal = (CurrentUserPrincipal) dav.properties.get(CurrentUserPrincipal.NAME); if (currentUserPrincipal != null && currentUserPrincipal.href != null) {
if (currentUserPrincipal != null && currentUserPrincipal.href != null) { HttpUrl principal = url.resolve(currentUserPrincipal.href);
HttpUrl principal = url.resolve(currentUserPrincipal.href); if (principal != null) {
if (principal != null) { Constants.log.info("Found current-user-principal: " + principal);
Constants.log.info("Found current-user-principal: " + principal); return principal;
return principal;
}
} }
} catch(IOException|HttpException|DavException e) {
Constants.log.debug("PROPFIND for current-user-principal on " + url + " failed", e);
} }
return null; return null;
} }

View File

@ -47,6 +47,8 @@ public class AccountSettings {
KEY_USERNAME = "user_name", KEY_USERNAME = "user_name",
KEY_AUTH_PREEMPTIVE = "auth_preemptive", 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"; KEY_LAST_ANDROID_VERSION = "last_android_version";
public final static long SYNC_INTERVAL_MANUALLY = -1; public final static long SYNC_INTERVAL_MANUALLY = -1;
@ -84,9 +86,8 @@ public class AccountSettings {
showNotification(Constants.NOTIFICATION_ANDROID_VERSION_UPDATED, showNotification(Constants.NOTIFICATION_ANDROID_VERSION_UPDATED,
context.getString(R.string.settings_android_update_title), context.getString(R.string.settings_android_update_title),
context.getString(R.string.settings_android_update_description)); 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 // authentication settings
public String getUserName() { public String username() { return accountManager.getUserData(account, KEY_USERNAME); }
return accountManager.getUserData(account, KEY_USERNAME); public void username(String userName) { accountManager.setUserData(account, KEY_USERNAME, userName); }
}
public void setUserName(String userName) { accountManager.setUserData(account, KEY_USERNAME, userName); }
public String getPassword() { public String password() { return accountManager.getPassword(account); }
return accountManager.getPassword(account); public void password(String password) { accountManager.setPassword(account, password); }
}
public void setPassword(String password) { accountManager.setPassword(account, password); }
public boolean getPreemptiveAuth() { return Boolean.parseBoolean(accountManager.getUserData(account, KEY_AUTH_PREEMPTIVE)); } public boolean preemptiveAuth() { return Boolean.parseBoolean(accountManager.getUserData(account, KEY_AUTH_PREEMPTIVE)); }
public void setPreemptiveAuth(boolean preemptive) { accountManager.setUserData(account, KEY_AUTH_PREEMPTIVE, Boolean.toString(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 // sync. settings
@ -229,7 +235,7 @@ public class AccountSettings {
} }
private void update_1_2() throws ContactsStorageException { private void update_1_2() throws ContactsStorageException {
/* - KEY_ADDRESSBOOK_URL ("addressbook_url"),, /* - KEY_ADDRESSBOOK_URL ("addressbook_url"),
- KEY_ADDRESSBOOK_CTAG ("addressbook_ctag"), - KEY_ADDRESSBOOK_CTAG ("addressbook_ctag"),
- KEY_ADDRESSBOOK_VCARD_VERSION ("addressbook_vcard_version") are not used anymore (now stored in ContactsContract.SyncState) - 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 - 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 // move previous address book info to ContactsContract.SyncState
@Cleanup("release") ContentProviderClient provider = context.getContentResolver().acquireContentProviderClient(ContactsContract.AUTHORITY); @Cleanup("release") ContentProviderClient provider = context.getContentResolver().acquireContentProviderClient(ContactsContract.AUTHORITY);
if (provider != null) { if (provider == null)
LocalAddressBook addr = new LocalAddressBook(account, provider); throw new ContactsStorageException("Couldn't access Contacts provider");
String url = accountManager.getUserData(account, "addressbook_url"); LocalAddressBook addr = new LocalAddressBook(account, provider);
if (!TextUtils.isEmpty(url))
addr.setURL(url);
accountManager.setUserData(account, "addressbook_url", null);
String cTag = accountManager.getUserData(account, "addressbook_ctag"); // until now, ContactsContract.Settings.UNGROUPED_VISIBLE was not set explicitly
if (!TextUtils.isEmpty(cTag)) ContentValues values = new ContentValues();
addr.setCTag(cTag); values.put(ContactsContract.Settings.UNGROUPED_VISIBLE, 1);
accountManager.setUserData(account, "addressbook_ctag", null); addr.updateSettings(values);
}
// store current Android version String url = accountManager.getUserData(account, "addressbook_url");
accountManager.setUserData(account, KEY_LAST_ANDROID_VERSION, String.valueOf(Build.VERSION.SDK_INT)); 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"); accountManager.setUserData(account, KEY_SETTINGS_VERSION, "2");
} }

View File

@ -74,7 +74,7 @@ public class CalendarSyncManager extends SyncManager {
Thread.currentThread().setContextClassLoader(context.getClassLoader()); // required for ical4j Thread.currentThread().setContextClassLoader(context.getClassLoader()); // required for ical4j
collectionURL = HttpUrl.parse(localCalendar().getName()); collectionURL = HttpUrl.parse(localCalendar().getName());
davCollection = new DavCalendar(httpClient, collectionURL); davCollection = new DavCalendar(log, httpClient, collectionURL);
} }
@Override @Override
@ -90,7 +90,7 @@ public class CalendarSyncManager extends SyncManager {
int color = (pColor != null && pColor.color != null) ? pColor.color : LocalCalendar.defaultColor; int color = (pColor != null && pColor.color != null) ? pColor.color : LocalCalendar.defaultColor;
ContentValues values = new ContentValues(2); 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_DISPLAY_NAME, displayName);
values.put(Calendars.CALENDAR_COLOR, color); values.put(Calendars.CALENDAR_COLOR, color);
localCalendar().update(values); localCalendar().update(values);
@ -119,20 +119,20 @@ public class CalendarSyncManager extends SyncManager {
remoteResources = new HashMap<>(davCollection.members.size()); remoteResources = new HashMap<>(davCollection.members.size());
for (DavResource vCard : davCollection.members) { for (DavResource vCard : davCollection.members) {
String fileName = vCard.fileName(); String fileName = vCard.fileName();
Constants.log.debug("Found remote VEVENT: " + fileName); log.debug("Found remote VEVENT: " + fileName);
remoteResources.put(fileName, vCard); remoteResources.put(fileName, vCard);
} }
} }
@Override @Override
protected void downloadRemote() throws IOException, HttpException, DavException, CalendarStorageException { 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 // download new/updated iCalendars from server
for (DavResource[] bunch : ArrayUtils.partition(toDownload.toArray(new DavResource[toDownload.size()]), MAX_MULTIGET)) { for (DavResource[] bunch : ArrayUtils.partition(toDownload.toArray(new DavResource[toDownload.size()]), MAX_MULTIGET)) {
if (Thread.interrupted()) if (Thread.interrupted())
return; return;
Constants.log.info("Downloading " + StringUtils.join(bunch, ", ")); log.info("Downloading " + StringUtils.join(bunch, ", "));
if (bunch.length == 1) { if (bunch.length == 1) {
// only one contact, use GET // only one contact, use GET
@ -190,7 +190,7 @@ public class CalendarSyncManager extends SyncManager {
try { try {
events = Event.fromStream(stream, charset); events = Event.fromStream(stream, charset);
} catch (InvalidCalendarException e) { } catch (InvalidCalendarException e) {
Constants.log.error("Received invalid iCalendar, ignoring"); log.error("Received invalid iCalendar, ignoring");
return; return;
} }
@ -200,18 +200,18 @@ public class CalendarSyncManager extends SyncManager {
// delete local event, if it exists // delete local event, if it exists
LocalEvent localEvent = (LocalEvent)localResources.get(fileName); LocalEvent localEvent = (LocalEvent)localResources.get(fileName);
if (localEvent != null) { if (localEvent != null) {
Constants.log.info("Updating " + fileName + " in local calendar"); log.info("Updating " + fileName + " in local calendar");
localEvent.setETag(eTag); localEvent.setETag(eTag);
localEvent.update(newData); localEvent.update(newData);
syncResult.stats.numUpdates++; syncResult.stats.numUpdates++;
} else { } else {
Constants.log.info("Adding " + fileName + " to local calendar"); log.info("Adding " + fileName + " to local calendar");
localEvent = new LocalEvent(localCalendar(), newData, fileName, eTag); localEvent = new LocalEvent(localCalendar(), newData, fileName, eTag);
localEvent.add(); localEvent.add();
syncResult.stats.numInserts++; syncResult.stats.numInserts++;
} }
} else } 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);
} }
} }

View File

@ -83,7 +83,7 @@ public class ContactsSyncManager extends SyncManager {
if (url == null) if (url == null)
throw new ContactsStorageException("Couldn't get address book URL"); throw new ContactsStorageException("Couldn't get address book URL");
collectionURL = HttpUrl.parse(url); collectionURL = HttpUrl.parse(url);
davCollection = new DavAddressBook(httpClient, collectionURL); davCollection = new DavAddressBook(log, httpClient, collectionURL);
processChangedGroups(); processChangedGroups();
} }
@ -98,7 +98,7 @@ public class ContactsSyncManager extends SyncManager {
for (MediaType type : supportedAddressData.types) for (MediaType type : supportedAddressData.types)
if ("text/vcard; version=4.0".equalsIgnoreCase(type.toString())) if ("text/vcard; version=4.0".equalsIgnoreCase(type.toString()))
hasVCard4 = true; hasVCard4 = true;
Constants.log.info("Server advertises VCard/4 support: " + hasVCard4); log.info("Server advertises VCard/4 support: " + hasVCard4);
} }
@Override @Override
@ -118,7 +118,7 @@ public class ContactsSyncManager extends SyncManager {
davAddressBook().addressbookQuery(); davAddressBook().addressbookQuery();
} catch(HttpException e) { } catch(HttpException e) {
if (e.status/100 == 4) { 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); davAddressBook().propfind(1, GetETag.NAME);
} }
} }
@ -126,14 +126,14 @@ public class ContactsSyncManager extends SyncManager {
remoteResources = new HashMap<>(davCollection.members.size()); remoteResources = new HashMap<>(davCollection.members.size());
for (DavResource vCard : davCollection.members) { for (DavResource vCard : davCollection.members) {
String fileName = vCard.fileName(); String fileName = vCard.fileName();
Constants.log.debug("Found remote VCard: " + fileName); log.debug("Found remote VCard: " + fileName);
remoteResources.put(fileName, vCard); remoteResources.put(fileName, vCard);
} }
} }
@Override @Override
protected void downloadRemote() throws IOException, HttpException, DavException, ContactsStorageException { 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 // prepare downloader which may be used to download external resource like contact photos
Contact.Downloader downloader = new ResourceDownloader(httpClient, collectionURL); Contact.Downloader downloader = new ResourceDownloader(httpClient, collectionURL);
@ -143,7 +143,7 @@ public class ContactsSyncManager extends SyncManager {
if (Thread.interrupted()) if (Thread.interrupted())
return; return;
Constants.log.info("Downloading " + StringUtils.join(bunch, ", ")); log.info("Downloading " + StringUtils.join(bunch, ", "));
if (bunch.length == 1) { if (bunch.length == 1) {
// only one contact, use GET // only one contact, use GET
@ -202,7 +202,7 @@ public class ContactsSyncManager extends SyncManager {
// groups with DELETED=1: remove group finally // groups with DELETED=1: remove group finally
for (LocalGroup group : addressBook.getDeletedGroups()) { for (LocalGroup group : addressBook.getDeletedGroups()) {
long groupId = group.getId(); 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) // 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 // NOTE: doesn't work that way because Contact Provider removes the group memberships even for DELETED groups
// addressBook.removeGroupMemberships(groupId, false); // 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 // groups with DIRTY=1: mark all memberships as dirty, then clean DIRTY flag of group
for (LocalGroup group : addressBook.getDirtyGroups()) { for (LocalGroup group : addressBook.getDirtyGroups()) {
long groupId = group.getId(); 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); addressBook.markMembersDirty(groupId);
group.clearDirty(); group.clearDirty();
} }
@ -226,32 +226,32 @@ public class ContactsSyncManager extends SyncManager {
// update local contact, if it exists // update local contact, if it exists
LocalContact localContact = (LocalContact)localResources.get(fileName); LocalContact localContact = (LocalContact)localResources.get(fileName);
if (localContact != null) { if (localContact != null) {
Constants.log.info("Updating " + fileName + " in local address book"); log.info("Updating " + fileName + " in local address book");
localContact.eTag = eTag; localContact.eTag = eTag;
localContact.update(newData); localContact.update(newData);
syncResult.stats.numUpdates++; syncResult.stats.numUpdates++;
} else { } 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 = new LocalContact(localAddressBook(), newData, fileName, eTag);
localContact.add(); localContact.add();
syncResult.stats.numInserts++; syncResult.stats.numInserts++;
} }
} else } 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 // downloader helper class
@RequiredArgsConstructor @RequiredArgsConstructor
private static class ResourceDownloader implements Contact.Downloader { private class ResourceDownloader implements Contact.Downloader {
final HttpClient httpClient; final HttpClient httpClient;
final HttpUrl baseUrl; final HttpUrl baseUrl;
@Override @Override
public byte[] download(String url, String accepts) { public byte[] download(String url, String accepts) {
HttpUrl httpUrl = HttpUrl.parse(url); HttpUrl httpUrl = HttpUrl.parse(url);
HttpClient resourceClient = new HttpClient(httpClient, httpUrl.host()); HttpClient resourceClient = new HttpClient(log, httpClient, httpUrl.host());
try { try {
Response response = resourceClient.newCall(new Request.Builder() Response response = resourceClient.newCall(new Request.Builder()
.get() .get()
@ -264,10 +264,10 @@ public class ContactsSyncManager extends SyncManager {
if (response.isSuccessful() && stream != null) { if (response.isSuccessful() && stream != null) {
return IOUtils.toByteArray(stream); return IOUtils.toByteArray(stream);
} else } else
Constants.log.error("Couldn't download external resource"); log.error("Couldn't download external resource");
} }
} catch(IOException e) { } catch(IOException e) {
Constants.log.error("Couldn't download external resource", e); log.error("Couldn't download external resource", e);
} }
return null; return null;
} }

View File

@ -1,4 +1,4 @@
/* /*
* Copyright © 2013 2015 Ricki Hirner (bitfire web engineering). * Copyright © 2013 2015 Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials * All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0 * 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.HttpUrl;
import com.squareup.okhttp.RequestBody; import com.squareup.okhttp.RequestBody;
import org.slf4j.Logger;
import java.io.IOException; import java.io.IOException;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
@ -42,6 +44,7 @@ import at.bitfire.dav4android.property.GetETag;
import at.bitfire.davdroid.Constants; import at.bitfire.davdroid.Constants;
import at.bitfire.davdroid.HttpClient; import at.bitfire.davdroid.HttpClient;
import at.bitfire.davdroid.R; import at.bitfire.davdroid.R;
import at.bitfire.davdroid.log.ExternalFileLogger;
import at.bitfire.davdroid.resource.LocalCollection; import at.bitfire.davdroid.resource.LocalCollection;
import at.bitfire.davdroid.resource.LocalResource; import at.bitfire.davdroid.resource.LocalResource;
import at.bitfire.davdroid.ui.DebugInfoActivity; import at.bitfire.davdroid.ui.DebugInfoActivity;
@ -75,6 +78,8 @@ abstract public class SyncManager {
protected final AccountSettings settings; protected final AccountSettings settings;
protected LocalCollection localCollection; protected LocalCollection localCollection;
protected Logger log;
protected final HttpClient httpClient; protected final HttpClient httpClient;
protected HttpUrl collectionURL; protected HttpUrl collectionURL;
protected DavResource davCollection; protected DavResource davCollection;
@ -101,9 +106,19 @@ abstract public class SyncManager {
this.authority = authority; this.authority = authority;
this.syncResult = syncResult; this.syncResult = syncResult;
// get account settings and generate httpClient // get account settings and log to file (if requested)
settings = new AccountSettings(context, account); 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 // dismiss previous error notifications
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
@ -115,60 +130,60 @@ abstract public class SyncManager {
public void performSync() { public void performSync() {
int syncPhase = SYNC_PHASE_PREPARE; int syncPhase = SYNC_PHASE_PREPARE;
try { try {
Constants.log.info("Preparing synchronization"); log.info("Preparing synchronization");
prepare(); prepare();
if (Thread.interrupted()) if (Thread.interrupted())
return; return;
syncPhase = SYNC_PHASE_QUERY_CAPABILITIES; syncPhase = SYNC_PHASE_QUERY_CAPABILITIES;
Constants.log.info("Querying capabilities"); log.info("Querying capabilities");
queryCapabilities(); queryCapabilities();
syncPhase = SYNC_PHASE_PROCESS_LOCALLY_DELETED; syncPhase = SYNC_PHASE_PROCESS_LOCALLY_DELETED;
Constants.log.info("Processing locally deleted entries"); log.info("Processing locally deleted entries");
processLocallyDeleted(); processLocallyDeleted();
if (Thread.interrupted()) if (Thread.interrupted())
return; return;
syncPhase = SYNC_PHASE_PREPARE_DIRTY; syncPhase = SYNC_PHASE_PREPARE_DIRTY;
Constants.log.info("Locally preparing dirty entries"); log.info("Locally preparing dirty entries");
prepareDirty(); prepareDirty();
syncPhase = SYNC_PHASE_UPLOAD_DIRTY; syncPhase = SYNC_PHASE_UPLOAD_DIRTY;
Constants.log.info("Uploading dirty entries"); log.info("Uploading dirty entries");
uploadDirty(); uploadDirty();
syncPhase = SYNC_PHASE_CHECK_SYNC_STATE; syncPhase = SYNC_PHASE_CHECK_SYNC_STATE;
Constants.log.info("Checking sync state"); log.info("Checking sync state");
if (checkSyncState()) { if (checkSyncState()) {
syncPhase = SYNC_PHASE_LIST_LOCAL; syncPhase = SYNC_PHASE_LIST_LOCAL;
Constants.log.info("Listing local entries"); log.info("Listing local entries");
listLocal(); listLocal();
if (Thread.interrupted()) if (Thread.interrupted())
return; return;
syncPhase = SYNC_PHASE_LIST_REMOTE; syncPhase = SYNC_PHASE_LIST_REMOTE;
Constants.log.info("Listing remote entries"); log.info("Listing remote entries");
listRemote(); listRemote();
if (Thread.interrupted()) if (Thread.interrupted())
return; return;
syncPhase = SYNC_PHASE_COMPARE_LOCAL_REMOTE; syncPhase = SYNC_PHASE_COMPARE_LOCAL_REMOTE;
Constants.log.info("Comparing local/remote entries"); log.info("Comparing local/remote entries");
compareLocalRemote(); compareLocalRemote();
syncPhase = SYNC_PHASE_DOWNLOAD_REMOTE; syncPhase = SYNC_PHASE_DOWNLOAD_REMOTE;
Constants.log.info("Downloading remote entries"); log.info("Downloading remote entries");
downloadRemote(); downloadRemote();
syncPhase = SYNC_PHASE_SAVE_SYNC_STATE; syncPhase = SYNC_PHASE_SAVE_SYNC_STATE;
Constants.log.info("Saving sync state"); log.info("Saving sync state");
saveSyncState(); saveSyncState();
} else } 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) { } 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++; syncResult.stats.numIoExceptions++;
if (e instanceof ServiceUnavailableException) { if (e instanceof ServiceUnavailableException) {
@ -183,19 +198,19 @@ abstract public class SyncManager {
final int messageString; final int messageString;
if (e instanceof UnauthorizedException) { if (e instanceof UnauthorizedException) {
Constants.log.error("Not authorized anymore", e); log.error("Not authorized anymore", e);
messageString = R.string.sync_error_unauthorized; messageString = R.string.sync_error_unauthorized;
syncResult.stats.numAuthExceptions++; syncResult.stats.numAuthExceptions++;
} else if (e instanceof HttpException || e instanceof DavException) { } 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; messageString = R.string.sync_error_http_dav;
syncResult.stats.numParseExceptions++; syncResult.stats.numParseExceptions++;
} else if (e instanceof CalendarStorageException || e instanceof ContactsStorageException) { } 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; messageString = R.string.sync_error_local_storage;
syncResult.databaseError = true; syncResult.databaseError = true;
} else { } else {
Constants.log.error("Unknown sync error", e); log.error("Unknown sync error", e);
messageString = R.string.sync_error; messageString = R.string.sync_error;
syncResult.stats.numParseExceptions++; syncResult.stats.numParseExceptions++;
} }
@ -237,6 +252,13 @@ abstract public class SyncManager {
notification = builder.getNotification(); notification = builder.getNotification();
} }
notificationManager.notify(account.name, notificationId, notification); 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(); final String fileName = local.getFileName();
if (!TextUtils.isEmpty(fileName)) { 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 { try {
new DavResource(httpClient, collectionURL.newBuilder().addPathSegment(fileName).build()) new DavResource(log, httpClient, collectionURL.newBuilder().addPathSegment(fileName).build())
.delete(local.getETag()); .delete(local.getETag());
} catch (IOException|HttpException e) { } 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 } 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(); local.delete();
syncResult.stats.numDeletes++; 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 // assign file names and UIDs to new contacts so that we can use the file name as an index
for (LocalResource local : localCollection.getWithoutFileName()) { for (LocalResource local : localCollection.getWithoutFileName()) {
String uuid = UUID.randomUUID().toString(); 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); local.updateFileNameAndUID(uuid);
} }
} }
@ -296,7 +318,7 @@ abstract public class SyncManager {
final String fileName = local.getFileName(); 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) // generate entity to upload (VCard, iCal, whatever)
RequestBody body = prepareUpload(local); RequestBody body = prepareUpload(local);
@ -304,25 +326,25 @@ abstract public class SyncManager {
try { try {
if (local.getETag() == null) { if (local.getETag() == null) {
Constants.log.info("Uploading new record " + fileName); log.info("Uploading new record " + fileName);
remote.put(body, null, true); remote.put(body, null, true);
} else { } else {
Constants.log.info("Uploading locally modified record " + fileName); log.info("Uploading locally modified record " + fileName);
remote.put(body, local.getETag(), false); remote.put(body, local.getETag(), false);
} }
} catch (ConflictException|PreconditionFailedException e) { } catch (ConflictException|PreconditionFailedException e) {
// we can't interact with the user to resolve the conflict, so we treat 409 like 412 // 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; String eTag = null;
GetETag newETag = (GetETag) remote.properties.get(GetETag.NAME); GetETag newETag = (GetETag) remote.properties.get(GetETag.NAME);
if (newETag != null) { if (newETag != null) {
eTag = newETag.eTag; eTag = newETag.eTag;
Constants.log.debug("Received new ETag=" + eTag + " after uploading"); log.debug("Received new ETag=" + eTag + " after uploading");
} else } 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); local.clearDirty(eTag);
} }
@ -343,12 +365,12 @@ abstract public class SyncManager {
String localCTag = null; String localCTag = null;
if (extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL)) if (extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL))
Constants.log.info("Manual sync, ignoring CTag"); log.info("Manual sync, ignoring CTag");
else else
localCTag = localCollection.getCTag(); localCTag = localCollection.getCTag();
if (remoteCTag != null && remoteCTag.equals(localCTag)) { 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; return false;
} else } else
return true; return true;
@ -362,7 +384,7 @@ abstract public class SyncManager {
LocalResource[] localList = localCollection.getAll(); LocalResource[] localList = localCollection.getAll();
localResources = new HashMap<>(localList.length); localResources = new HashMap<>(localList.length);
for (LocalResource resource : localList) { for (LocalResource resource : localList) {
Constants.log.debug("Found local resource: " + resource.getFileName()); log.debug("Found local resource: " + resource.getFileName());
localResources.put(resource.getFileName(), resource); localResources.put(resource.getFileName(), resource);
} }
} }
@ -389,7 +411,7 @@ abstract public class SyncManager {
for (String localName : localResources.keySet()) { for (String localName : localResources.keySet()) {
DavResource remote = remoteResources.get(localName); DavResource remote = remoteResources.get(localName);
if (remote == null) { 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(); localResources.get(localName).delete();
syncResult.stats.numDeletes++; syncResult.stats.numDeletes++;
} else { } else {
@ -402,7 +424,7 @@ abstract public class SyncManager {
if (remoteETag.equals(localETag)) if (remoteETag.equals(localETag))
syncResult.stats.numSkippedEntries++; syncResult.stats.numSkippedEntries++;
else { 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); toDownload.add(remote);
} }
@ -413,7 +435,7 @@ abstract public class SyncManager {
// add all unseen (= remotely added) remote contacts // add all unseen (= remotely added) remote contacts
if (!remoteResources.isEmpty()) { 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()); 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 /* 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 (for instance, because another client has uploaded changes), because this will simply
cause all remote entries to be listed at the next sync. */ 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); localCollection.setCTag(remoteCTag);
} }

View File

@ -79,7 +79,7 @@ public class TasksSyncManager extends SyncManager {
Thread.currentThread().setContextClassLoader(context.getClassLoader()); // required for ical4j Thread.currentThread().setContextClassLoader(context.getClassLoader()); // required for ical4j
collectionURL = HttpUrl.parse(localTaskList().getSyncId()); collectionURL = HttpUrl.parse(localTaskList().getSyncId());
davCollection = new DavCalendar(httpClient, collectionURL); davCollection = new DavCalendar(log, httpClient, collectionURL);
} }
@Override @Override
@ -95,7 +95,7 @@ public class TasksSyncManager extends SyncManager {
int color = (pColor != null && pColor.color != null) ? pColor.color : LocalCalendar.defaultColor; int color = (pColor != null && pColor.color != null) ? pColor.color : LocalCalendar.defaultColor;
ContentValues values = new ContentValues(2); 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_NAME, displayName);
values.put(TaskLists.LIST_COLOR, color); values.put(TaskLists.LIST_COLOR, color);
localTaskList().update(values); localTaskList().update(values);
@ -117,21 +117,21 @@ public class TasksSyncManager extends SyncManager {
remoteResources = new HashMap<>(davCollection.members.size()); remoteResources = new HashMap<>(davCollection.members.size());
for (DavResource vCard : davCollection.members) { for (DavResource vCard : davCollection.members) {
String fileName = vCard.fileName(); String fileName = vCard.fileName();
Constants.log.debug("Found remote VTODO: " + fileName); log.debug("Found remote VTODO: " + fileName);
remoteResources.put(fileName, vCard); remoteResources.put(fileName, vCard);
} }
} }
@Override @Override
protected void downloadRemote() throws IOException, HttpException, DavException, CalendarStorageException { 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 // download new/updated iCalendars from server
for (DavResource[] bunch : ArrayUtils.partition(toDownload.toArray(new DavResource[toDownload.size()]), MAX_MULTIGET)) { for (DavResource[] bunch : ArrayUtils.partition(toDownload.toArray(new DavResource[toDownload.size()]), MAX_MULTIGET)) {
if (Thread.interrupted()) if (Thread.interrupted())
return; return;
Constants.log.info("Downloading " + StringUtils.join(bunch, ", ")); log.info("Downloading " + StringUtils.join(bunch, ", "));
if (bunch.length == 1) { if (bunch.length == 1) {
// only one contact, use GET // only one contact, use GET
@ -189,7 +189,7 @@ public class TasksSyncManager extends SyncManager {
try { try {
tasks = Task.fromStream(stream, charset); tasks = Task.fromStream(stream, charset);
} catch (InvalidCalendarException e) { } catch (InvalidCalendarException e) {
Constants.log.error("Received invalid iCalendar, ignoring", e); log.error("Received invalid iCalendar, ignoring", e);
return; return;
} }
@ -199,18 +199,18 @@ public class TasksSyncManager extends SyncManager {
// update local task, if it exists // update local task, if it exists
LocalTask localTask = (LocalTask)localResources.get(fileName); LocalTask localTask = (LocalTask)localResources.get(fileName);
if (localTask != null) { if (localTask != null) {
Constants.log.info("Updating " + fileName + " in local tasklist"); log.info("Updating " + fileName + " in local tasklist");
localTask.setETag(eTag); localTask.setETag(eTag);
localTask.update(newData); localTask.update(newData);
syncResult.stats.numUpdates++; syncResult.stats.numUpdates++;
} else { } 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 = new LocalTask(localTaskList(), newData, fileName, eTag);
localTask.add(); localTask.add();
syncResult.stats.numInserts++; syncResult.stats.numInserts++;
} }
} else } else
Constants.log.error("Received VCALENDAR with not exactly one VTODO; ignoring " + fileName); log.error("Received VCALENDAR with not exactly one VTODO; ignoring " + fileName);
} }
} }

View File

@ -26,7 +26,6 @@ public class AccountActivity extends Activity {
getActionBar().setDisplayHomeAsUpEnabled(true); getActionBar().setDisplayHomeAsUpEnabled(true);
final FragmentManager fm = getFragmentManager(); final FragmentManager fm = getFragmentManager();
AccountFragment fragment = (AccountFragment)fm.findFragmentById(R.id.account_fragment); AccountFragment fragment = (AccountFragment)fm.findFragmentById(R.id.account_fragment);
if (fragment == null) { if (fragment == null) {
fragment = new AccountFragment(); fragment = new AccountFragment();
@ -36,8 +35,9 @@ public class AccountActivity extends Activity {
fragment.setArguments(args); fragment.setArguments(args);
getFragmentManager().beginTransaction() getFragmentManager().beginTransaction()
.add(R.id.account_fragment, fragment) .add(R.id.account_fragment, fragment, SettingsActivity.TAG_ACCOUNT_SETTINGS)
.commit(); .commit();
} }
} }
} }

View File

@ -9,8 +9,10 @@
package at.bitfire.davdroid.ui.settings; package at.bitfire.davdroid.ui.settings;
import android.accounts.Account; import android.accounts.Account;
import android.app.AlertDialog;
import android.app.DialogFragment;
import android.content.DialogInterface;
import android.os.Bundle; import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference; import android.preference.EditTextPreference;
import android.preference.ListPreference; import android.preference.ListPreference;
import android.preference.Preference; import android.preference.Preference;
@ -22,7 +24,6 @@ import android.provider.ContactsContract;
import at.bitfire.davdroid.R; import at.bitfire.davdroid.R;
import at.bitfire.davdroid.syncadapter.AccountSettings; import at.bitfire.davdroid.syncadapter.AccountSettings;
import at.bitfire.ical4android.TaskProvider; import at.bitfire.ical4android.TaskProvider;
import ezvcard.VCardVersion;
public class AccountFragment extends PreferenceFragment { public class AccountFragment extends PreferenceFragment {
final static String ARG_ACCOUNT = "account"; final static String ARG_ACCOUNT = "account";
@ -36,44 +37,41 @@ public class AccountFragment extends PreferenceFragment {
addPreferencesFromResource(R.xml.settings_account_prefs); addPreferencesFromResource(R.xml.settings_account_prefs);
account = getArguments().getParcelable(ARG_ACCOUNT); account = getArguments().getParcelable(ARG_ACCOUNT);
readFromAccount(); refresh();
} }
public void readFromAccount() { public void refresh() {
final AccountSettings settings = new AccountSettings(getActivity(), account); final AccountSettings settings = new AccountSettings(getActivity(), account);
// category: authentication // category: authentication
final EditTextPreference prefUserName = (EditTextPreference)findPreference("username"); final EditTextPreference prefUserName = (EditTextPreference)findPreference("username");
prefUserName.setSummary(settings.getUserName()); prefUserName.setSummary(settings.username());
prefUserName.setText(settings.getUserName()); prefUserName.setText(settings.username());
prefUserName.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { prefUserName.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override @Override
public boolean onPreferenceChange(Preference preference, Object newValue) { public boolean onPreferenceChange(Preference preference, Object newValue) {
settings.setUserName((String)newValue); settings.username((String) newValue);
readFromAccount(); refresh(); return false;
return true;
} }
}); });
final EditTextPreference prefPassword = (EditTextPreference)findPreference("password"); final EditTextPreference prefPassword = (EditTextPreference)findPreference("password");
prefPassword.setText(settings.getPassword()); prefPassword.setText(settings.password());
prefPassword.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { prefPassword.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override @Override
public boolean onPreferenceChange(Preference preference, Object newValue) { public boolean onPreferenceChange(Preference preference, Object newValue) {
settings.setPassword((String)newValue); settings.password((String) newValue);
readFromAccount(); refresh(); return false;
return true;
} }
}); });
final SwitchPreference prefPreemptive = (SwitchPreference)findPreference("preemptive"); final SwitchPreference prefPreemptive = (SwitchPreference)findPreference("preemptive");
prefPreemptive.setChecked(settings.getPreemptiveAuth()); prefPreemptive.setChecked(settings.preemptiveAuth());
prefPreemptive.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { prefPreemptive.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override @Override
public boolean onPreferenceChange(Preference preference, Object newValue) { public boolean onPreferenceChange(Preference preference, Object newValue) {
settings.setPreemptiveAuth((Boolean)newValue); settings.preemptiveAuth((Boolean) newValue);
readFromAccount(); refresh(); return false;
return true;
} }
}); });
@ -90,8 +88,7 @@ public class AccountFragment extends PreferenceFragment {
@Override @Override
public boolean onPreferenceChange(Preference preference, Object newValue) { public boolean onPreferenceChange(Preference preference, Object newValue) {
settings.setSyncInterval(ContactsContract.AUTHORITY, Long.parseLong((String) newValue)); settings.setSyncInterval(ContactsContract.AUTHORITY, Long.parseLong((String) newValue));
readFromAccount(); refresh(); return false;
return true;
} }
}); });
} else { } else {
@ -111,8 +108,7 @@ public class AccountFragment extends PreferenceFragment {
@Override @Override
public boolean onPreferenceChange(Preference preference, Object newValue) { public boolean onPreferenceChange(Preference preference, Object newValue) {
settings.setSyncInterval(CalendarContract.AUTHORITY, Long.parseLong((String) newValue)); settings.setSyncInterval(CalendarContract.AUTHORITY, Long.parseLong((String) newValue));
readFromAccount(); refresh(); return false;
return true;
} }
}); });
} else { } else {
@ -132,8 +128,7 @@ public class AccountFragment extends PreferenceFragment {
@Override @Override
public boolean onPreferenceChange(Preference preference, Object newValue) { public boolean onPreferenceChange(Preference preference, Object newValue) {
settings.setSyncInterval(TaskProvider.ProviderName.OpenTasks.authority, Long.parseLong((String) newValue)); settings.setSyncInterval(TaskProvider.ProviderName.OpenTasks.authority, Long.parseLong((String) newValue));
readFromAccount(); refresh(); return false;
return true;
} }
}); });
} else { } else {
@ -141,5 +136,80 @@ public class AccountFragment extends PreferenceFragment {
prefSyncTasks.setSummary(R.string.settings_sync_summary_not_available); 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();
}
}
} }

View File

@ -17,7 +17,7 @@ import android.os.Bundle;
import at.bitfire.davdroid.R; import at.bitfire.davdroid.R;
public class SettingsActivity extends Activity { 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; boolean tabletLayout;
@ -44,7 +44,7 @@ public class SettingsActivity extends Activity {
getFragmentManager().beginTransaction() getFragmentManager().beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.replace(R.id.right_pane, fragment) .replace(R.id.right_pane, fragment, TAG_ACCOUNT_SETTINGS)
.commit(); .commit();
} else { // phone layout } else { // phone layout
Intent intent = new Intent(getApplicationContext(), AccountActivity.class); Intent intent = new Intent(getApplicationContext(), AccountActivity.class);

View File

@ -110,7 +110,7 @@ public class QueryServerDialogFragment extends DialogFragment implements LoaderC
); );
try { try {
DavResourceFinder finder = new DavResourceFinder(context, serverInfo); DavResourceFinder finder = new DavResourceFinder(null, context, serverInfo);
finder.findResources(); finder.findResources();
} catch (URISyntaxException e) { } catch (URISyntaxException e) {
serverInfo.setErrorMessage(getContext().getString(R.string.exception_uri_syntax, e.getMessage())); serverInfo.setErrorMessage(getContext().getString(R.string.exception_uri_syntax, e.getMessage()));

View File

@ -154,10 +154,15 @@
<item>Every 4 hours</item> <item>Every 4 hours</item>
<item>Once a day</item> <item>Once a day</item>
</string-array> </string-array>
<string name="settings_carddav">Address book</string> <string name="settings_debug">Debugging</string>
<string name="settings_carddav_vcard4_support">VCard 4.0 support</string> <string name="settings_security_warning">Potential security risk!</string>
<string name="settings_carddav_vcard4_supported">Contacts are sent in VCard 4.0 format</string> <string name="settings_log_to_external_file">Log to external file</string>
<string name="settings_carddav_vcard4_not_supported">Contacts are sent in VCard 3.0 format</string> <string name="settings_log_to_external_file_confirmation">External log files will contain private data and be accessible by other apps. Turn off external logging and delete the log files after use.</string>
<string name="settings_log_to_external_file_on">Logs are written to external files (if possible)</string>
<string name="settings_log_to_external_file_off">Logs are written to ADB</string>
<string name="settings_log_verbose">Verbose logging</string>
<string name="settings_log_verbose_on">Log synchronization information and network traffic</string>
<string name="settings_log_verbose_off">Log only synchronization information</string>
<string name="settings_android_update_title">Android version update</string> <string name="settings_android_update_title">Android version update</string>
<string name="settings_android_update_description">Android version updates may have an impact on how DAVdroid works. If there are problems, please delete your DAVdroid accounts and add them again.</string> <string name="settings_android_update_description">Android version updates may have an impact on how DAVdroid works. If there are problems, please delete your DAVdroid accounts and add them again.</string>

View File

@ -59,10 +59,22 @@
</PreferenceCategory> </PreferenceCategory>
<!-- <PreferenceCategory android:title="@string/settings_debug">
<PreferenceCategory android:title="@string/settings_carddav">
<SwitchPreference
android:key="log_external_file"
android:persistent="false"
android:title="@string/settings_log_to_external_file"
android:summaryOn="@string/settings_log_to_external_file_on"
android:summaryOff="@string/settings_log_to_external_file_off" />
<SwitchPreference
android:key="log_verbose"
android:persistent="false"
android:title="@string/settings_log_verbose"
android:summaryOn="@string/settings_log_verbose_on"
android:summaryOff="@string/settings_log_verbose_off" />
</PreferenceCategory> </PreferenceCategory>
-->
</PreferenceScreen> </PreferenceScreen>

@ -1 +1 @@
Subproject commit e7218aeb8ad8a96620208140bc7b86f63bf05fad Subproject commit 47541c169b970a6991b0f2f6c589232009e258ad