1
0
mirror of https://github.com/etesync/android synced 2025-01-11 00:01:12 +00:00

Debug settings

* preference screen for debug settings
* allow to disable HTTP compression
* verbose network logging only when activated
* single HttpClient for all threads (before: single HttpClient per adapter)
* bump version to 0.5.13-alpha
This commit is contained in:
rfc2822 2014-05-08 21:18:45 +02:00
parent ec94fe61fd
commit 3e4efe8b82
10 changed files with 182 additions and 68 deletions

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="at.bitfire.davdroid"
android:versionCode="35"
android:versionName="0.5.12-alpha" android:installLocation="internalOnly">
android:versionCode="36"
android:versionName="0.5.13-alpha" android:installLocation="internalOnly">
<uses-sdk
android:minSdkVersion="14"
@ -20,7 +20,8 @@
android:allowBackup="false"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
android:theme="@style/AppTheme"
android:process=":sync" >
<service
android:name=".syncadapter.AccountAuthenticatorService"
android:exported="false" >
@ -34,8 +35,7 @@
</service>
<service
android:name=".syncadapter.ContactsSyncAdapterService"
android:exported="true"
android:process=":sync" >
android:exported="true" >
<intent-filter>
<action android:name="android.content.SyncAdapter" />
</intent-filter>
@ -49,8 +49,7 @@
</service>
<service
android:name=".syncadapter.CalendarsSyncAdapterService"
android:exported="true"
android:process=":sync" >
android:exported="true" >
<intent-filter>
<action android:name="android.content.SyncAdapter" />
</intent-filter>
@ -72,6 +71,9 @@
android:name=".syncadapter.AddAccountActivity"
android:excludeFromRecents="true" >
</activity>
</application>
<activity
android:name=".syncadapter.GeneralSettingsActivity" >
</activity>
</application>
</manifest>

View File

@ -107,4 +107,13 @@
<string name="account_name_info">&quot;Verwenden Sie Ihre Email-Adresse als Kontoname, da Android den Kontonamen als ORGANIZER-Feld in Terminen benutzt. Sie können keine zwei Konten mit dem gleichen Namen anlegen.</string>
<string name="read_only">schreibgeschützt</string>
<string name="general_settings">Allgemeine Einstellungen</string>
<string name="debug_settings">Einstellungen zur Fehlersuche</string>
<string name="disable_http_compression">HTTP-Komprimierung deaktivieren</string>
<string name="http_compression_disabled">HTTP-Komprimierung ist deaktiviert (zur Fehlersuche)</string>
<string name="http_compression_enabled">HTTP-Komprimierung wird verwendet, falls möglich</string>
<string name="network_logging">Netzwerkverkehr aufzeichnen</string>
<string name="network_logging_enabled">Der gesamte Netzwerkverkehr wird in den Android-Logs mitgeschrieben (zur Fehlersuche)</string>
<string name="network_logging_disabled">Netzwerkverkehr wird nicht aufgezeichnet</string>
</resources>

View File

@ -113,5 +113,14 @@
<string name="organizer_hint">"ORGANIZER of your events; required if you use attendee info"</string>
<string name="account_name_info">"Use your email address as account name because Android will use the account name as ORGANIZER field for events you create. You can't have two accounts with the same name.</string>
<string name="read_only">read-only</string>
<string name="general_settings">General settings</string>
<string name="debug_settings">Debug settings</string>
<string name="disable_http_compression">Disable HTTP compression</string>
<string name="http_compression_disabled">HTTP compression is disabled (debug mode)</string>
<string name="http_compression_enabled">HTTP compression is used whenever possible</string>
<string name="network_logging">Log network traffic</string>
<string name="network_logging_enabled">All network traffic is being logged verbosely (debug mode)</string>
<string name="network_logging_disabled">Network traffic is not being logged</string>
</resources>

View File

@ -1,11 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
<Preference android:title="@string/davdroid_help" >
<PreferenceScreen android:title="@string/davdroid_help" >
<intent
android:targetPackage="at.bitfire.davdroid"
android:targetClass="at.bitfire.davdroid.MainActivity"
android:action="ACTION_VIEW" />
</Preference>
</PreferenceScreen>
<PreferenceCategory android:title="@string/general_settings" />
<PreferenceScreen android:title="@string/debug_settings">
<intent
android:targetPackage="at.bitfire.davdroid"
android:targetClass="at.bitfire.davdroid.syncadapter.GeneralSettingsActivity"
android:action="ACTION_VIEW" />
</PreferenceScreen>
</PreferenceScreen>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
<CheckBoxPreference
android:key="disable_compression"
android:summaryOff="@string/http_compression_enabled"
android:summaryOn="@string/http_compression_disabled"
android:title="@string/disable_http_compression" />
<CheckBoxPreference android:title="@string/network_logging" android:summaryOn="@string/network_logging_enabled" android:key="network_logging" android:summaryOff="@string/network_logging_disabled"/>
</PreferenceScreen>

View File

@ -12,9 +12,10 @@ package at.bitfire.davdroid;
public class Constants {
public static final String
APP_VERSION = "0.5.12-alpha",
APP_VERSION = "0.5.13-alpha",
ACCOUNT_TYPE = "bitfire.at.davdroid",
WEB_URL_HELP = "http://davdroid.bitfire.at/configuration?pk_campaign=davdroid-app",
WEB_URL_HELP = "http://davdroid.bitfire.at/configuration?pk_campaign=davdroid-app";
SETTING_DISABLE_COMPRESSION = "disable_compression",
SETTING_NETWORK_LOGGING = "network_logging";
}

View File

@ -15,10 +15,10 @@ import java.io.IOException;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import lombok.Getter;
import org.apache.http.HttpStatus;
import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient;
import lombok.Getter;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.AbstractThreadedSyncAdapter;
@ -36,6 +36,7 @@ import at.bitfire.davdroid.resource.RemoteCollection;
import at.bitfire.davdroid.webdav.DavException;
import at.bitfire.davdroid.webdav.DavHttpClient;
import at.bitfire.davdroid.webdav.HttpException;
import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient;
public abstract class DavSyncAdapter extends AbstractThreadedSyncAdapter implements Closeable {
private final static String TAG = "davdroid.DavSyncAdapter";
@ -44,8 +45,17 @@ public abstract class DavSyncAdapter extends AbstractThreadedSyncAdapter impleme
protected AccountManager accountManager;
protected CloseableHttpClient httpClient = DavHttpClient.create();
final ReentrantReadWriteLock httpClientLock = new ReentrantReadWriteLock();
/* We use one static httpClient for
* - all sync adapters (CalendarsSyncAdapter, ContactsSyncAdapter)
* - and all threads (= accounts) of each sync adapter
* so that HttpClient's threaded pool management can do its best.
*/
protected static CloseableHttpClient httpClient;
/* One static read/write lock pair for the static httpClient:
* Use the READ lock when httpClient will only be called (to prevent it from being unset while being used).
* Use the WRITE lock when httpClient will be modified (set/unset). */
private final static ReentrantReadWriteLock httpClientLock = new ReentrantReadWriteLock();
public DavSyncAdapter(Context context) {
@ -61,14 +71,18 @@ public abstract class DavSyncAdapter extends AbstractThreadedSyncAdapter impleme
@Override
public void close() {
// apparently may be called from a GUI thread
Log.d(TAG, "Closing httpClient");
// may be called from a GUI thread, so we need an AsyncTask
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
try {
httpClientLock.writeLock().lock();
httpClient.close();
httpClient = null;
if (httpClient != null) {
httpClient.close();
httpClient = null;
}
httpClientLock.writeLock().unlock();
} catch (IOException e) {
Log.w(TAG, "Couldn't close HTTP client", e);
@ -78,7 +92,6 @@ public abstract class DavSyncAdapter extends AbstractThreadedSyncAdapter impleme
}.execute();
}
protected abstract Map<LocalCollection<?>, RemoteCollection<?>> getSyncPairs(Account account, ContentProviderClient provider);
@ -89,42 +102,57 @@ public abstract class DavSyncAdapter extends AbstractThreadedSyncAdapter impleme
// set class loader for iCal4j ResourceLoader
Thread.currentThread().setContextClassLoader(getContext().getClassLoader());
Map<LocalCollection<?>, RemoteCollection<?>> syncCollections = getSyncPairs(account, provider);
if (syncCollections == null)
Log.i(TAG, "Nothing to synchronize");
else
try {
// prevent httpClient shutdown until we're ready
httpClientLock.readLock().lock();
for (Map.Entry<LocalCollection<?>, RemoteCollection<?>> entry : syncCollections.entrySet())
new SyncManager(entry.getKey(), entry.getValue()).synchronize(extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL), syncResult);
} catch (DavException ex) {
syncResult.stats.numParseExceptions++;
Log.e(TAG, "Invalid DAV response", ex);
} catch (HttpException ex) {
if (ex.getCode() == HttpStatus.SC_UNAUTHORIZED) {
Log.e(TAG, "HTTP Unauthorized " + ex.getCode(), ex);
syncResult.stats.numAuthExceptions++;
} else if (ex.isClientError()) {
Log.e(TAG, "Hard HTTP error " + ex.getCode(), ex);
// create httpClient, if necessary
httpClientLock.writeLock().lock();
if (httpClient == null) {
Log.d(TAG, "Creating new DavHttpClient");
httpClient = DavHttpClient.create(getContext());
}
// prevent httpClient shutdown until we're ready by holding a read lock
// acquiring read lock before releasing write lock will downgrade the write lock to a read lock
httpClientLock.readLock().lock();
httpClientLock.writeLock().unlock();
try {
// get local <-> remote collection pairs
Map<LocalCollection<?>, RemoteCollection<?>> syncCollections = getSyncPairs(account, provider);
if (syncCollections == null)
Log.i(TAG, "Nothing to synchronize");
else
try {
for (Map.Entry<LocalCollection<?>, RemoteCollection<?>> entry : syncCollections.entrySet())
new SyncManager(entry.getKey(), entry.getValue()).synchronize(extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL), syncResult);
} catch (DavException ex) {
syncResult.stats.numParseExceptions++;
} else {
Log.w(TAG, "Soft HTTP error " + ex.getCode() + " (Android will try again later)", ex);
Log.e(TAG, "Invalid DAV response", ex);
} catch (HttpException ex) {
if (ex.getCode() == HttpStatus.SC_UNAUTHORIZED) {
Log.e(TAG, "HTTP Unauthorized " + ex.getCode(), ex);
syncResult.stats.numAuthExceptions++;
} else if (ex.isClientError()) {
Log.e(TAG, "Hard HTTP error " + ex.getCode(), ex);
syncResult.stats.numParseExceptions++;
} else {
Log.w(TAG, "Soft HTTP error " + ex.getCode() + " (Android will try again later)", ex);
syncResult.stats.numIoExceptions++;
}
} catch (LocalStorageException ex) {
syncResult.databaseError = true;
Log.e(TAG, "Local storage (content provider) exception", ex);
} catch (IOException ex) {
syncResult.stats.numIoExceptions++;
Log.e(TAG, "I/O error (Android will try again later)", ex);
}
} catch (LocalStorageException ex) {
syncResult.databaseError = true;
Log.e(TAG, "Local storage (content provider) exception", ex);
} catch (IOException ex) {
syncResult.stats.numIoExceptions++;
Log.e(TAG, "I/O error (Android will try again later)", ex);
} finally {
// allow httpClient shutdown
httpClientLock.readLock().unlock();
}
} finally {
// allow httpClient shutdown
httpClientLock.readLock().unlock();
}
Log.i(TAG, "Sync complete for " + authority);
}
}

View File

@ -0,0 +1,30 @@
package at.bitfire.davdroid.syncadapter;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.preference.PreferenceFragment;
import at.bitfire.davdroid.R;
public class GeneralSettingsActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getFragmentManager().beginTransaction()
.replace(android.R.id.content, new GeneralSettingsFragment())
.commit();
}
public static class GeneralSettingsFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getPreferenceManager().setSharedPreferencesMode(Context.MODE_MULTI_PROCESS);
addPreferencesFromResource(R.xml.general_settings);
}
}
}

View File

@ -96,10 +96,12 @@ public class QueryServerDialogFragment extends DialogFragment implements LoaderC
static class ServerInfoLoader extends AsyncTaskLoader<ServerInfo> {
private static final String TAG = "davdroid.ServerInfoLoader";
Bundle args;
final Bundle args;
final Context context;
public ServerInfoLoader(Context context, Bundle args) {
super(context);
this.context = context;
this.args = args;
}
@ -112,7 +114,7 @@ public class QueryServerDialogFragment extends DialogFragment implements LoaderC
args.getBoolean(EXTRA_AUTH_PREEMPTIVE)
);
CloseableHttpClient httpClient = DavHttpClient.create();
CloseableHttpClient httpClient = DavHttpClient.create(context);
try {
// (1/5) detect capabilities
WebDavResource base = new WebDavResource(httpClient, new URI(serverInfo.getProvidedURL()), serverInfo.getUserName(),

View File

@ -10,6 +10,10 @@
******************************************************************************/
package at.bitfire.davdroid.webdav;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
import at.bitfire.davdroid.Constants;
import ch.boye.httpclientandroidlib.client.config.RequestConfig;
import ch.boye.httpclientandroidlib.config.Registry;
@ -17,11 +21,13 @@ import ch.boye.httpclientandroidlib.config.RegistryBuilder;
import ch.boye.httpclientandroidlib.conn.socket.ConnectionSocketFactory;
import ch.boye.httpclientandroidlib.conn.socket.PlainConnectionSocketFactory;
import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient;
import ch.boye.httpclientandroidlib.impl.client.HttpClientBuilder;
import ch.boye.httpclientandroidlib.impl.client.HttpClients;
import ch.boye.httpclientandroidlib.impl.conn.ManagedHttpClientConnectionFactory;
import ch.boye.httpclientandroidlib.impl.conn.PoolingHttpClientConnectionManager;
public class DavHttpClient {
private final static String TAG = "davdroid.DavHttpClient";
private final static RequestConfig defaultRqConfig;
private final static Registry<ConnectionSocketFactory> socketFactoryRegistry;
@ -38,28 +44,34 @@ public class DavHttpClient {
.setSocketTimeout(20*1000)
.setStaleConnectionCheckEnabled(false)
.build();
// enable logging
ManagedHttpClientConnectionFactory.INSTANCE.wirelog.enableDebug(true);
ManagedHttpClientConnectionFactory.INSTANCE.log.enableDebug(true);
}
public static CloseableHttpClient create() {
public static CloseableHttpClient create(Context context) {
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
// limits per DavHttpClient (= per DavSyncAdapter extends AbstractThreadedSyncAdapter)
connectionManager.setMaxTotal(2); // max. 2 connections in total
connectionManager.setMaxTotal(3); // max. 3 connections in total
connectionManager.setDefaultMaxPerRoute(2); // max. 2 connections per host
return HttpClients.custom()
HttpClientBuilder builder = HttpClients.custom()
.useSystemProperties()
.setConnectionManager(connectionManager)
.setDefaultRequestConfig(defaultRqConfig)
.setRetryHandler(DavHttpRequestRetryHandler.INSTANCE)
.setUserAgent("DAVdroid/" + Constants.APP_VERSION)
.disableCookieManagement()
.build();
.disableCookieManagement();
// debug options
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
if (settings.getBoolean(Constants.SETTING_DISABLE_COMPRESSION, false))
builder = builder.disableContentCompression();
boolean networkLogging = settings.getBoolean(Constants.SETTING_NETWORK_LOGGING, false);
Log.d(TAG, "Network logging: " + networkLogging);
ManagedHttpClientConnectionFactory.INSTANCE.wirelog.enableDebug(networkLogging);
ManagedHttpClientConnectionFactory.INSTANCE.log.enableDebug(networkLogging);
return builder.build();
}
}