1
0
mirror of https://github.com/etesync/android synced 2024-11-22 16:08:13 +00:00

Sync manager optimization

* allow cancellation of synchronization within appropriate time
* sync error notification: use loader, show all accounts, show whether JB Workaround is installed, reorder
This commit is contained in:
Ricki Hirner 2015-10-17 11:33:35 +02:00
parent 4ecca76a95
commit 80231dd44b
No known key found for this signature in database
GPG Key ID: C4A212CF0B2B4566
10 changed files with 220 additions and 135 deletions

View File

@ -62,47 +62,10 @@ public class HttpClient extends OkHttpClient {
protected String username, password; protected String username, password;
public HttpClient() { protected HttpClient(Context context) {
super();
context = null;
initialize();
}
public HttpClient(Context context, String username, String password, boolean preemptive) {
super(); super();
this.context = context; this.context = context;
initialize();
// authentication
this.username = username;
this.password = password;
if (preemptive)
networkInterceptors().add(new PreemptiveAuthenticationInterceptor(username, password));
else
setAuthenticator(new BasicDigestAuthenticator(null, username, password));
}
/**
* Creates a new HttpClient (based on another one) which can be used to download external resources:
* 1. it does not use preemptive authentication
* 2. it only authenticates against a given host
* @param client user name and password from this client will be used
* @param host authentication will be restricted to this host
*/
public HttpClient(HttpClient client, String host) {
super();
context = client.context;
initialize();
username = client.username;
password = client.password;
setAuthenticator(new BasicDigestAuthenticator(host, username, password));
}
protected void initialize() {
if (context != null) { if (context != null) {
// use MemorizingTrustManager to manage self-signed certificates // use MemorizingTrustManager to manage self-signed certificates
MemorizingTrustManager mtm = new MemorizingTrustManager(context); MemorizingTrustManager mtm = new MemorizingTrustManager(context);
@ -131,6 +94,39 @@ public class HttpClient extends OkHttpClient {
enableLogs(); enableLogs();
} }
public HttpClient(Context context, String username, String password, boolean preemptive) {
this(context);
// authentication
this.username = username;
this.password = password;
if (preemptive)
networkInterceptors().add(new PreemptiveAuthenticationInterceptor(username, password));
else
setAuthenticator(new BasicDigestAuthenticator(null, username, password));
}
/**
* Creates a new HttpClient (based on another one) which can be used to download external resources:
* 1. it does not use preemptive authentication
* 2. it only authenticates against a given host
* @param client user name and password from this client will be used
* @param host authentication will be restricted to this host
*/
public HttpClient(HttpClient client, String host) {
this(client.context);
username = client.username;
password = client.password;
setAuthenticator(new BasicDigestAuthenticator(host, username, password));
}
// for testing (mock server doesn't need auth)
protected HttpClient() {
this(null, null, null, false);
}
protected void enableLogs() { protected void enableLogs() {
interceptors().add(loggingInterceptor); interceptors().add(loggingInterceptor);
} }

View File

@ -59,8 +59,8 @@ public class CalendarSyncManager extends SyncManager {
protected static final int MAX_MULTIGET = 20; protected static final int MAX_MULTIGET = 20;
public CalendarSyncManager(Context context, Account account, Bundle extras, SyncResult result, LocalCalendar calendar) { public CalendarSyncManager(Context context, Account account, Bundle extras, String authority, SyncResult result, LocalCalendar calendar) {
super(Constants.NOTIFICATION_CALENDAR_SYNC, context, account, extras, result); super(Constants.NOTIFICATION_CALENDAR_SYNC, context, account, extras, authority, result);
localCollection = calendar; localCollection = calendar;
} }
@ -130,6 +130,8 @@ public class CalendarSyncManager extends SyncManager {
// 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())
return;
Constants.log.info("Downloading " + StringUtils.join(bunch, ", ")); Constants.log.info("Downloading " + StringUtils.join(bunch, ", "));
if (bunch.length == 1) { if (bunch.length == 1) {

View File

@ -55,7 +55,7 @@ public class CalendarsSyncAdapterService extends Service {
try { try {
for (LocalCalendar calendar : (LocalCalendar[])LocalCalendar.find(account, provider, LocalCalendar.Factory.INSTANCE, CalendarContract.Calendars.SYNC_EVENTS + "!=0", null)) { for (LocalCalendar calendar : (LocalCalendar[])LocalCalendar.find(account, provider, LocalCalendar.Factory.INSTANCE, CalendarContract.Calendars.SYNC_EVENTS + "!=0", null)) {
Constants.log.info("Synchronizing calendar #" + calendar.getId() + ", URL: " + calendar.getName()); Constants.log.info("Synchronizing calendar #" + calendar.getId() + ", URL: " + calendar.getName());
CalendarSyncManager syncManager = new CalendarSyncManager(getContext(), account, extras, syncResult, calendar); CalendarSyncManager syncManager = new CalendarSyncManager(getContext(), account, extras, authority, syncResult, calendar);
syncManager.performSync(); syncManager.performSync();
} }
} catch (CalendarStorageException e) { } catch (CalendarStorageException e) {

View File

@ -48,7 +48,7 @@ public class ContactsSyncAdapterService extends Service {
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
Constants.log.info("Starting address book sync (" + authority + ")"); Constants.log.info("Starting address book sync (" + authority + ")");
ContactsSyncManager syncManager = new ContactsSyncManager(getContext(), account, extras, provider, syncResult); ContactsSyncManager syncManager = new ContactsSyncManager(getContext(), account, extras, authority, provider, syncResult);
syncManager.performSync(); syncManager.performSync();
Constants.log.info("Address book sync complete"); Constants.log.info("Address book sync complete");

View File

@ -63,8 +63,8 @@ public class ContactsSyncManager extends SyncManager {
protected boolean hasVCard4; protected boolean hasVCard4;
public ContactsSyncManager(Context context, Account account, Bundle extras, ContentProviderClient provider, SyncResult result) { public ContactsSyncManager(Context context, Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult result) {
super(Constants.NOTIFICATION_CONTACTS_SYNC, context, account, extras, result); super(Constants.NOTIFICATION_CONTACTS_SYNC, context, account, extras, authority, result);
this.provider = provider; this.provider = provider;
} }
@ -140,6 +140,9 @@ public class ContactsSyncManager extends SyncManager {
// download new/updated VCards from server // download new/updated VCards 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())
return;
Constants.log.info("Downloading " + StringUtils.join(bunch, ", ")); Constants.log.info("Downloading " + StringUtils.join(bunch, ", "));
if (bunch.length == 1) { if (bunch.length == 1) {

View File

@ -31,6 +31,7 @@ import java.util.Set;
import java.util.UUID; import java.util.UUID;
import at.bitfire.dav4android.DavResource; import at.bitfire.dav4android.DavResource;
import at.bitfire.dav4android.exception.ConflictException;
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.dav4android.exception.UnauthorizedException; import at.bitfire.dav4android.exception.UnauthorizedException;
@ -68,6 +69,7 @@ abstract public class SyncManager {
protected final Context context; protected final Context context;
protected final Account account; protected final Account account;
protected final Bundle extras; protected final Bundle extras;
protected final String authority;
protected final SyncResult syncResult; protected final SyncResult syncResult;
protected final AccountSettings settings; protected final AccountSettings settings;
@ -92,10 +94,11 @@ abstract public class SyncManager {
public SyncManager(int notificationId, Context context, Account account, Bundle extras, SyncResult syncResult) { public SyncManager(int notificationId, Context context, Account account, Bundle extras, String authority, SyncResult syncResult) {
this.context = context; this.context = context;
this.account = account; this.account = account;
this.extras = extras; this.extras = extras;
this.authority = authority;
this.syncResult = syncResult; this.syncResult = syncResult;
// get account settings and generate httpClient // get account settings and generate httpClient
@ -115,6 +118,8 @@ abstract public class SyncManager {
Constants.log.info("Preparing synchronization"); Constants.log.info("Preparing synchronization");
prepare(); prepare();
if (Thread.interrupted())
return;
syncPhase = SYNC_PHASE_QUERY_CAPABILITIES; syncPhase = SYNC_PHASE_QUERY_CAPABILITIES;
Constants.log.info("Querying capabilities"); Constants.log.info("Querying capabilities");
queryCapabilities(); queryCapabilities();
@ -123,6 +128,8 @@ abstract public class SyncManager {
Constants.log.info("Processing locally deleted entries"); Constants.log.info("Processing locally deleted entries");
processLocallyDeleted(); processLocallyDeleted();
if (Thread.interrupted())
return;
syncPhase = SYNC_PHASE_PREPARE_DIRTY; syncPhase = SYNC_PHASE_PREPARE_DIRTY;
Constants.log.info("Locally preparing dirty entries"); Constants.log.info("Locally preparing dirty entries");
prepareDirty(); prepareDirty();
@ -138,10 +145,14 @@ abstract public class SyncManager {
Constants.log.info("Listing local entries"); Constants.log.info("Listing local entries");
listLocal(); listLocal();
if (Thread.interrupted())
return;
syncPhase = SYNC_PHASE_LIST_REMOTE; syncPhase = SYNC_PHASE_LIST_REMOTE;
Constants.log.info("Listing remote entries"); Constants.log.info("Listing remote entries");
listRemote(); listRemote();
if (Thread.interrupted())
return;
syncPhase = SYNC_PHASE_COMPARE_LOCAL_REMOTE; syncPhase = SYNC_PHASE_COMPARE_LOCAL_REMOTE;
Constants.log.info("Comparing local/remote entries"); Constants.log.info("Comparing local/remote entries");
compareLocalRemote(); compareLocalRemote();
@ -197,6 +208,7 @@ abstract public class SyncManager {
detailsIntent = new Intent(context, DebugInfoActivity.class); detailsIntent = new Intent(context, DebugInfoActivity.class);
detailsIntent.putExtra(DebugInfoActivity.KEY_EXCEPTION, e); detailsIntent.putExtra(DebugInfoActivity.KEY_EXCEPTION, e);
detailsIntent.putExtra(DebugInfoActivity.KEY_ACCOUNT, account); detailsIntent.putExtra(DebugInfoActivity.KEY_ACCOUNT, account);
detailsIntent.putExtra(DebugInfoActivity.KEY_AUTHORITY, authority);
detailsIntent.putExtra(DebugInfoActivity.KEY_PHASE, syncPhase); detailsIntent.putExtra(DebugInfoActivity.KEY_PHASE, syncPhase);
} }
@ -225,7 +237,6 @@ abstract public class SyncManager {
notification = builder.getNotification(); notification = builder.getNotification();
} }
notificationManager.notify(account.name, notificationId, notification); notificationManager.notify(account.name, notificationId, notification);
} }
} }
@ -234,11 +245,18 @@ abstract public class SyncManager {
abstract protected void queryCapabilities() throws IOException, HttpException, DavException, CalendarStorageException, ContactsStorageException; abstract protected void queryCapabilities() throws IOException, HttpException, DavException, CalendarStorageException, ContactsStorageException;
/**
* Process locally deleted entries (DELETE them on the server as well).
* Checks Thread.interrupted() before each request to allow quick sync cancellation.
*/
protected void processLocallyDeleted() throws CalendarStorageException, ContactsStorageException { protected void processLocallyDeleted() throws CalendarStorageException, ContactsStorageException {
// Remove locally deleted entries from server (if they have a name, i.e. if they were uploaded before), // Remove locally deleted entries from server (if they have a name, i.e. if they were uploaded before),
// but only if they don't have changed on the server. Then finally remove them from the local address book. // but only if they don't have changed on the server. Then finally remove them from the local address book.
LocalResource[] localList = localCollection.getDeleted(); LocalResource[] localList = localCollection.getDeleted();
for (LocalResource local : localList) { for (LocalResource local : localList) {
if (Thread.interrupted())
return;
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"); Constants.log.info(fileName + " has been deleted locally -> deleting from server");
@ -246,7 +264,7 @@ abstract public class SyncManager {
new DavResource(httpClient, collectionURL.newBuilder().addPathSegment(fileName).build()) new DavResource(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"); Constants.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"); Constants.log.info("Removing local record #" + local.getId() + " which has been deleted locally and was never uploaded");
@ -266,9 +284,16 @@ abstract public class SyncManager {
abstract protected RequestBody prepareUpload(LocalResource resource) throws IOException, CalendarStorageException, ContactsStorageException; abstract protected RequestBody prepareUpload(LocalResource resource) throws IOException, CalendarStorageException, ContactsStorageException;
/**
* Uploads dirty records to the server, using a PUT request for each record.
* Checks Thread.interrupted() before each request to allow quick sync cancellation.
*/
protected void uploadDirty() throws IOException, HttpException, CalendarStorageException, ContactsStorageException { protected void uploadDirty() throws IOException, HttpException, CalendarStorageException, ContactsStorageException {
// upload dirty contacts // upload dirty contacts
for (LocalResource local : localCollection.getDirty()) { for (LocalResource local : localCollection.getDirty()) {
if (Thread.interrupted())
return;
final String fileName = local.getFileName(); final String fileName = local.getFileName();
DavResource remote = new DavResource(httpClient, collectionURL.newBuilder().addPathSegment(fileName).build()); DavResource remote = new DavResource(httpClient, collectionURL.newBuilder().addPathSegment(fileName).build());
@ -281,14 +306,13 @@ abstract public class SyncManager {
if (local.getETag() == null) { if (local.getETag() == null) {
Constants.log.info("Uploading new record " + fileName); Constants.log.info("Uploading new record " + fileName);
remote.put(body, null, true); remote.put(body, null, true);
// TODO handle 30x
} else { } else {
Constants.log.info("Uploading locally modified record " + fileName); Constants.log.info("Uploading locally modified record " + fileName);
remote.put(body, local.getETag(), false); remote.put(body, local.getETag(), false);
// TODO handle 30x
} }
} catch (PreconditionFailedException e) { } catch (ConflictException|PreconditionFailedException e) {
// we can't interact with the user to resolve the conflict, so we treat 409 like 412
Constants.log.info("Resource has been modified on the server before upload, ignoring", e); Constants.log.info("Resource has been modified on the server before upload, ignoring", e);
} }
@ -396,6 +420,7 @@ abstract public class SyncManager {
/** /**
* Downloads the remote resources in {@link #toDownload} and stores them locally. * Downloads the remote resources in {@link #toDownload} and stores them locally.
* Must check Thread.interrupted() periodically to allow quick sync cancellation.
*/ */
abstract protected void downloadRemote() throws IOException, HttpException, DavException, ContactsStorageException, CalendarStorageException; abstract protected void downloadRemote() throws IOException, HttpException, DavException, ContactsStorageException, CalendarStorageException;

View File

@ -61,7 +61,7 @@ public class TasksSyncAdapterService extends Service {
for (LocalTaskList taskList : (LocalTaskList[])LocalTaskList.find(account, provider, LocalTaskList.Factory.INSTANCE, null, null)) { for (LocalTaskList taskList : (LocalTaskList[])LocalTaskList.find(account, provider, LocalTaskList.Factory.INSTANCE, null, null)) {
Constants.log.info("Synchronizing task list #" + taskList.getId() + ", URL: " + taskList.getSyncId()); Constants.log.info("Synchronizing task list #" + taskList.getId() + ", URL: " + taskList.getSyncId());
TasksSyncManager syncManager = new TasksSyncManager(getContext(), account, extras, provider, syncResult, taskList); TasksSyncManager syncManager = new TasksSyncManager(getContext(), account, extras, authority, provider, syncResult, taskList);
syncManager.performSync(); syncManager.performSync();
} }
} catch (CalendarStorageException e) { } catch (CalendarStorageException e) {

View File

@ -62,8 +62,8 @@ public class TasksSyncManager extends SyncManager {
final protected TaskProvider provider; final protected TaskProvider provider;
public TasksSyncManager(Context context, Account account, Bundle extras, TaskProvider provider, SyncResult result, LocalTaskList taskList) { public TasksSyncManager(Context context, Account account, Bundle extras, String authority, TaskProvider provider, SyncResult result, LocalTaskList taskList) {
super(Constants.NOTIFICATION_TASK_SYNC, context, account, extras, result); super(Constants.NOTIFICATION_TASK_SYNC, context, account, extras, authority, result);
this.provider = provider; this.provider = provider;
localCollection = taskList; localCollection = taskList;
} }
@ -128,6 +128,9 @@ public class TasksSyncManager extends SyncManager {
// 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())
return;
Constants.log.info("Downloading " + StringUtils.join(bunch, ", ")); Constants.log.info("Downloading " + StringUtils.join(bunch, ", "));
if (bunch.length == 1) { if (bunch.length == 1) {

View File

@ -9,13 +9,19 @@
package at.bitfire.davdroid.ui; package at.bitfire.davdroid.ui;
import android.accounts.Account; import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Activity; import android.app.Activity;
import android.app.LoaderManager;
import android.content.AsyncTaskLoader;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.Loader;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Debug;
import android.provider.CalendarContract; import android.provider.CalendarContract;
import android.provider.ContactsContract; import android.provider.ContactsContract;
import android.text.TextUtils; import android.text.TextUtils;
@ -30,12 +36,14 @@ 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;
public class DebugInfoActivity extends Activity { public class DebugInfoActivity extends Activity implements LoaderManager.LoaderCallbacks<String> {
public static final String public static final String
KEY_EXCEPTION = "exception", KEY_EXCEPTION = "exception",
KEY_ACCOUNT = "account", KEY_ACCOUNT = "account",
KEY_AUTHORITY = "authority",
KEY_PHASE = "phase"; KEY_PHASE = "phase";
TextView tvReport;
String report; String report;
@Override @Override
@ -44,8 +52,10 @@ public class DebugInfoActivity extends Activity {
setContentView(R.layout.debug_info_activity); setContentView(R.layout.debug_info_activity);
TextView tvReport = (TextView)findViewById(R.id.text_report); tvReport = (TextView)findViewById(R.id.text_report);
tvReport.setText(report = generateReport(getIntent().getExtras())); //tvReport.setText(report = generateReport(getIntent().getExtras()));
getLoaderManager().initLoader(0, getIntent().getExtras(), this);
} }
@Override @Override
@ -66,86 +76,132 @@ public class DebugInfoActivity extends Activity {
} }
} }
@Override
protected String generateReport(Bundle extras) { public Loader<String> onCreateLoader(int id, Bundle args) {
Exception exception = null; return new ReportLoader(this, args);
Account account = null;
Integer phase = null;
if (extras != null) {
exception = (Exception) extras.getSerializable(KEY_EXCEPTION);
account = (Account) extras.getParcelable(KEY_ACCOUNT);
phase = extras.getInt(KEY_PHASE);
}
StringBuilder report = new StringBuilder();
try {
report.append(
"SYSTEM INFORMATION\n" +
"Android version: " + Build.VERSION.RELEASE + " (" + Build.DISPLAY + ")\n" +
"Device: " + Build.MANUFACTURER + " / " + Build.MODEL + " (" + Build.DEVICE + ")\n\n"
);
} catch (Exception ex) {
Constants.log.error("Couldn't get system details", ex);
}
try {
PackageManager pm = getPackageManager();
String installedFrom = pm.getInstallerPackageName(BuildConfig.APPLICATION_ID);
if (TextUtils.isEmpty(installedFrom))
installedFrom = "APK (directly)";
else {
PackageInfo installer = pm.getPackageInfo(installedFrom, PackageManager.GET_META_DATA);
if (installer != null)
installedFrom = pm.getApplicationLabel(installer.applicationInfo).toString();
}
report.append(
"SOFTWARE INFORMATION\n" +
"DAVdroid version: " + BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ") " + BuildConfig.buildTime.toString() + "\n" +
"Installed from: " + installedFrom + "\n\n"
);
} catch(Exception ex) {
Constants.log.error("Couldn't get software information", ex);
}
report.append(
"CONFIGURATION\n" +
"System-wide synchronization: " + (ContentResolver.getMasterSyncAutomatically() ? "automatically" : "manually") + " (overrides account settings)\n"
);
if (account != null)
report.append(
"Account name: " + account.name + "\n" +
"Address book synchronization: " + syncStatus(account, ContactsContract.AUTHORITY) + "\n" +
"Calendar synchronization: " + syncStatus(account, CalendarContract.AUTHORITY) + "\n" +
"OpenTasks synchronization: " + syncStatus(account, "org.dmfs.tasks") + "\n\n"
);
if (phase != null) {
report.append("SYNCHRONIZATION INFO\nSychronization phase: " + phase + "\n\n");
}
if (exception instanceof HttpException) {
HttpException http = (HttpException)exception;
if (http.request != null)
report.append("HTTP REQUEST:\n" + http.request + "\n\n");
if (http.response != null)
report.append("HTTP RESPONSE:\n" + http.response + "\n\n");
}
if (exception != null) {
report.append("STACK TRACE\n");
for (String stackTrace : ExceptionUtils.getRootCauseStackTrace(exception))
report.append(stackTrace + "\n");
}
return report.toString();
} }
protected static String syncStatus(Account account, String authority) { @Override
return ContentResolver.getIsSyncable(account, authority) > 0 ? public void onLoadFinished(Loader<String> loader, String data) {
(ContentResolver.getSyncAutomatically(account, ContactsContract.AUTHORITY) ? "automatically" : "manually") : if (data != null)
""; tvReport.setText(report = data);
}
@Override
public void onLoaderReset(Loader<String> loader) {
}
static class ReportLoader extends AsyncTaskLoader<String> {
final Bundle extras;
public ReportLoader(Context context, Bundle extras) {
super(context);
this.extras = extras;
}
@Override
protected void onStartLoading() {
forceLoad();
}
@Override
public String loadInBackground() {
Exception exception = null;
String authority = null;
Account account = null;
Integer phase = null;
if (extras != null) {
exception = (Exception)extras.getSerializable(KEY_EXCEPTION);
account = extras.getParcelable(KEY_ACCOUNT);
authority = extras.getString(KEY_AUTHORITY);
phase = extras.getInt(KEY_PHASE);
}
StringBuilder report = new StringBuilder();
// begin with most specific information
if (phase != null)
report.append("SYNCHRONIZATION INFO\nSynchronization phase: " + phase + "\n");
if (account != null)
report.append("Account name: " + account.name + "\n");
if (authority != null)
report.append("Authority: " + authority + "\n\n");
if (exception instanceof HttpException) {
HttpException http = (HttpException)exception;
if (http.request != null)
report.append("HTTP REQUEST:\n" + http.request + "\n\n");
if (http.response != null)
report.append("HTTP RESPONSE:\n" + http.response + "\n");
}
if (exception != null) {
report.append("STACK TRACE:\n");
for (String stackTrace : ExceptionUtils.getRootCauseStackTrace(exception))
report.append(stackTrace + "\n");
report.append("\n");
}
try {
PackageManager pm = getContext().getPackageManager();
String installedFrom = pm.getInstallerPackageName(BuildConfig.APPLICATION_ID);
if (TextUtils.isEmpty(installedFrom))
installedFrom = "APK (directly)";
else {
PackageInfo installer = pm.getPackageInfo(installedFrom, PackageManager.GET_META_DATA);
if (installer != null)
installedFrom = pm.getApplicationLabel(installer.applicationInfo).toString();
}
boolean workaroundInstalled = false;
try {
workaroundInstalled = pm.getPackageInfo("at.bitfire.davdroid.jbworkaround", 0) != null;
} catch(PackageManager.NameNotFoundException e) {}
report.append(
"SOFTWARE INFORMATION\n" +
"DAVdroid version: " + BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ") " + BuildConfig.buildTime.toString() + "\n" +
"Installed from: " + installedFrom + "\n" +
"JB Workaround installed: " + (workaroundInstalled ? "yes" : "no") + "\n\n"
);
} catch(Exception ex) {
Constants.log.error("Couldn't get software information", ex);
}
report.append(
"CONFIGURATION\n" +
"System-wide synchronization: " + (ContentResolver.getMasterSyncAutomatically() ? "automatically" : "manually") + "\n"
);
AccountManager accountManager = AccountManager.get(getContext());
for (Account acc : accountManager.getAccountsByType(Constants.ACCOUNT_TYPE)) {
report.append(
" Account: " + acc.name + "\n" +
" Address book synchronization: " + syncStatus(acc, ContactsContract.AUTHORITY) + "\n" +
" Calendar synchronization: " + syncStatus(acc, CalendarContract.AUTHORITY) + "\n" +
" OpenTasks synchronization: " + syncStatus(acc, "org.dmfs.tasks") + "\n\n"
);
}
try {
report.append(
"SYSTEM INFORMATION\n" +
"Android version: " + Build.VERSION.RELEASE + " (" + Build.DISPLAY + ")\n" +
"Device: " + Build.MANUFACTURER + " / " + Build.MODEL + " (" + Build.DEVICE + ")\n\n"
);
} catch (Exception ex) {
Constants.log.error("Couldn't get system details", ex);
}
return report.toString();
}
protected String syncStatus(Account account, String authority) {
return ContentResolver.getIsSyncable(account, authority) > 0 ?
(ContentResolver.getSyncAutomatically(account, ContactsContract.AUTHORITY) ? "automatically" : "manually") :
"";
}
} }
} }

@ -1 +1 @@
Subproject commit 2083d075d3b4a4b9ac0a930af1d019547d7dcf07 Subproject commit a22eb4eb193c8f22180369791df3671e1cab6f1c