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

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.
This commit is contained in:
Tom Hacohen 2019-05-01 09:23:19 +01:00
parent 6250cacd30
commit 2615fbd9ce
5 changed files with 123 additions and 216 deletions

View File

@ -40,70 +40,34 @@ class AddressBooksSyncAdapterService : SyncAdapterService() {
private class AddressBooksSyncAdapter(context: Context) : SyncAdapterService.SyncAdapter(context) { 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) { override fun onPerformSyncDo(account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult) {
super.onPerformSync(account, extras, authority, provider, 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) val settings = AccountSettings(context, account)
notificationManager.cancel() if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && !checkSyncConditions(settings))
return
try { RefreshCollections(account, CollectionInfo.Type.ADDRESS_BOOK).run()
val contactsProvider = context.contentResolver.acquireContentProviderClient(ContactsContract.AUTHORITY)
if (contactsProvider == null) {
Logger.log.severe("Couldn't access contacts provider")
syncResult.databaseError = true
return
}
val settings = AccountSettings(context, account) updateLocalAddressBooks(contactsProvider, account)
if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && !checkSyncConditions(settings))
return
RefreshCollections(account, CollectionInfo.Type.ADDRESS_BOOK).run() contactsProvider.release()
updateLocalAddressBooks(contactsProvider, account) val accountManager = AccountManager.get(context)
for (addressBookAccount in accountManager.getAccountsByType(App.addressBookAccountType)) {
contactsProvider.release() Logger.log.log(Level.INFO, "Running sync for address book", addressBookAccount)
val syncExtras = Bundle(extras)
val accountManager = AccountManager.get(context) syncExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true)
for (addressBookAccount in accountManager.getAccountsByType(App.addressBookAccountType)) { syncExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true)
Logger.log.log(Level.INFO, "Running sync for address book", addressBookAccount) ContentResolver.requestSync(addressBookAccount, ContactsContract.AUTHORITY, syncExtras)
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))
} }
Logger.log.info("Address book sync complete") Logger.log.info("Address book sync complete")

View File

@ -31,68 +31,31 @@ import java.util.*
import java.util.logging.Level import java.util.logging.Level
class CalendarsSyncAdapterService : SyncAdapterService() { class CalendarsSyncAdapterService : SyncAdapterService() {
override fun syncAdapter(): AbstractThreadedSyncAdapter { override fun syncAdapter(): AbstractThreadedSyncAdapter {
return SyncAdapter(this) return SyncAdapter(this)
} }
private class SyncAdapter(context: Context) : SyncAdapterService.SyncAdapter(context) { 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) { override fun onPerformSyncDo(account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult) {
super.onPerformSync(account, extras, authority, provider, 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) RefreshCollections(account, CollectionInfo.Type.CALENDAR).run()
notificationManager.cancel()
try { updateLocalCalendars(provider, account, settings)
val settings = AccountSettings(context, account)
if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && !checkSyncConditions(settings))
return
RefreshCollections(account, CollectionInfo.Type.CALENDAR).run() val principal = HttpUrl.get(settings.uri!!)!!
updateLocalCalendars(provider, account, settings) 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)
val principal = HttpUrl.get(settings.uri!!)!! 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") Logger.log.info("Calendar sync complete")

View File

@ -30,63 +30,34 @@ class ContactsSyncAdapterService : SyncAdapterService() {
private class ContactsSyncAdapter(context: Context) : SyncAdapterService.SyncAdapter(context) { 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) { override fun onPerformSyncDo(account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult) {
super.onPerformSync(account, extras, authority, provider, syncResult) val addressBook = LocalAddressBook(context, account, provider)
val notificationManager = SyncNotification(context, "journals-contacts", Constants.NOTIFICATION_CONTACTS_SYNC)
notificationManager.cancel()
val settings: AccountSettings
try { 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 if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && !checkSyncConditions(settings))
try { return
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)) Logger.log.info("Synchronizing address book: " + addressBook.url)
return Logger.log.info("Taking settings from: " + addressBook.mainAccount)
Logger.log.info("Synchronizing address book: " + addressBook.url) val principal = HttpUrl.get(settings.uri!!)!!
Logger.log.info("Taking settings from: " + addressBook.mainAccount) ContactsSyncManager(context, account, settings, extras, authority, provider, syncResult, addressBook, principal).use {
it.performSync()
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))
} }
Logger.log.info("Contacts sync complete") Logger.log.info("Contacts sync complete")

View File

@ -12,6 +12,7 @@ import android.accounts.Account
import android.app.PendingIntent import android.app.PendingIntent
import android.app.Service import android.app.Service
import android.content.* import android.content.*
import android.database.sqlite.SQLiteException
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.net.wifi.WifiManager import android.net.wifi.WifiManager
import android.os.Bundle import android.os.Bundle
@ -19,6 +20,8 @@ import android.os.IBinder
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.util.Pair import androidx.core.util.Pair
import at.bitfire.ical4android.CalendarStorageException
import at.bitfire.vcard4android.ContactsStorageException
import com.etesync.syncadapter.* import com.etesync.syncadapter.*
import com.etesync.syncadapter.journalmanager.Crypto import com.etesync.syncadapter.journalmanager.Crypto
import com.etesync.syncadapter.journalmanager.Exceptions 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.CollectionInfo
import com.etesync.syncadapter.model.JournalEntity import com.etesync.syncadapter.model.JournalEntity
import com.etesync.syncadapter.model.JournalModel import com.etesync.syncadapter.model.JournalModel
import com.etesync.syncadapter.ui.DebugInfoActivity
import com.etesync.syncadapter.ui.PermissionsActivity import com.etesync.syncadapter.ui.PermissionsActivity
import com.etesync.syncadapter.utils.NotificationUtils import com.etesync.syncadapter.utils.NotificationUtils
import okhttp3.HttpUrl import okhttp3.HttpUrl
@ -45,12 +49,53 @@ abstract class SyncAdapterService : Service() {
abstract class SyncAdapter(context: Context) : AbstractThreadedSyncAdapter(context, false) { 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) { 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()) Logger.log.log(Level.INFO, "$authority sync of $account has been initiated.", extras.keySet().toTypedArray())
// required for dav4android (ServiceLoader) // required for dav4android (ServiceLoader)
Thread.currentThread().contextClassLoader = context.classLoader 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) { override fun onSecurityException(account: Account, extras: Bundle, authority: String, syncResult: SyncResult) {

View File

@ -45,72 +45,36 @@ class TasksSyncAdapterService: SyncAdapterService() {
class TasksSyncAdapter( class TasksSyncAdapter(
context: Context context: Context
): SyncAdapter(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) { override fun onPerformSyncDo(account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult) {
super.onPerformSync(account, extras, authority, provider, syncResult)
val notificationManager = SyncNotification(context, "journals-tasks", Constants.NOTIFICATION_TASK_SYNC) val taskProvider = TaskProvider.fromProviderClient(context, provider)
notificationManager.cancel()
try { // make sure account can be seen by OpenTasks
val taskProvider = TaskProvider.fromProviderClient(context, provider) 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 val accountSettings = AccountSettings(context, account)
if (Build.VERSION.SDK_INT >= 26) /* don't run sync if
AccountManager.get(context).setAccountVisibility(account, taskProvider.name.packageName, AccountManager.VISIBILITY_VISIBLE) - 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) RefreshCollections(account, CollectionInfo.Type.TASKS).run()
/* 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
updateLocalTaskLists(taskProvider, account, accountSettings)
RefreshCollections(account, CollectionInfo.Type.TASKS).run() val principal = HttpUrl.get(accountSettings.uri!!)!!
updateLocalTaskLists(taskProvider, account, accountSettings) 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}]")
val principal = HttpUrl.get(accountSettings.uri!!)!! 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") Logger.log.info("Task sync complete")