1
0
mirror of https://github.com/etesync/android synced 2024-12-24 15:38:09 +00:00

Handle errors when syncing journals, not just entries.

Also create a helper notification manager that encapsulates all of the
notification creation logic (from throwable).
This commit is contained in:
Tom Hacohen 2017-02-07 23:09:38 +00:00
parent f2febfeb8c
commit 80bb0d6a70
5 changed files with 150 additions and 55 deletions

View File

@ -0,0 +1,84 @@
package at.bitfire.davdroid;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.support.v4.app.NotificationManagerCompat;
import android.support.v7.app.NotificationCompat;
import java.util.logging.Level;
import at.bitfire.davdroid.journalmanager.Exceptions;
import at.bitfire.davdroid.ui.AccountSettingsActivity;
import at.bitfire.davdroid.ui.DebugInfoActivity;
import at.bitfire.ical4android.CalendarStorageException;
import at.bitfire.vcard4android.ContactsStorageException;
import lombok.Getter;
public class NotificationHelper {
final NotificationManagerCompat notificationManager;
final Context context;
final String notificationTag;
final int notificationId;
@Getter
Intent detailsIntent;
int messageString;
public NotificationHelper(Context context, String notificationTag, int notificationId) {
this.notificationManager = NotificationManagerCompat.from(context);
this.context = context;
this.notificationTag = notificationTag;
this.notificationId = notificationId;
}
public void setThrowable(Throwable e) {
if (e instanceof Exceptions.UnauthorizedException) {
App.log.log(Level.SEVERE, "Not authorized anymore", e);
messageString = R.string.sync_error_unauthorized;
} else if (e instanceof Exceptions.ServiceUnavailableException) {
App.log.log(Level.SEVERE, "Service unavailable");
messageString = R.string.sync_error_unavailable;
} else if (e instanceof Exceptions.HttpException) {
App.log.log(Level.SEVERE, "HTTP Exception during sync", e);
messageString = R.string.sync_error_http_dav;
} else if (e instanceof CalendarStorageException || e instanceof ContactsStorageException || e instanceof SQLiteException) {
App.log.log(Level.SEVERE, "Couldn't access local storage", e);
messageString = R.string.sync_error_local_storage;
} else if (e instanceof Exceptions.IntegrityException) {
App.log.log(Level.SEVERE, "Integrity error", e);
messageString = R.string.sync_error_integrity;
} else {
App.log.log(Level.SEVERE, "Unknown sync error", e);
messageString = R.string.sync_error;
}
if (e instanceof Exceptions.UnauthorizedException) {
detailsIntent = new Intent(context, AccountSettingsActivity.class);
} else {
detailsIntent = new Intent(context, DebugInfoActivity.class);
detailsIntent.putExtra(DebugInfoActivity.KEY_THROWABLE, e);
}
detailsIntent.setData(Uri.parse("uri://" + getClass().getName() + "/" + notificationTag));
}
public void notify(String title, String state) {
String message = context.getString(messageString, state);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
builder.setSmallIcon(R.drawable.ic_error_light)
.setLargeIcon(App.getLauncherBitmap(context))
.setContentTitle(title)
.setContentIntent(PendingIntent.getActivity(context, 0, detailsIntent, PendingIntent.FLAG_CANCEL_CURRENT))
.setCategory(NotificationCompat.CATEGORY_ERROR)
.setContentText(message);
notificationManager.notify(notificationTag, notificationId, builder.build());
}
public void cancel() {
notificationManager.cancel(notificationTag, notificationId);
}
}

View File

@ -13,6 +13,7 @@ import android.content.ContentProviderClient;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.SyncResult; import android.content.SyncResult;
import android.database.Cursor; import android.database.Cursor;
import android.database.DatabaseUtils; import android.database.DatabaseUtils;
@ -21,6 +22,7 @@ import android.database.sqlite.SQLiteException;
import android.os.Bundle; import android.os.Bundle;
import android.provider.CalendarContract; import android.provider.CalendarContract;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v4.app.NotificationManagerCompat;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
@ -28,13 +30,17 @@ import java.util.logging.Level;
import at.bitfire.davdroid.AccountSettings; import at.bitfire.davdroid.AccountSettings;
import at.bitfire.davdroid.App; import at.bitfire.davdroid.App;
import at.bitfire.davdroid.InvalidAccountException; import at.bitfire.davdroid.Constants;
import at.bitfire.davdroid.NotificationHelper;
import at.bitfire.davdroid.R;
import at.bitfire.davdroid.journalmanager.Exceptions; import at.bitfire.davdroid.journalmanager.Exceptions;
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.model.ServiceDB.Collections; import at.bitfire.davdroid.model.ServiceDB.Collections;
import at.bitfire.davdroid.model.ServiceDB.Services; import at.bitfire.davdroid.model.ServiceDB.Services;
import at.bitfire.davdroid.resource.LocalCalendar; import at.bitfire.davdroid.resource.LocalCalendar;
import at.bitfire.davdroid.ui.AccountSettingsActivity;
import at.bitfire.davdroid.ui.DebugInfoActivity;
import at.bitfire.ical4android.CalendarStorageException; import at.bitfire.ical4android.CalendarStorageException;
import lombok.Cleanup; import lombok.Cleanup;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
@ -57,6 +63,9 @@ public class CalendarsSyncAdapterService extends SyncAdapterService {
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) {
super.onPerformSync(account, extras, authority, provider, syncResult); super.onPerformSync(account, extras, authority, provider, syncResult);
NotificationHelper notificationManager = new NotificationHelper(getContext(), "journals", Constants.NOTIFICATION_CALENDAR_SYNC);
notificationManager.cancel();
try { try {
AccountSettings settings = new AccountSettings(getContext(), account); AccountSettings settings = new AccountSettings(getContext(), account);
if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && !checkSyncConditions(settings)) if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && !checkSyncConditions(settings))
@ -71,15 +80,27 @@ public class CalendarsSyncAdapterService extends SyncAdapterService {
CalendarSyncManager syncManager = new CalendarSyncManager(getContext(), account, settings, extras, authority, syncResult, calendar, principal); CalendarSyncManager syncManager = new CalendarSyncManager(getContext(), account, settings, extras, authority, syncResult, calendar, principal);
syncManager.performSync(); syncManager.performSync();
} }
} catch (CalendarStorageException | SQLiteException e) { } catch (Exception | OutOfMemoryError e) {
App.log.log(Level.SEVERE, "Couldn't prepare local calendars", e); if (e instanceof CalendarStorageException || e instanceof SQLiteException) {
syncResult.databaseError = true; App.log.log(Level.SEVERE, "Couldn't prepare local calendars", e);
} catch (InvalidAccountException e) { syncResult.databaseError = true;
App.log.log(Level.SEVERE, "Couldn't get account settings", e); }
} catch (Exceptions.HttpException e) {
e.printStackTrace(); String syncPhase = SyncManager.SYNC_PHASE_JOURNALS;
} catch (Exceptions.IntegrityException e) { String title = getContext().getString(R.string.sync_error_calendar, account.name);
e.printStackTrace();
notificationManager.setThrowable(e);
final Intent detailsIntent = notificationManager.getDetailsIntent();
if (e instanceof Exceptions.UnauthorizedException) {
detailsIntent.putExtra(AccountSettingsActivity.EXTRA_ACCOUNT, account);
} else {
detailsIntent.putExtra(DebugInfoActivity.KEY_ACCOUNT, account);
detailsIntent.putExtra(DebugInfoActivity.KEY_AUTHORITY, authority);
detailsIntent.putExtra(DebugInfoActivity.KEY_PHASE, syncPhase);
}
notificationManager.notify(title, syncPhase);
} }
App.log.info("Calendar sync complete"); App.log.info("Calendar sync complete");

View File

@ -13,6 +13,7 @@ import android.content.ContentProviderClient;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.SyncResult; import android.content.SyncResult;
import android.database.Cursor; import android.database.Cursor;
import android.database.DatabaseUtils; import android.database.DatabaseUtils;
@ -20,16 +21,22 @@ import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.app.NotificationManagerCompat;
import java.util.logging.Level; import java.util.logging.Level;
import at.bitfire.davdroid.AccountSettings; import at.bitfire.davdroid.AccountSettings;
import at.bitfire.davdroid.App; import at.bitfire.davdroid.App;
import at.bitfire.davdroid.Constants;
import at.bitfire.davdroid.InvalidAccountException; import at.bitfire.davdroid.InvalidAccountException;
import at.bitfire.davdroid.NotificationHelper;
import at.bitfire.davdroid.R;
import at.bitfire.davdroid.journalmanager.Exceptions; import at.bitfire.davdroid.journalmanager.Exceptions;
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.model.ServiceDB.Collections; import at.bitfire.davdroid.model.ServiceDB.Collections;
import at.bitfire.davdroid.ui.AccountSettingsActivity;
import at.bitfire.davdroid.ui.DebugInfoActivity;
import lombok.Cleanup; import lombok.Cleanup;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
@ -50,6 +57,8 @@ public class ContactsSyncAdapterService extends SyncAdapterService {
@Override @Override
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) {
super.onPerformSync(account, extras, authority, provider, syncResult); super.onPerformSync(account, extras, authority, provider, syncResult);
NotificationHelper notificationManager = new NotificationHelper(getContext(), "journals", Constants.NOTIFICATION_CONTACTS_SYNC);
notificationManager.cancel();
ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(getContext()); ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(getContext());
try { try {
@ -72,12 +81,21 @@ public class ContactsSyncAdapterService extends SyncAdapterService {
} }
} else } else
App.log.info("No CardDAV service found in DB"); App.log.info("No CardDAV service found in DB");
} catch (InvalidAccountException e) { } catch (Exception | OutOfMemoryError e) {
App.log.log(Level.SEVERE, "Couldn't get account settings", e); String syncPhase = SyncManager.SYNC_PHASE_JOURNALS;
} catch (Exceptions.HttpException e) { String title = getContext().getString(R.string.sync_error_contacts, account.name);
e.printStackTrace();
} catch (Exceptions.IntegrityException e) { notificationManager.setThrowable(e);
e.printStackTrace();
final Intent detailsIntent = notificationManager.getDetailsIntent();
if (e instanceof Exceptions.UnauthorizedException) {
detailsIntent.putExtra(AccountSettingsActivity.EXTRA_ACCOUNT, account);
} else {
detailsIntent.putExtra(DebugInfoActivity.KEY_ACCOUNT, account);
detailsIntent.putExtra(DebugInfoActivity.KEY_AUTHORITY, authority);
detailsIntent.putExtra(DebugInfoActivity.KEY_PHASE, syncPhase);
}
notificationManager.notify(title, syncPhase);
} finally { } finally {
dbHelper.close(); dbHelper.close();
} }

View File

@ -9,14 +9,11 @@ package at.bitfire.davdroid.syncadapter;
import android.accounts.Account; import android.accounts.Account;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SyncResult; import android.content.SyncResult;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.NotificationManagerCompat; import android.support.v4.app.NotificationManagerCompat;
import android.support.v7.app.NotificationCompat;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
@ -34,7 +31,7 @@ import at.bitfire.davdroid.App;
import at.bitfire.davdroid.GsonHelper; import at.bitfire.davdroid.GsonHelper;
import at.bitfire.davdroid.HttpClient; import at.bitfire.davdroid.HttpClient;
import at.bitfire.davdroid.InvalidAccountException; import at.bitfire.davdroid.InvalidAccountException;
import at.bitfire.davdroid.R; import at.bitfire.davdroid.NotificationHelper;
import at.bitfire.davdroid.journalmanager.Exceptions; import at.bitfire.davdroid.journalmanager.Exceptions;
import at.bitfire.davdroid.journalmanager.JournalEntryManager; import at.bitfire.davdroid.journalmanager.JournalEntryManager;
import at.bitfire.davdroid.model.CollectionInfo; import at.bitfire.davdroid.model.CollectionInfo;
@ -50,7 +47,8 @@ import okhttp3.OkHttpClient;
abstract public class SyncManager { abstract public class SyncManager {
protected final String SYNC_PHASE_PREPARE = "sync_phase_prepare", final static String SYNC_PHASE_PREPARE = "sync_phase_prepare",
SYNC_PHASE_JOURNALS = "sync_phase_journals",
SYNC_PHASE_QUERY_CAPABILITIES = "sync_phase_query_capabilities", SYNC_PHASE_QUERY_CAPABILITIES = "sync_phase_query_capabilities",
SYNC_PHASE_PREPARE_LOCAL = "sync_phase_prepare_local", SYNC_PHASE_PREPARE_LOCAL = "sync_phase_prepare_local",
SYNC_PHASE_CREATE_LOCAL_ENTRIES = "sync_phase_create_local_entries", SYNC_PHASE_CREATE_LOCAL_ENTRIES = "sync_phase_create_local_entries",
@ -62,7 +60,7 @@ abstract public class SyncManager {
SYNC_PHASE_SAVE_SYNC_TAG = "sync_phase_save_sync_tag"; SYNC_PHASE_SAVE_SYNC_TAG = "sync_phase_save_sync_tag";
protected final NotificationManagerCompat notificationManager; protected final NotificationHelper notificationManager;
protected final String uniqueCollectionId; protected final String uniqueCollectionId;
protected final Context context; protected final Context context;
@ -113,8 +111,8 @@ abstract public class SyncManager {
// dismiss previous error notifications // dismiss previous error notifications
this.uniqueCollectionId = uniqueCollectionId; this.uniqueCollectionId = uniqueCollectionId;
notificationManager = NotificationManagerCompat.from(context); notificationManager = new NotificationHelper(context, uniqueCollectionId, notificationId());
notificationManager.cancel(uniqueCollectionId, notificationId()); notificationManager.cancel();
} }
protected abstract int notificationId(); protected abstract int notificationId();
@ -193,57 +191,30 @@ abstract public class SyncManager {
// syncResult.delayUntil = (retryAfter.getTime() - new Date().getTime()) / 1000; // syncResult.delayUntil = (retryAfter.getTime() - new Date().getTime()) / 1000;
} }
} catch (Exception | OutOfMemoryError e) { } catch (Exception | OutOfMemoryError e) {
final int messageString;
if (e instanceof Exceptions.UnauthorizedException) { if (e instanceof Exceptions.UnauthorizedException) {
App.log.log(Level.SEVERE, "Not authorized anymore", e);
messageString = R.string.sync_error_unauthorized;
syncResult.stats.numAuthExceptions++; syncResult.stats.numAuthExceptions++;
} else if (e instanceof Exceptions.HttpException) { } else if (e instanceof Exceptions.HttpException) {
App.log.log(Level.SEVERE, "HTTP Exception during sync", e);
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) {
App.log.log(Level.SEVERE, "Couldn't access local storage", e);
messageString = R.string.sync_error_local_storage;
syncResult.databaseError = true; syncResult.databaseError = true;
} else if (e instanceof Exceptions.IntegrityException) { } else if (e instanceof Exceptions.IntegrityException) {
App.log.log(Level.SEVERE, "Integrity error", e);
messageString = R.string.sync_error_integrity;
syncResult.stats.numParseExceptions++; syncResult.stats.numParseExceptions++;
} else { } else {
App.log.log(Level.SEVERE, "Unknown sync error", e);
messageString = R.string.sync_error;
syncResult.stats.numParseExceptions++; syncResult.stats.numParseExceptions++;
} }
final Intent detailsIntent; notificationManager.setThrowable(e);
final Intent detailsIntent = notificationManager.getDetailsIntent();
if (e instanceof Exceptions.UnauthorizedException) { if (e instanceof Exceptions.UnauthorizedException) {
detailsIntent = new Intent(context, AccountSettingsActivity.class);
detailsIntent.putExtra(AccountSettingsActivity.EXTRA_ACCOUNT, account); detailsIntent.putExtra(AccountSettingsActivity.EXTRA_ACCOUNT, account);
} else { } else {
detailsIntent = new Intent(context, DebugInfoActivity.class);
detailsIntent.putExtra(DebugInfoActivity.KEY_THROWABLE, e);
detailsIntent.putExtra(DebugInfoActivity.KEY_ACCOUNT, account); detailsIntent.putExtra(DebugInfoActivity.KEY_ACCOUNT, account);
detailsIntent.putExtra(DebugInfoActivity.KEY_AUTHORITY, authority); detailsIntent.putExtra(DebugInfoActivity.KEY_AUTHORITY, authority);
detailsIntent.putExtra(DebugInfoActivity.KEY_PHASE, syncPhase); detailsIntent.putExtra(DebugInfoActivity.KEY_PHASE, syncPhase);
} }
// to make the PendingIntent unique notificationManager.notify(getSyncErrorTitle(), syncPhase);
detailsIntent.setData(Uri.parse("uri://" + getClass().getName() + "/" + uniqueCollectionId));
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
builder.setSmallIcon(R.drawable.ic_error_light)
.setLargeIcon(App.getLauncherBitmap(context))
.setContentTitle(getSyncErrorTitle())
.setContentIntent(PendingIntent.getActivity(context, 0, detailsIntent, PendingIntent.FLAG_CANCEL_CURRENT))
.setCategory(NotificationCompat.CATEGORY_ERROR);
String message = context.getString(messageString, syncPhase);
builder.setContentText(message);
notificationManager.notify(uniqueCollectionId, notificationId(), builder.build());
} }
} }

View File

@ -264,6 +264,7 @@
<string name="sync_error">Error while %s</string> <string name="sync_error">Error while %s</string>
<string name="sync_error_integrity">Integrity error while %s</string> <string name="sync_error_integrity">Integrity error while %s</string>
<string name="sync_error_http_dav">Server error while %s</string> <string name="sync_error_http_dav">Server error while %s</string>
<string name="sync_error_unavailable">Could not connect to server while %s</string>
<string name="sync_error_local_storage">Database error while %s</string> <string name="sync_error_local_storage">Database error while %s</string>
<string-array name="sync_error_phases"> <string-array name="sync_error_phases">
<item>preparing synchronization</item> <item>preparing synchronization</item>