1
0
mirror of https://github.com/etesync/android synced 2025-06-07 08:38:49 +00:00

Refactoring

* move AccountSettings up to package context
* HttpClient: take authentication from AccountSettings in the constructor
* App: provide global instance of MemorizingTrustManager
* App: provide global Java logger, optionally with verbose and external file logging
* LoginCredentials: moved from inner-class into setup package
This commit is contained in:
Ricki Hirner 2016-02-24 15:56:30 +01:00
parent 50f7006e59
commit 552f6b6936
25 changed files with 341 additions and 208 deletions

View File

@ -26,7 +26,7 @@ public class HttpClientTest extends InstrumentationTestCase {
@Override @Override
public void setUp() throws IOException { public void setUp() throws IOException {
httpClient = HttpClient.create(getInstrumentation().getTargetContext().getApplicationContext()); httpClient = HttpClient.create(getInstrumentation().getTargetContext().getApplicationContext(), null);
server = new MockWebServer(); server = new MockWebServer();
server.start(); server.start();

View File

@ -3,12 +3,12 @@ package at.bitfire.davdroid.resource;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import java.io.IOException; import java.io.IOException;
import java.net.URI;
import at.bitfire.dav4android.exception.DavException; import at.bitfire.dav4android.exception.DavException;
import at.bitfire.dav4android.exception.HttpException; import at.bitfire.dav4android.exception.HttpException;
import at.bitfire.davdroid.Constants;
import at.bitfire.davdroid.ui.setup.DavResourceFinder; import at.bitfire.davdroid.ui.setup.DavResourceFinder;
import at.bitfire.davdroid.ui.setup.LoginCredentialsFragment; import at.bitfire.davdroid.ui.setup.LoginCredentials;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.MockWebServer;
@ -30,7 +30,7 @@ public class DavResourceFinderTest extends InstrumentationTestCase {
public void testGetCurrentUserPrincipal() throws IOException, HttpException, DavException { public void testGetCurrentUserPrincipal() throws IOException, HttpException, DavException {
HttpUrl url = server.url("/dav"); HttpUrl url = server.url("/dav");
LoginCredentialsFragment.LoginCredentials credentials = new LoginCredentialsFragment.LoginCredentials(url.uri(), "admin", "12345", true); LoginCredentials credentials = new LoginCredentials(url.uri(), "admin", "12345", true);
DavResourceFinder finder = new DavResourceFinder(getInstrumentation().getTargetContext().getApplicationContext(), credentials); DavResourceFinder finder = new DavResourceFinder(getInstrumentation().getTargetContext().getApplicationContext(), credentials);
// positive test case // positive test case
@ -51,8 +51,8 @@ public class DavResourceFinderTest extends InstrumentationTestCase {
server.enqueue(new MockResponse() // OPTIONS response server.enqueue(new MockResponse() // OPTIONS response
.setResponseCode(200) .setResponseCode(200)
.setHeader("DAV", "addressbook")); .setHeader("DAV", "addressbook"));
HttpUrl principal = finder.getCurrentUserPrincipal(url, DavResourceFinder.Service.CARDDAV); URI principal = finder.getCurrentUserPrincipal(url, DavResourceFinder.Service.CARDDAV);
assertEquals(url.resolve("/principals/myself"), principal); assertEquals(url.resolve("/principals/myself").uri(), principal);
// negative test case: no current-user-principal // negative test case: no current-user-principal
server.enqueue(new MockResponse() server.enqueue(new MockResponse()

View File

@ -43,6 +43,7 @@
<!-- ical4android declares task access permissions --> <!-- ical4android declares task access permissions -->
<application <application
android:name=".App"
android:allowBackup="true" android:allowBackup="true"
android:fullBackupContent="false" android:fullBackupContent="false"
android:icon="@drawable/ic_launcher" android:icon="@drawable/ic_launcher"

View File

@ -5,7 +5,7 @@
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html * http://www.gnu.org/licenses/gpl.html
*/ */
package at.bitfire.davdroid.syncadapter; package at.bitfire.davdroid;
import android.accounts.Account; import android.accounts.Account;
import android.accounts.AccountManager; import android.accounts.AccountManager;
@ -33,8 +33,6 @@ import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.List; import java.util.List;
import at.bitfire.davdroid.Constants;
import at.bitfire.davdroid.R;
import at.bitfire.davdroid.resource.LocalAddressBook; import at.bitfire.davdroid.resource.LocalAddressBook;
import at.bitfire.vcard4android.ContactsStorageException; import at.bitfire.vcard4android.ContactsStorageException;
import lombok.Cleanup; import lombok.Cleanup;
@ -46,8 +44,6 @@ 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;
@ -129,15 +125,6 @@ public class AccountSettings {
public void preemptiveAuth(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
public Long getSyncInterval(String authority) { public Long getSyncInterval(String authority) {

View File

@ -0,0 +1,110 @@
/*
* Copyright © 2013 2016 Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.davdroid;
import android.app.Application;
import android.content.SharedPreferences;
import java.io.File;
import java.io.IOException;
import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import javax.net.ssl.HostnameVerifier;
import at.bitfire.dav4android.*;
import at.bitfire.davdroid.log.LogcatHandler;
import de.duenndns.ssl.MemorizingTrustManager;
import lombok.Getter;
import okhttp3.internal.tls.OkHostnameVerifier;
public class App extends Application implements SharedPreferences.OnSharedPreferenceChangeListener {
public static final String
PREF_FILE = "global",
PREF_LOG_TO_FILE = "log_to_file",
PREF_VERBOSE_LOGGING = "verbose_logging";
@Getter
private static SSLSocketFactoryCompat sslSocketFactoryCompat;
@Getter
private static HostnameVerifier hostnameVerifier;
public final static Logger log = Logger.getLogger("davdroid");
@Getter
private SharedPreferences preferences;
@Override
public void onCreate() {
super.onCreate();
preferences = getSharedPreferences(PREF_FILE, MODE_PRIVATE);
// initialize MemorizingTrustManager
MemorizingTrustManager mtm = new MemorizingTrustManager(this);
sslSocketFactoryCompat = new SSLSocketFactoryCompat(mtm);
hostnameVerifier = mtm.wrapHostnameVerifier(OkHostnameVerifier.INSTANCE);
reinitLogger();
preferences.registerOnSharedPreferenceChangeListener(this);
}
@Override
public void onTerminate() {
// will never be called on production devices
super.onTerminate();
preferences.unregisterOnSharedPreferenceChangeListener(this);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
reinitLogger();
}
public void reinitLogger() {
// don't use Android default logging, we have our own handlers
log.setUseParentHandlers(false);
// set logging level according to preferences
log.setLevel(preferences.getBoolean(PREF_VERBOSE_LOGGING, false) ? Level.ALL : Level.INFO);
// remove all handlers
for (Handler handler : log.getHandlers())
log.removeHandler(handler);
// add logcat handler
log.addHandler(LogcatHandler.INSTANCE);
// log to external file according to preferences
if (preferences.getBoolean(PREF_LOG_TO_FILE, false)) {
File dir = getExternalFilesDir(null);
if (dir != null)
try {
String pattern = new File(dir, "davdroid-%u.txt").toString();
log.info("Logging to external file: " + pattern);
FileHandler fileHandler = new FileHandler(pattern);
fileHandler.setFormatter(new SimpleFormatter());
log.addHandler(fileHandler);
} catch (IOException e) {
log.log(Level.SEVERE, "Can't create external log file", e);
}
else
log.severe("No external media found, can't create external log file");
}
}
}

View File

@ -40,7 +40,6 @@ import at.bitfire.dav4android.property.CalendarHomeSet;
import at.bitfire.dav4android.property.GroupMembership; import at.bitfire.dav4android.property.GroupMembership;
import at.bitfire.davdroid.model.CollectionInfo; import at.bitfire.davdroid.model.CollectionInfo;
import at.bitfire.davdroid.model.ServiceDB.*; import at.bitfire.davdroid.model.ServiceDB.*;
import at.bitfire.davdroid.syncadapter.AccountSettings;
import lombok.Cleanup; import lombok.Cleanup;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
@ -277,11 +276,7 @@ public class DavService extends Service {
@Cleanup Cursor cursor = db.query(Services._TABLE, new String[]{Services.ACCOUNT_NAME}, Services.ID + "=?", new String[] { String.valueOf(service) }, null, null, null); @Cleanup Cursor cursor = db.query(Services._TABLE, new String[]{Services.ACCOUNT_NAME}, Services.ID + "=?", new String[] { String.valueOf(service) }, null, null, null);
if (cursor.moveToNext()) { if (cursor.moveToNext()) {
Account account = new Account(cursor.getString(0), Constants.ACCOUNT_TYPE); Account account = new Account(cursor.getString(0), Constants.ACCOUNT_TYPE);
AccountSettings settings = new AccountSettings(DavService.this, account); return HttpClient.create(DavService.this, account);
OkHttpClient httpClient = HttpClient.create(DavService.this);
httpClient = HttpClient.addAuthentication(httpClient, settings.username(), settings.password(), settings.preemptiveAuth());
return httpClient;
} else } else
throw new IllegalArgumentException("Service not found"); throw new IllegalArgumentException("Service not found");
} }

View File

@ -8,11 +8,10 @@
package at.bitfire.davdroid; package at.bitfire.davdroid;
import android.accounts.Account;
import android.content.Context; import android.content.Context;
import android.os.Build; import android.os.Build;
import org.slf4j.Logger;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.StringReader; import java.io.StringReader;
@ -20,23 +19,24 @@ import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.Locale; import java.util.Locale;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import at.bitfire.dav4android.BasicDigestAuthenticator; import at.bitfire.dav4android.BasicDigestAuthenticator;
import at.bitfire.davdroid.syncadapter.AccountSettings; import at.bitfire.davdroid.log.ExternalFileLogger;
import de.duenndns.ssl.MemorizingTrustManager;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.text.format.DateFormat;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import okhttp3.Credentials; import okhttp3.Credentials;
import okhttp3.Interceptor; import okhttp3.Interceptor;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
import okhttp3.internal.tls.OkHostnameVerifier;
import okhttp3.logging.HttpLoggingInterceptor; import okhttp3.logging.HttpLoggingInterceptor;
public class HttpClient { public class HttpClient {
private static final int MAX_LOG_LINE_LENGTH = 85;
private static final OkHttpClient client = new OkHttpClient(); private static final OkHttpClient client = new OkHttpClient();
private static final UserAgentInterceptor userAgentInterceptor = new UserAgentInterceptor(); private static final UserAgentInterceptor userAgentInterceptor = new UserAgentInterceptor();
@ -49,15 +49,14 @@ public class HttpClient {
private HttpClient() { private HttpClient() {
} }
public static OkHttpClient create(Context context) { public static OkHttpClient create(Context context, Account account) {
OkHttpClient.Builder builder = client.newBuilder(); OkHttpClient.Builder builder = client.newBuilder();
if (context != null) { // use MemorizingTrustManager to manage self-signed certificates
// use MemorizingTrustManager to manage self-signed certificates if (App.getSslSocketFactoryCompat() != null)
MemorizingTrustManager mtm = new MemorizingTrustManager(context); builder.sslSocketFactory(App.getSslSocketFactoryCompat());
builder.sslSocketFactory(new SSLSocketFactoryCompat(mtm)); if (App.getHostnameVerifier() != null)
builder.hostnameVerifier(mtm.wrapHostnameVerifier(OkHostnameVerifier.INSTANCE)); builder.hostnameVerifier(App.getHostnameVerifier());
}
// set timeouts // set timeouts
builder.connectTimeout(30, TimeUnit.SECONDS); builder.connectTimeout(30, TimeUnit.SECONDS);
@ -73,58 +72,57 @@ public class HttpClient {
// add cookie store for non-persistent cookies (some services like Horde use cookies for session tracking) // add cookie store for non-persistent cookies (some services like Horde use cookies for session tracking)
builder.cookieJar(MemoryCookieStore.INSTANCE); builder.cookieJar(MemoryCookieStore.INSTANCE);
if (context != null && account != null) {
// use account settings for authentication and logging
AccountSettings settings = new AccountSettings(context, account);
if (settings.preemptiveAuth())
builder.addNetworkInterceptor(new PreemptiveAuthenticationInterceptor(settings.username(), settings.password()));
else
builder.authenticator(new BasicDigestAuthenticator(null, settings.username(), settings.password()));
}
if (App.log.isLoggable(Level.FINEST))
addLogger(builder, App.log);
return builder.build(); return builder.build();
} }
public static OkHttpClient addAuthentication(@NonNull OkHttpClient httpClient, @NonNull String username, @NonNull String password, boolean preemptive) { private static OkHttpClient.Builder addAuthentication(@NonNull OkHttpClient.Builder builder, @NonNull String username, @NonNull String password, boolean preemptive) {
OkHttpClient.Builder builder = httpClient.newBuilder();
if (preemptive) if (preemptive)
builder.addNetworkInterceptor(new PreemptiveAuthenticationInterceptor(username, password)); builder.addNetworkInterceptor(new PreemptiveAuthenticationInterceptor(username, password));
else else
builder.authenticator(new BasicDigestAuthenticator(null, username, password)); builder.authenticator(new BasicDigestAuthenticator(null, username, password));
return builder;
}
public static OkHttpClient addAuthentication(@NonNull OkHttpClient client, @NonNull String username, @NonNull String password, boolean preemptive) {
OkHttpClient.Builder builder = client.newBuilder();
addAuthentication(builder, username, password, preemptive);
return builder.build(); return builder.build();
} }
public static OkHttpClient addAuthentication(@NonNull OkHttpClient httpClient, @NonNull AccountSettings accountSettings) { public static OkHttpClient addAuthentication(@NonNull OkHttpClient client, @NonNull String host, @NonNull String username, @NonNull String password) {
return addAuthentication(httpClient, accountSettings.username(), accountSettings.password(), accountSettings.preemptiveAuth()); return client.newBuilder()
}
public static OkHttpClient addAuthentication(@NonNull OkHttpClient httpClient, @NonNull String host, @NonNull String username, @NonNull String password) {
return httpClient.newBuilder()
.authenticator(new BasicDigestAuthenticator(host, username, password)) .authenticator(new BasicDigestAuthenticator(host, username, password))
.build(); .build();
} }
public static OkHttpClient addLogger(@NonNull OkHttpClient httpClient, @NonNull final Logger logger) { private static OkHttpClient.Builder addLogger(@NonNull OkHttpClient.Builder builder, @NonNull final Logger logger) {
// enable verbose logs, if requested // trace-level logging add network traffic interceptor
if (logger.isTraceEnabled()) { HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() { @Override
@Override public void log(String message) {
public void log(String message) { logger.finest(message);
BufferedReader reader = new BufferedReader(new StringReader(message)); }
String line; });
try { loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
while ((line = reader.readLine()) != null) { return builder.addInterceptor(loggingInterceptor);
int len = line.length(); }
for (int pos = 0; pos < len; pos += MAX_LOG_LINE_LENGTH)
if (pos < len - MAX_LOG_LINE_LENGTH)
logger.trace(line.substring(pos, pos + MAX_LOG_LINE_LENGTH) + "\\");
else
logger.trace(line.substring(pos));
}
} catch(IOException e) {
// for some reason, we couldn't split our message
logger.trace(message);
}
}
});
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
return httpClient.newBuilder() public static OkHttpClient addLogger(@NonNull OkHttpClient client, @NonNull final Logger logger) {
.addInterceptor(loggingInterceptor) return addLogger(client.newBuilder(), logger).build();
.build();
} else
return httpClient;
} }

View File

@ -9,6 +9,7 @@
package at.bitfire.davdroid; package at.bitfire.davdroid;
import android.os.Build; import android.os.Build;
import android.support.annotation.NonNull;
import android.text.TextUtils; import android.text.TextUtils;
import java.io.IOException; import java.io.IOException;
@ -25,12 +26,10 @@ import java.util.Locale;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager; import javax.net.ssl.X509TrustManager;
import de.duenndns.ssl.MemorizingTrustManager; import de.duenndns.ssl.MemorizingTrustManager;
import lombok.Cleanup; import lombok.Cleanup;
import lombok.NonNull;
public class SSLSocketFactoryCompat extends SSLSocketFactory { public class SSLSocketFactoryCompat extends SSLSocketFactory {
@ -51,7 +50,7 @@ public class SSLSocketFactoryCompat extends SSLSocketFactory {
for (String protocol : socket.getSupportedProtocols()) for (String protocol : socket.getSupportedProtocols())
if (!protocol.toUpperCase(Locale.US).contains("SSL")) if (!protocol.toUpperCase(Locale.US).contains("SSL"))
protocols.add(protocol); protocols.add(protocol);
Constants.log.debug("Setting allowed TLS protocols: " + TextUtils.join(", ", protocols)); Constants.log.info("Setting allowed TLS protocols: " + TextUtils.join(", ", protocols));
SSLSocketFactoryCompat.protocols = protocols.toArray(new String[protocols.size()]); SSLSocketFactoryCompat.protocols = protocols.toArray(new String[protocols.size()]);
/* set up reasonable cipher suites */ /* set up reasonable cipher suites */
@ -92,7 +91,7 @@ public class SSLSocketFactoryCompat extends SSLSocketFactory {
HashSet<String> enabledCiphers = preferredCiphers; HashSet<String> enabledCiphers = preferredCiphers;
enabledCiphers.addAll(new HashSet<>(Arrays.asList(socket.getEnabledCipherSuites()))); enabledCiphers.addAll(new HashSet<>(Arrays.asList(socket.getEnabledCipherSuites())));
Constants.log.debug("Enabling (only) those TLS ciphers: " + TextUtils.join(", ", enabledCiphers)); Constants.log.info("Enabling (only) those TLS ciphers: " + TextUtils.join(", ", enabledCiphers));
SSLSocketFactoryCompat.cipherSuites = enabledCiphers.toArray(new String[enabledCiphers.size()]); SSLSocketFactoryCompat.cipherSuites = enabledCiphers.toArray(new String[enabledCiphers.size()]);
} }
} }
@ -112,15 +111,11 @@ public class SSLSocketFactoryCompat extends SSLSocketFactory {
} }
private void upgradeTLS(SSLSocket ssl) { private void upgradeTLS(SSLSocket ssl) {
if (protocols != null) { if (protocols != null)
Constants.log.trace("Setting allowed TLS protocols: " + TextUtils.join(", ", protocols));
ssl.setEnabledProtocols(protocols); ssl.setEnabledProtocols(protocols);
}
if (Build.VERSION.SDK_INT < 20 && cipherSuites != null) { if (cipherSuites != null)
Constants.log.trace("Setting allowed TLS ciphers: " + TextUtils.join(", ", cipherSuites));
ssl.setEnabledCipherSuites(cipherSuites); ssl.setEnabledCipherSuites(cipherSuites);
}
} }

View File

@ -34,7 +34,6 @@ public abstract class CustomLogger implements Logger {
protected String name; protected String name;
protected PrintWriter writer; protected PrintWriter writer;
protected boolean verbose;
// CUSTOM LOGGING METHODS // CUSTOM LOGGING METHODS
@ -69,36 +68,32 @@ public abstract class CustomLogger implements Logger {
@Override @Override
public boolean isTraceEnabled() { public boolean isTraceEnabled() {
return verbose; return true;
} }
@Override @Override
public void trace(String msg) { public void trace(String msg) {
if (verbose) log(PREFIX_TRACE, msg);
log(PREFIX_TRACE, msg);
} }
@Override @Override
public void trace(String format, Object arg) { public void trace(String format, Object arg) {
if (verbose) log(PREFIX_TRACE, format, arg);
log(PREFIX_TRACE, format, arg);
} }
@Override @Override
public void trace(String format, Object arg1, Object arg2) { public void trace(String format, Object arg1, Object arg2) {
if (verbose) log(PREFIX_TRACE, format, arg1, arg2); log(PREFIX_TRACE, format, arg1, arg2);
} }
@Override @Override
public void trace(String format, Object... arguments) { public void trace(String format, Object... arguments) {
if (verbose) log(PREFIX_TRACE, format, arguments);
log(PREFIX_TRACE, format, arguments);
} }
@Override @Override
public void trace(String msg, Throwable t) { public void trace(String msg, Throwable t) {
if (verbose) log(PREFIX_TRACE, msg, t);
log(PREFIX_TRACE, msg, t);
} }

View File

@ -19,9 +19,7 @@ import java.io.PrintWriter;
public class ExternalFileLogger extends CustomLogger implements Closeable { public class ExternalFileLogger extends CustomLogger implements Closeable {
public ExternalFileLogger(Context context, String fileName, boolean verbose) throws IOException { public ExternalFileLogger(Context context, String fileName) throws IOException {
this.verbose = verbose;
File dir = getDirectory(context); File dir = getDirectory(context);
if (dir == null) if (dir == null)
throw new IOException("External media not available for log creation"); throw new IOException("External media not available for log creation");

View File

@ -0,0 +1,67 @@
/*
* Copyright © 2013 2016 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.util.Log;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
public class LogcatHandler extends Handler {
private static final String TAG = "davdroid";
public static final LogcatHandler INSTANCE = new LogcatHandler();
private LogcatHandler() {
super();
setFormatter(AdbFormatter.INSTANCE);
setLevel(Level.ALL);
}
@Override
public void publish(LogRecord record) {
String line = getFormatter().format(record);
int level = record.getLevel().intValue();
if (level >= Level.SEVERE.intValue())
Log.e(TAG, line);
else if (level >= Level.WARNING.intValue())
Log.w(TAG, line);
else if (level >= Level.CONFIG.intValue())
Log.i(TAG, line);
else if (level >= Level.FINER.intValue())
Log.d(TAG, line);
else
Log.v(TAG, line);
}
@Override
public void flush() {
}
@Override
public void close() {
}
private static class AdbFormatter extends Formatter {
public static AdbFormatter INSTANCE = new AdbFormatter();
private AdbFormatter() {
}
@Override
public String format(LogRecord r) {
return String.format("[%s] %s", r.getSourceClassName(), r.getMessage());
}
}
}

View File

@ -14,16 +14,12 @@ import java.io.StringWriter;
import lombok.Getter; import lombok.Getter;
public class StringLogger extends CustomLogger { public class StringLogger extends CustomLogger {
@Getter protected final String name; @Getter protected final String name;
protected final StringWriter stringWriter = new StringWriter(); protected final StringWriter stringWriter = new StringWriter();
public StringLogger(String name) {
public StringLogger(String name, boolean verbose) {
this.name = name; this.name = name;
this.verbose = verbose;
writer = new PrintWriter(stringWriter); writer = new PrintWriter(stringWriter);
} }
@ -31,5 +27,4 @@ public class StringLogger extends CustomLogger {
return stringWriter.toString(); return stringWriter.toString();
} }
} }

View File

@ -290,7 +290,7 @@ public class ContactsSyncManager extends SyncManager {
return null; return null;
} }
OkHttpClient resourceClient = HttpClient.create(context); OkHttpClient resourceClient = HttpClient.create(context, null);
// authenticate only against a certain host, and only upon request // authenticate only against a certain host, and only upon request
resourceClient = HttpClient.addAuthentication(resourceClient, baseUrl.host(), settings.username(), settings.password()); resourceClient = HttpClient.addAuthentication(resourceClient, baseUrl.host(), settings.username(), settings.password());

View File

@ -39,6 +39,7 @@ package at.bitfire.davdroid.syncadapter;
import at.bitfire.dav4android.exception.UnauthorizedException; import at.bitfire.dav4android.exception.UnauthorizedException;
import at.bitfire.dav4android.property.GetCTag; import at.bitfire.dav4android.property.GetCTag;
import at.bitfire.dav4android.property.GetETag; import at.bitfire.dav4android.property.GetETag;
import at.bitfire.davdroid.AccountSettings;
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;
@ -107,21 +108,11 @@ abstract public class SyncManager {
this.authority = authority; this.authority = authority;
this.syncResult = syncResult; this.syncResult = syncResult;
// get account settings and log to file (if requested) // get account settings (for sync interval etc.)
settings = new AccountSettings(context, account); settings = new AccountSettings(context, account);
try {
if (settings.logToExternalFile())
log = new ExternalFileLogger(context, "davdroid-SyncManager-" + account.name + "-" + authority + ".txt", settings.logVerbose());
} catch(IOException e) {
Constants.log.error("Couldn't log to external file", e);
}
if (log == null)
log = Constants.log;
// create HttpClient with given logger // create HttpClient with given logger
httpClient = HttpClient.create(context); httpClient = HttpClient.create(context, account);
httpClient = HttpClient.addLogger(httpClient, log);
httpClient = HttpClient.addAuthentication(httpClient, 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);

View File

@ -19,7 +19,7 @@ import android.support.v7.preference.PreferenceFragmentCompat;
import android.support.v7.preference.SwitchPreferenceCompat; import android.support.v7.preference.SwitchPreferenceCompat;
import at.bitfire.davdroid.R; import at.bitfire.davdroid.R;
import at.bitfire.davdroid.syncadapter.AccountSettings; import at.bitfire.davdroid.AccountSettings;
import at.bitfire.ical4android.TaskProvider; import at.bitfire.ical4android.TaskProvider;
public class AccountSettingsFragment extends PreferenceFragmentCompat { public class AccountSettingsFragment extends PreferenceFragmentCompat {

View File

@ -20,6 +20,7 @@ import android.support.v7.widget.Toolbar;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import at.bitfire.davdroid.App;
import at.bitfire.davdroid.Constants; import at.bitfire.davdroid.Constants;
import at.bitfire.davdroid.R; import at.bitfire.davdroid.R;
import at.bitfire.davdroid.ui.setup.LoginActivity; import at.bitfire.davdroid.ui.setup.LoginActivity;

View File

@ -50,7 +50,6 @@ import at.bitfire.davdroid.model.CollectionInfo;
import at.bitfire.davdroid.model.Service; import at.bitfire.davdroid.model.Service;
import at.bitfire.davdroid.model.HomeSet; import at.bitfire.davdroid.model.HomeSet;
import at.bitfire.davdroid.model.ServiceDB; import at.bitfire.davdroid.model.ServiceDB;
import at.bitfire.davdroid.syncadapter.AccountSettings;
import lombok.Cleanup; import lombok.Cleanup;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
@ -195,8 +194,7 @@ public class CreateAddressBookActivity extends AppCompatActivity implements Load
@Override @Override
public Exception loadInBackground() { public Exception loadInBackground() {
OkHttpClient client = HttpClient.create(getContext()); OkHttpClient client = HttpClient.create(getContext(), account);
client = HttpClient.addAuthentication(client, new AccountSettings(getContext(), account));
StringWriter writer = new StringWriter(); StringWriter writer = new StringWriter();
try { try {

View File

@ -16,7 +16,6 @@ import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment; import android.support.v4.app.DialogFragment;
@ -39,7 +38,6 @@ import at.bitfire.davdroid.HttpClient;
import at.bitfire.davdroid.R; import at.bitfire.davdroid.R;
import at.bitfire.davdroid.model.CollectionInfo; import at.bitfire.davdroid.model.CollectionInfo;
import at.bitfire.davdroid.model.ServiceDB; import at.bitfire.davdroid.model.ServiceDB;
import at.bitfire.davdroid.syncadapter.AccountSettings;
import lombok.Cleanup; import lombok.Cleanup;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
@ -126,11 +124,6 @@ public class CreateCollectionFragment extends DialogFragment implements LoaderMa
@Override @Override
public Exception loadInBackground() { public Exception loadInBackground() {
Constants.log.info("MKCOl !!!!");
OkHttpClient client = HttpClient.create(getContext());
client = HttpClient.addAuthentication(client, new AccountSettings(getContext(), account));
StringWriter writer = new StringWriter(); StringWriter writer = new StringWriter();
try { try {
XmlSerializer serializer = XmlUtils.newSerializer(); XmlSerializer serializer = XmlUtils.newSerializer();
@ -212,6 +205,8 @@ public class CreateCollectionFragment extends DialogFragment implements LoaderMa
} }
ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(getContext()); ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(getContext());
OkHttpClient client = HttpClient.create(getContext(), account);
DavResource collection = new DavResource(null, client, HttpUrl.parse(info.url)); DavResource collection = new DavResource(null, client, HttpUrl.parse(info.url));
try { try {
// create collection on remote server // create collection on remote server

View File

@ -24,6 +24,7 @@ import android.provider.CalendarContract;
import android.provider.ContactsContract; import android.provider.ContactsContract;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.widget.TextView; import android.widget.TextView;
@ -40,7 +41,7 @@ import at.bitfire.dav4android.exception.HttpException;
import at.bitfire.davdroid.BuildConfig; import at.bitfire.davdroid.BuildConfig;
import at.bitfire.davdroid.Constants; import at.bitfire.davdroid.Constants;
import at.bitfire.davdroid.R; import at.bitfire.davdroid.R;
import at.bitfire.davdroid.syncadapter.AccountSettings; import at.bitfire.davdroid.AccountSettings;
public class DebugInfoActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<String> { public class DebugInfoActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<String> {
public static final String public static final String
@ -173,8 +174,7 @@ public class DebugInfoActivity extends AppCompatActivity implements LoaderManage
if (exception != null) { if (exception != null) {
report.append("\nSTACK TRACE:\n"); report.append("\nSTACK TRACE:\n");
for (String stackTrace : ExceptionUtils.getRootCauseStackTrace(exception)) report.append(Log.getStackTraceString(exception));
report.append(stackTrace + "\n");
report.append("\n"); report.append("\n");
} }

View File

@ -32,7 +32,6 @@ import at.bitfire.davdroid.HttpClient;
import at.bitfire.davdroid.R; import at.bitfire.davdroid.R;
import at.bitfire.davdroid.model.CollectionInfo; import at.bitfire.davdroid.model.CollectionInfo;
import at.bitfire.davdroid.model.ServiceDB; import at.bitfire.davdroid.model.ServiceDB;
import at.bitfire.davdroid.syncadapter.AccountSettings;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
@ -109,8 +108,7 @@ public class DeleteCollectionFragment extends DialogFragment implements LoaderMa
@Override @Override
public Exception loadInBackground() { public Exception loadInBackground() {
OkHttpClient httpClient = HttpClient.create(getContext()); OkHttpClient httpClient = HttpClient.create(getContext(), account);
httpClient = HttpClient.addAuthentication(httpClient, new AccountSettings(getContext(), account));
DavResource collection = new DavResource(null, httpClient, HttpUrl.parse(collectionInfo.url)); DavResource collection = new DavResource(null, httpClient, HttpUrl.parse(collectionInfo.url));
try { try {

View File

@ -35,7 +35,7 @@ import at.bitfire.davdroid.model.ServiceDB.Collections;
import at.bitfire.davdroid.model.ServiceDB.HomeSets; import at.bitfire.davdroid.model.ServiceDB.HomeSets;
import at.bitfire.davdroid.model.ServiceDB.OpenHelper; import at.bitfire.davdroid.model.ServiceDB.OpenHelper;
import at.bitfire.davdroid.model.ServiceDB.Services; import at.bitfire.davdroid.model.ServiceDB.Services;
import at.bitfire.davdroid.syncadapter.AccountSettings; import at.bitfire.davdroid.AccountSettings;
import at.bitfire.ical4android.TaskProvider; import at.bitfire.ical4android.TaskProvider;
import lombok.Cleanup; import lombok.Cleanup;

View File

@ -8,7 +8,7 @@
package at.bitfire.davdroid.ui.setup; package at.bitfire.davdroid.ui.setup;
import android.content.Context; import android.content.Context;
import android.text.TextUtils; import android.support.annotation.NonNull;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.xbill.DNS.Lookup; import org.xbill.DNS.Lookup;
@ -46,7 +46,6 @@ import at.bitfire.dav4android.property.SupportedCalendarComponentSet;
import at.bitfire.davdroid.HttpClient; import at.bitfire.davdroid.HttpClient;
import at.bitfire.davdroid.log.StringLogger; import at.bitfire.davdroid.log.StringLogger;
import at.bitfire.davdroid.model.CollectionInfo; import at.bitfire.davdroid.model.CollectionInfo;
import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.ToString; import lombok.ToString;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
@ -60,21 +59,21 @@ public class DavResourceFinder {
final String name; final String name;
Service(String name) { this.name = name;} Service(String name) { this.name = name;}
@Override public String toString() { return name; } @Override public String toString() { return name; }
}; }
protected final Context context; protected final Context context;
protected final LoginCredentialsFragment.LoginCredentials credentials; protected final LoginCredentials credentials;
protected final Logger log = new StringLogger("DavResourceFinder", true); protected final Logger log = new StringLogger("DavResourceFinder");
protected OkHttpClient httpClient; protected OkHttpClient httpClient;
public DavResourceFinder(@NonNull Context context, @NonNull LoginCredentialsFragment.LoginCredentials credentials) { public DavResourceFinder(@NonNull Context context, @NonNull LoginCredentials credentials) {
this.context = context; this.context = context;
this.credentials = credentials; this.credentials = credentials;
httpClient = HttpClient.create(context); httpClient = HttpClient.create(context, null);
httpClient = HttpClient.addLogger(httpClient, log);
httpClient = HttpClient.addAuthentication(httpClient, credentials.userName, credentials.password, credentials.authPreemptive); httpClient = HttpClient.addAuthentication(httpClient, credentials.userName, credentials.password, credentials.authPreemptive);
//httpClient = HttpClient.addLogger(httpClient, log);
} }

View File

@ -14,6 +14,7 @@ import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment; import android.support.v4.app.DialogFragment;
import android.support.v4.app.LoaderManager; import android.support.v4.app.LoaderManager;
import android.support.v4.content.AsyncTaskLoader; import android.support.v4.content.AsyncTaskLoader;
@ -31,9 +32,17 @@ import at.bitfire.davdroid.ui.DebugInfoActivity;
import lombok.Cleanup; import lombok.Cleanup;
public class DetectConfigurationFragment extends DialogFragment implements LoaderManager.LoaderCallbacks<Configuration> { public class DetectConfigurationFragment extends DialogFragment implements LoaderManager.LoaderCallbacks<Configuration> {
protected static final String ARG_LOGIN_CREDENTIALS = "credentials";
static final String ARG_LOGIN_CREDENTIALS = "credentials"; public static DetectConfigurationFragment newInstance(LoginCredentials credentials) {
DetectConfigurationFragment frag = new DetectConfigurationFragment();
Bundle args = new Bundle(1);
args.putParcelable(ARG_LOGIN_CREDENTIALS, credentials);
frag.setArguments(args);
return frag;
}
@NonNull
@Override @Override
public Dialog onCreateDialog(Bundle savedInstanceState) { public Dialog onCreateDialog(Bundle savedInstanceState) {
ProgressDialog dialog = new ProgressDialog(getActivity()); ProgressDialog dialog = new ProgressDialog(getActivity());
@ -55,7 +64,7 @@ public class DetectConfigurationFragment extends DialogFragment implements Loade
@Override @Override
public Loader<Configuration> onCreateLoader(int id, Bundle args) { public Loader<Configuration> onCreateLoader(int id, Bundle args) {
return new ServerConfigurationLoader(getContext(), args); return new ServerConfigurationLoader(getContext(), (LoginCredentials)args.getParcelable(ARG_LOGIN_CREDENTIALS));
} }
@Override @Override
@ -117,12 +126,12 @@ public class DetectConfigurationFragment extends DialogFragment implements Loade
static class ServerConfigurationLoader extends AsyncTaskLoader<Configuration> { static class ServerConfigurationLoader extends AsyncTaskLoader<Configuration> {
final Context context; final Context context;
final LoginCredentialsFragment.LoginCredentials credentials; final LoginCredentials credentials;
public ServerConfigurationLoader(Context context, Bundle args) { public ServerConfigurationLoader(Context context, LoginCredentials credentials) {
super(context); super(context);
this.context = context; this.context = context;
credentials = (LoginCredentialsFragment.LoginCredentials)args.getParcelable(ARG_LOGIN_CREDENTIALS); this.credentials = credentials;
} }
@Override @Override

View File

@ -0,0 +1,53 @@
/*
* Copyright © 2013 2016 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.ui.setup;
import android.os.Parcel;
import android.os.Parcelable;
import java.net.URI;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class LoginCredentials implements Parcelable {
public final URI uri;
public final String userName, password;
public final boolean authPreemptive;
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeSerializable(uri);
dest.writeString(userName);
dest.writeString(password);
dest.writeValue(authPreemptive);
}
public static final Creator CREATOR = new Creator<LoginCredentials>() {
@Override
public LoginCredentials createFromParcel(Parcel source) {
LoginCredentials credentials = new LoginCredentials(
(URI)source.readSerializable(),
source.readString(), source.readString(),
(boolean)source.readValue(null)
);
return credentials;
}
@Override
public LoginCredentials[] newArray(int size) {
return new LoginCredentials[size];
}
};
}

View File

@ -10,9 +10,6 @@ package at.bitfire.davdroid.ui.setup;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v7.widget.AppCompatCheckBox; import android.support.v7.widget.AppCompatCheckBox;
import android.support.v7.widget.AppCompatRadioButton; import android.support.v7.widget.AppCompatRadioButton;
@ -30,7 +27,6 @@ import java.net.URISyntaxException;
import at.bitfire.davdroid.R; import at.bitfire.davdroid.R;
import at.bitfire.davdroid.ui.widget.EditPassword; import at.bitfire.davdroid.ui.widget.EditPassword;
import lombok.RequiredArgsConstructor;
public class LoginCredentialsFragment extends Fragment implements CompoundButton.OnCheckedChangeListener { public class LoginCredentialsFragment extends Fragment implements CompoundButton.OnCheckedChangeListener {
@ -73,15 +69,8 @@ public class LoginCredentialsFragment extends Fragment implements CompoundButton
@Override @Override
public void onClick(View v) { public void onClick(View v) {
LoginCredentials credentials = validateLoginData(); LoginCredentials credentials = validateLoginData();
if (credentials != null) { if (credentials != null)
// login data OK, continue with DetectConfigurationFragment DetectConfigurationFragment.newInstance(credentials).show(getFragmentManager(), null);
Bundle args = new Bundle(1);
args.putParcelable(DetectConfigurationFragment.ARG_LOGIN_CREDENTIALS, credentials);
DialogFragment dialog = new DetectConfigurationFragment();
dialog.setArguments(args);
dialog.show(getFragmentManager(), DetectConfigurationFragment.class.getName());
}
} }
}); });
@ -126,20 +115,17 @@ public class LoginCredentialsFragment extends Fragment implements CompoundButton
URI uri = null; URI uri = null;
boolean valid = true; boolean valid = true;
String host = null, path = null;
int port = -1;
Uri baseUrl = Uri.parse(editBaseURL.getText().toString()); Uri baseUrl = Uri.parse(editBaseURL.getText().toString());
String scheme = baseUrl.getScheme(); String scheme = baseUrl.getScheme();
if ("https".equalsIgnoreCase(scheme) || "http".equalsIgnoreCase(scheme)) { if ("https".equalsIgnoreCase(scheme) || "http".equalsIgnoreCase(scheme)) {
host = IDN.toASCII(baseUrl.getHost()); String host = IDN.toASCII(baseUrl.getHost());
if (host.isEmpty()) { if (host.isEmpty()) {
editBaseURL.setError(getString(R.string.login_url_host_name_required)); editBaseURL.setError(getString(R.string.login_url_host_name_required));
valid = false; valid = false;
} }
path = baseUrl.getEncodedPath(); String path = baseUrl.getEncodedPath();
port = baseUrl.getPort(); int port = baseUrl.getPort();
try { try {
uri = new URI(baseUrl.getScheme(), null, host, port, path, null, null); uri = new URI(baseUrl.getScheme(), null, host, port, path, null, null);
} catch (URISyntaxException e) { } catch (URISyntaxException e) {
@ -169,42 +155,4 @@ public class LoginCredentialsFragment extends Fragment implements CompoundButton
return null; return null;
} }
@RequiredArgsConstructor
public static class LoginCredentials implements Parcelable {
public final URI uri;
public final String userName, password;
public final boolean authPreemptive;
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeSerializable(uri);
dest.writeString(userName);
dest.writeString(password);
dest.writeInt(authPreemptive ? 1 : 0);
}
public static final Parcelable.Creator CREATOR = new Parcelable.Creator<LoginCredentials>() {
@Override
public LoginCredentials createFromParcel(Parcel source) {
LoginCredentials credentials = new LoginCredentials(
(URI)source.readSerializable(),
source.readString(), source.readString(),
source.readInt() != 0 ? true : false
);
return null;
}
@Override
public LoginCredentials[] newArray(int size) {
return new LoginCredentials[0];
}
};
}
} }