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")