From 2615fbd9ce4b3ce0504e2fb172e0b74c704d44ab Mon Sep 17 00:00:00 2001 From: Tom Hacohen Date: Wed, 1 May 2019 09:23:19 +0100 Subject: [PATCH] Unify all of the sync adapter exception handling. The exception handling was duplicated across every sync backend. It was redundant and made it easy to not handle all of the exceptions correctly everywhere. --- .../AddressBooksSyncAdapterService.kt | 80 +++++-------------- .../CalendarsSyncAdapterService.kt | 63 +++------------ .../syncadapter/ContactsSyncAdapterService.kt | 73 +++++------------ .../syncadapter/SyncAdapterService.kt | 45 +++++++++++ .../syncadapter/TasksSyncAdapterService.kt | 78 +++++------------- 5 files changed, 123 insertions(+), 216 deletions(-) diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/AddressBooksSyncAdapterService.kt b/app/src/main/java/com/etesync/syncadapter/syncadapter/AddressBooksSyncAdapterService.kt index df19c85d..b32cdf80 100644 --- a/app/src/main/java/com/etesync/syncadapter/syncadapter/AddressBooksSyncAdapterService.kt +++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/AddressBooksSyncAdapterService.kt @@ -40,70 +40,34 @@ class AddressBooksSyncAdapterService : SyncAdapterService() { private class AddressBooksSyncAdapter(context: Context) : SyncAdapterService.SyncAdapter(context) { + override val syncErrorTitle = R.string.sync_error_contacts + override val notificationManager = SyncNotification(context, "journals-contacts", Constants.NOTIFICATION_CONTACTS_SYNC) - override fun onPerformSync(account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult) { - super.onPerformSync(account, extras, authority, provider, syncResult) + override fun onPerformSyncDo(account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult) { + val contactsProvider = context.contentResolver.acquireContentProviderClient(ContactsContract.AUTHORITY) + if (contactsProvider == null) { + Logger.log.severe("Couldn't access contacts provider") + syncResult.databaseError = true + return + } - val notificationManager = SyncNotification(context, "journals-contacts", Constants.NOTIFICATION_CONTACTS_SYNC) - notificationManager.cancel() + val settings = AccountSettings(context, account) + if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && !checkSyncConditions(settings)) + return - try { - val contactsProvider = context.contentResolver.acquireContentProviderClient(ContactsContract.AUTHORITY) - if (contactsProvider == null) { - Logger.log.severe("Couldn't access contacts provider") - syncResult.databaseError = true - return - } + RefreshCollections(account, CollectionInfo.Type.ADDRESS_BOOK).run() - val settings = AccountSettings(context, account) - if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && !checkSyncConditions(settings)) - return + updateLocalAddressBooks(contactsProvider, account) - RefreshCollections(account, CollectionInfo.Type.ADDRESS_BOOK).run() + contactsProvider.release() - updateLocalAddressBooks(contactsProvider, account) - - contactsProvider.release() - - val accountManager = AccountManager.get(context) - for (addressBookAccount in accountManager.getAccountsByType(App.addressBookAccountType)) { - Logger.log.log(Level.INFO, "Running sync for address book", addressBookAccount) - val syncExtras = Bundle(extras) - syncExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true) - syncExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true) - ContentResolver.requestSync(addressBookAccount, ContactsContract.AUTHORITY, syncExtras) - } - } catch (e: Exceptions.ServiceUnavailableException) { - syncResult.stats.numIoExceptions++ - syncResult.delayUntil = if (e.retryAfter > 0) e.retryAfter else Constants.DEFAULT_RETRY_DELAY - } catch (e: Exceptions.IgnorableHttpException) { - // Ignore - } catch (e: Exception) { - if (e is ContactsStorageException || e is SQLiteException) { - Logger.log.log(Level.SEVERE, "Couldn't prepare local address books", e) - syncResult.databaseError = true - } - - val syncPhase = R.string.sync_phase_journals - val title = context.getString(R.string.sync_error_contacts, account.name) - - notificationManager.setThrowable(e) - - val detailsIntent = notificationManager.detailsIntent - detailsIntent.putExtra(KEY_ACCOUNT, account) - if (e !is Exceptions.UnauthorizedException) { - detailsIntent.putExtra(DebugInfoActivity.KEY_AUTHORITY, authority) - detailsIntent.putExtra(DebugInfoActivity.KEY_PHASE, syncPhase) - } - - notificationManager.notify(title, context.getString(syncPhase)) - } catch (e: OutOfMemoryError) { - val syncPhase = R.string.sync_phase_journals - val title = context.getString(R.string.sync_error_contacts, account.name) - notificationManager.setThrowable(e) - val detailsIntent = notificationManager.detailsIntent - detailsIntent.putExtra(KEY_ACCOUNT, account) - notificationManager.notify(title, context.getString(syncPhase)) + val accountManager = AccountManager.get(context) + for (addressBookAccount in accountManager.getAccountsByType(App.addressBookAccountType)) { + Logger.log.log(Level.INFO, "Running sync for address book", addressBookAccount) + val syncExtras = Bundle(extras) + syncExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true) + syncExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true) + ContentResolver.requestSync(addressBookAccount, ContactsContract.AUTHORITY, syncExtras) } Logger.log.info("Address book sync complete") diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/CalendarsSyncAdapterService.kt b/app/src/main/java/com/etesync/syncadapter/syncadapter/CalendarsSyncAdapterService.kt index 2b5fbfdb..c8804055 100644 --- a/app/src/main/java/com/etesync/syncadapter/syncadapter/CalendarsSyncAdapterService.kt +++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/CalendarsSyncAdapterService.kt @@ -31,68 +31,31 @@ import java.util.* import java.util.logging.Level class CalendarsSyncAdapterService : SyncAdapterService() { - override fun syncAdapter(): AbstractThreadedSyncAdapter { return SyncAdapter(this) } private class SyncAdapter(context: Context) : SyncAdapterService.SyncAdapter(context) { + override val syncErrorTitle = R.string.sync_error_calendar + override val notificationManager = SyncNotification(context, "journals-calendar", Constants.NOTIFICATION_CALENDAR_SYNC) - override fun onPerformSync(account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult) { - super.onPerformSync(account, extras, authority, provider, syncResult) + override fun onPerformSyncDo(account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult) { + val settings = AccountSettings(context, account) + if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && !checkSyncConditions(settings)) + return - val notificationManager = SyncNotification(context, "journals-calendar", Constants.NOTIFICATION_CALENDAR_SYNC) - notificationManager.cancel() + RefreshCollections(account, CollectionInfo.Type.CALENDAR).run() - try { - val settings = AccountSettings(context, account) - if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && !checkSyncConditions(settings)) - return + updateLocalCalendars(provider, account, settings) - RefreshCollections(account, CollectionInfo.Type.CALENDAR).run() + val principal = HttpUrl.get(settings.uri!!)!! - updateLocalCalendars(provider, account, settings) - - val principal = HttpUrl.get(settings.uri!!)!! - - for (calendar in AndroidCalendar.find(account, provider, LocalCalendar.Factory, CalendarContract.Calendars.SYNC_EVENTS + "!=0", null)) { - Logger.log.info("Synchronizing calendar #" + calendar.id + ", URL: " + calendar.name) - CalendarSyncManager(context, account, settings, extras, authority, syncResult, calendar, principal).use { - it.performSync() - } + for (calendar in AndroidCalendar.find(account, provider, LocalCalendar.Factory, CalendarContract.Calendars.SYNC_EVENTS + "!=0", null)) { + Logger.log.info("Synchronizing calendar #" + calendar.id + ", URL: " + calendar.name) + CalendarSyncManager(context, account, settings, extras, authority, syncResult, calendar, principal).use { + it.performSync() } - } catch (e: Exceptions.ServiceUnavailableException) { - syncResult.stats.numIoExceptions++ - syncResult.delayUntil = if (e.retryAfter > 0) e.retryAfter else Constants.DEFAULT_RETRY_DELAY - } catch (e: Exceptions.IgnorableHttpException) { - // Ignore - } catch (e: Exception) { - if (e is CalendarStorageException || e is SQLiteException) { - Logger.log.log(Level.SEVERE, "Couldn't prepare local calendars", e) - syncResult.databaseError = true - } - - val syncPhase = R.string.sync_phase_journals - val title = context.getString(R.string.sync_error_calendar, account.name) - - notificationManager.setThrowable(e) - - val detailsIntent = notificationManager.detailsIntent - detailsIntent.putExtra(KEY_ACCOUNT, account) - if (e !is Exceptions.UnauthorizedException) { - detailsIntent.putExtra(DebugInfoActivity.KEY_AUTHORITY, authority) - detailsIntent.putExtra(DebugInfoActivity.KEY_PHASE, syncPhase) - } - - notificationManager.notify(title, context.getString(syncPhase)) - } catch (e: OutOfMemoryError) { - val syncPhase = R.string.sync_phase_journals - val title = context.getString(R.string.sync_error_calendar, account.name) - notificationManager.setThrowable(e) - val detailsIntent = notificationManager.detailsIntent - detailsIntent.putExtra(KEY_ACCOUNT, account) - notificationManager.notify(title, context.getString(syncPhase)) } Logger.log.info("Calendar sync complete") diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncAdapterService.kt b/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncAdapterService.kt index 781442c6..a27595f1 100644 --- a/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncAdapterService.kt +++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncAdapterService.kt @@ -30,63 +30,34 @@ class ContactsSyncAdapterService : SyncAdapterService() { private class ContactsSyncAdapter(context: Context) : SyncAdapterService.SyncAdapter(context) { + override val syncErrorTitle = R.string.sync_error_contacts + override val notificationManager = SyncNotification(context, "journals-contacts", Constants.NOTIFICATION_CONTACTS_SYNC) - override fun onPerformSync(account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult) { - super.onPerformSync(account, extras, authority, provider, syncResult) - val notificationManager = SyncNotification(context, "journals-contacts", Constants.NOTIFICATION_CONTACTS_SYNC) - notificationManager.cancel() + override fun onPerformSyncDo(account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult) { + val addressBook = LocalAddressBook(context, account, provider) + val settings: AccountSettings try { - val addressBook = LocalAddressBook(context, account, provider) + settings = AccountSettings(context, addressBook.mainAccount) + } catch (e: InvalidAccountException) { + Logger.log.info("Skipping sync due to invalid account.") + Logger.log.info(e.localizedMessage) + return + } catch (e: ContactsStorageException) { + Logger.log.info("Skipping sync due to invalid account.") + Logger.log.info(e.localizedMessage) + return + } - val settings: AccountSettings - try { - settings = AccountSettings(context, addressBook.mainAccount) - } catch (e: InvalidAccountException) { - Logger.log.info("Skipping sync due to invalid account.") - Logger.log.info(e.localizedMessage) - return - } catch (e: ContactsStorageException) { - Logger.log.info("Skipping sync due to invalid account.") - Logger.log.info(e.localizedMessage) - return - } + if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && !checkSyncConditions(settings)) + return - if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && !checkSyncConditions(settings)) - return + Logger.log.info("Synchronizing address book: " + addressBook.url) + Logger.log.info("Taking settings from: " + addressBook.mainAccount) - Logger.log.info("Synchronizing address book: " + addressBook.url) - Logger.log.info("Taking settings from: " + addressBook.mainAccount) - - val principal = HttpUrl.get(settings.uri!!)!! - ContactsSyncManager(context, account, settings, extras, authority, provider, syncResult, addressBook, principal).use { - it.performSync() - } - } catch (e: Exceptions.ServiceUnavailableException) { - syncResult.stats.numIoExceptions++ - syncResult.delayUntil = if (e.retryAfter > 0) e.retryAfter else Constants.DEFAULT_RETRY_DELAY - } catch (e: Exceptions.IgnorableHttpException) { - // Ignore - } catch (e: Exception) { - val syncPhase = R.string.sync_phase_journals - val title = context.getString(R.string.sync_error_contacts, account.name) - - notificationManager.setThrowable(e) - - val detailsIntent = notificationManager.detailsIntent - detailsIntent.putExtra(KEY_ACCOUNT, account) - if (e !is Exceptions.UnauthorizedException) { - detailsIntent.putExtra(DebugInfoActivity.KEY_AUTHORITY, authority) - detailsIntent.putExtra(DebugInfoActivity.KEY_PHASE, syncPhase) - } - notificationManager.notify(title, context.getString(syncPhase)) - } catch (e: OutOfMemoryError) { - val syncPhase = R.string.sync_phase_journals - val title = context.getString(R.string.sync_error_contacts, account.name) - notificationManager.setThrowable(e) - val detailsIntent = notificationManager.detailsIntent - detailsIntent.putExtra(KEY_ACCOUNT, account) - notificationManager.notify(title, context.getString(syncPhase)) + val principal = HttpUrl.get(settings.uri!!)!! + ContactsSyncManager(context, account, settings, extras, authority, provider, syncResult, addressBook, principal).use { + it.performSync() } Logger.log.info("Contacts sync complete") diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncAdapterService.kt b/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncAdapterService.kt index b1718ff9..85109337 100644 --- a/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncAdapterService.kt +++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncAdapterService.kt @@ -12,6 +12,7 @@ import android.accounts.Account import android.app.PendingIntent import android.app.Service import android.content.* +import android.database.sqlite.SQLiteException import android.net.ConnectivityManager import android.net.wifi.WifiManager import android.os.Bundle @@ -19,6 +20,8 @@ import android.os.IBinder import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.util.Pair +import at.bitfire.ical4android.CalendarStorageException +import at.bitfire.vcard4android.ContactsStorageException import com.etesync.syncadapter.* import com.etesync.syncadapter.journalmanager.Crypto import com.etesync.syncadapter.journalmanager.Exceptions @@ -27,6 +30,7 @@ import com.etesync.syncadapter.log.Logger import com.etesync.syncadapter.model.CollectionInfo import com.etesync.syncadapter.model.JournalEntity import com.etesync.syncadapter.model.JournalModel +import com.etesync.syncadapter.ui.DebugInfoActivity import com.etesync.syncadapter.ui.PermissionsActivity import com.etesync.syncadapter.utils.NotificationUtils import okhttp3.HttpUrl @@ -45,12 +49,53 @@ abstract class SyncAdapterService : Service() { abstract class SyncAdapter(context: Context) : AbstractThreadedSyncAdapter(context, false) { + abstract val syncErrorTitle: Int + abstract val notificationManager: SyncNotification + + abstract fun onPerformSyncDo(account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult) override fun onPerformSync(account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult) { Logger.log.log(Level.INFO, "$authority sync of $account has been initiated.", extras.keySet().toTypedArray()) // required for dav4android (ServiceLoader) Thread.currentThread().contextClassLoader = context.classLoader + + notificationManager.cancel() + + try { + onPerformSyncDo(account, extras, authority, provider, syncResult) + } catch (e: Exceptions.ServiceUnavailableException) { + syncResult.stats.numIoExceptions++ + syncResult.delayUntil = if (e.retryAfter > 0) e.retryAfter else Constants.DEFAULT_RETRY_DELAY + } catch (e: Exceptions.IgnorableHttpException) { + // Ignore + } catch (e: Exception) { + if (e is ContactsStorageException || e is CalendarStorageException || e is SQLiteException) { + Logger.log.log(Level.SEVERE, "Couldn't prepare local journals", e) + syncResult.databaseError = true + } + + val syncPhase = R.string.sync_phase_journals + val title = context.getString(syncErrorTitle, account.name) + + notificationManager.setThrowable(e) + + val detailsIntent = notificationManager.detailsIntent + detailsIntent.putExtra(Constants.KEY_ACCOUNT, account) + if (e !is Exceptions.UnauthorizedException) { + detailsIntent.putExtra(DebugInfoActivity.KEY_AUTHORITY, authority) + detailsIntent.putExtra(DebugInfoActivity.KEY_PHASE, syncPhase) + } + + notificationManager.notify(title, context.getString(syncPhase)) + } catch (e: OutOfMemoryError) { + val syncPhase = R.string.sync_phase_journals + val title = context.getString(syncErrorTitle, account.name) + notificationManager.setThrowable(e) + val detailsIntent = notificationManager.detailsIntent + detailsIntent.putExtra(Constants.KEY_ACCOUNT, account) + notificationManager.notify(title, context.getString(syncPhase)) + } } override fun onSecurityException(account: Account, extras: Bundle, authority: String, syncResult: SyncResult) { diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/TasksSyncAdapterService.kt b/app/src/main/java/com/etesync/syncadapter/syncadapter/TasksSyncAdapterService.kt index 408522bf..07fba9c1 100644 --- a/app/src/main/java/com/etesync/syncadapter/syncadapter/TasksSyncAdapterService.kt +++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/TasksSyncAdapterService.kt @@ -45,72 +45,36 @@ class TasksSyncAdapterService: SyncAdapterService() { class TasksSyncAdapter( context: Context ): SyncAdapter(context) { + override val syncErrorTitle = R.string.sync_error_tasks + override val notificationManager = SyncNotification(context, "journals-tasks", Constants.NOTIFICATION_TASK_SYNC) - override fun onPerformSync(account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult) { - super.onPerformSync(account, extras, authority, provider, syncResult) + override fun onPerformSyncDo(account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult) { - val notificationManager = SyncNotification(context, "journals-tasks", Constants.NOTIFICATION_TASK_SYNC) - notificationManager.cancel() + val taskProvider = TaskProvider.fromProviderClient(context, provider) - try { - val taskProvider = TaskProvider.fromProviderClient(context, provider) + // make sure account can be seen by OpenTasks + if (Build.VERSION.SDK_INT >= 26) + AccountManager.get(context).setAccountVisibility(account, taskProvider.name.packageName, AccountManager.VISIBILITY_VISIBLE) - // make sure account can be seen by OpenTasks - if (Build.VERSION.SDK_INT >= 26) - AccountManager.get(context).setAccountVisibility(account, taskProvider.name.packageName, AccountManager.VISIBILITY_VISIBLE) + val accountSettings = AccountSettings(context, account) + /* don't run sync if + - sync conditions (e.g. "sync only in WiFi") are not met AND + - this is is an automatic sync (i.e. manual syncs are run regardless of sync conditions) + */ + if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && !checkSyncConditions(accountSettings)) + return - val accountSettings = AccountSettings(context, account) - /* don't run sync if - - sync conditions (e.g. "sync only in WiFi") are not met AND - - this is is an automatic sync (i.e. manual syncs are run regardless of sync conditions) - */ - if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && !checkSyncConditions(accountSettings)) - return + RefreshCollections(account, CollectionInfo.Type.TASKS).run() + updateLocalTaskLists(taskProvider, account, accountSettings) - RefreshCollections(account, CollectionInfo.Type.TASKS).run() + val principal = HttpUrl.get(accountSettings.uri!!)!! - updateLocalTaskLists(taskProvider, account, accountSettings) - - val principal = HttpUrl.get(accountSettings.uri!!)!! - - for (taskList in AndroidTaskList.find(account, taskProvider, LocalTaskList.Factory, "${TaskContract.TaskLists.SYNC_ENABLED}!=0", null)) { - Logger.log.info("Synchronizing task list #${taskList.id} [${taskList.syncId}]") - TasksSyncManager(context, account, accountSettings, extras, authority, syncResult, taskList, principal).use { - it.performSync() - } + for (taskList in AndroidTaskList.find(account, taskProvider, LocalTaskList.Factory, "${TaskContract.TaskLists.SYNC_ENABLED}!=0", null)) { + Logger.log.info("Synchronizing task list #${taskList.id} [${taskList.syncId}]") + TasksSyncManager(context, account, accountSettings, extras, authority, syncResult, taskList, principal).use { + it.performSync() } - } catch (e: Exceptions.ServiceUnavailableException) { - syncResult.stats.numIoExceptions++ - syncResult.delayUntil = if (e.retryAfter > 0) e.retryAfter else Constants.DEFAULT_RETRY_DELAY - } catch (e: Exceptions.IgnorableHttpException) { - // Ignore - } catch (e: Exception) { - if (e is SQLiteException) { - Logger.log.log(Level.SEVERE, "Couldn't prepare local task list", e) - syncResult.databaseError = true - } - - val syncPhase = R.string.sync_phase_journals - val title = context.getString(R.string.sync_error_tasks, account.name) - - notificationManager.setThrowable(e) - - val detailsIntent = notificationManager.detailsIntent - detailsIntent.putExtra(Constants.KEY_ACCOUNT, account) - if (e !is Exceptions.UnauthorizedException) { - detailsIntent.putExtra(DebugInfoActivity.KEY_AUTHORITY, authority) - detailsIntent.putExtra(DebugInfoActivity.KEY_PHASE, syncPhase) - } - - notificationManager.notify(title, context.getString(syncPhase)) - } catch (e: OutOfMemoryError) { - val syncPhase = R.string.sync_phase_journals - val title = context.getString(R.string.sync_error_tasks, account.name) - notificationManager.setThrowable(e) - val detailsIntent = notificationManager.detailsIntent - detailsIntent.putExtra(Constants.KEY_ACCOUNT, account) - notificationManager.notify(title, context.getString(syncPhase)) } Logger.log.info("Task sync complete")